2

我有一个相当可怕的查询,它使用需要在我们的数据库上运行的 2 个“IN”语句。首先是模式(此示例已简化):

CREATE TABLE [dbo].[SystemUser]
(
    [SystemUserID] [int] IDENTITY(1,1) NOT NULL,
    [FirstName] [nvarchar](50) NULL,
    [Surname] [nvarchar](50) NULL
    CONSTRAINT [PK_ApplicationUser] PRIMARY KEY CLUSTERED 
    (
        [SystemUserID] ASC
    )
)
GO

CREATE TABLE [dbo].[Group]
(
    [GroupID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NULL
    CONSTRAINT [PK_Group] PRIMARY KEY CLUSTERED 
    (
        [GroupID] ASC
    )
)
GO

CREATE TABLE [dbo].[GroupMembership]
(
    [SystemUserID] [int] NOT NULL,
    [GroupID] [int] NOT NULL
    CONSTRAINT [PK_GroupMembership] PRIMARY KEY CLUSTERED 
    (
        [SystemUserID] ASC,
        [GroupID] ASC
    )
)
GO

我想要做的是找到与 SystemUserID 列表匹配的所有“SystemUser”记录,这些 SystemUserID 不具有 GroupID 列表中的“组”的成员资格。

因此,在一个查询中比较了 2 个单独的 ID 列表。我目前能想到的最快方法如下:

SELECT SU.SystemUserID
FROM [dbo].[SystemUser] SU 
LEFT JOIN
(
    SELECT GM.SystemUserID
    FROM [dbo].[GroupMembership] GM
    WHERE GM.GroupID IN
    (
        1, 7, 8, 10, 32
    )
) GM ON GM.SystemUserID = SU.SystemUserID
WHERE SU.SystemUserID IN
(
    10, 61, 80, 93, 98
)
AND GM.SystemUserID IS NULL /* Not matched */

有什么我想念的吗?“WHERE NOT EXISTS”检查会更有效吗?或者你能想出更好的方法来处理和过滤这两个列表吗?

4

4 回答 4

4

假设 SQL Server 2005 或更高版本,

SELECT SU.SystemUserID
FROM [dbo].[SystemUser] SU 
WHERE SU.SystemUserID IN
(
    10, 61, 80, 93, 98
)
EXCEPT
SELECT GM.SystemUserID
FROM [dbo].[GroupMembership] GM
WHERE GM.GroupID IN
(
    1, 7, 8, 10, 32
)
于 2012-08-22T09:54:34.030 回答
2

而不是使用IN(发出单独的查询),使用BETWEEN包围范围:

SELECT SU.SystemUserID 
FROM [dbo].[SystemUser] SU  
LEFT JOIN 
( 
    SELECT GM.SystemUserID 
    FROM [dbo].[GroupMembership] GM 
    WHERE GM.GroupID BETWEEN 1 AND 5

) GM ON GM.SystemUserID = SU.SystemUserID 
WHERE SU.SystemUserID BETWEEN 10 AND 14
AND GM.SystemUserID IS NULL /* Not matched */

如果您的范围根本不是连续的,请创建一个临时表(或 CTE),填充值,然后对其进行内部连接。

于 2012-08-22T09:47:49.337 回答
2

很少有问题查询可以通过重写查询文本来解决,您的也不例外。性能问题的罪魁祸首几乎总是缺少索引,你的也不例外。

SELECT SU.SystemUserID
FROM [dbo].[SystemUser] SU 
LEFT JOIN
(
    SELECT GM.SystemUserID
    FROM [dbo].[GroupMembership] GM
    WHERE GM.GroupID IN
    (
        1, 7, 8, 10, 32
    )
) GM ON GM.SystemUserID = SU.SystemUserID
WHERE SU.SystemUserID IN
(
    10, 61, 80, 93, 98
)
AND GM.SystemUserID IS NULL /* Not matched */

所以你需要:

  1. GroupID索引GroupMembership
  2. SystemUserID索引SystemUser
  3. SystemUserID索引GroupMembership(用于连接)

您的 DDL(这是为了添加它!)表明您已经解决了 2)和 3),但没有解决 1)。所以添加缺失的索引:

CREATE NONCLUSTERED INDEX idx_GroupMembership_GroupID ON GroupMembership(GroupID, SystemUserID)

根据经验: (leftId, rightId) 形式的多对多表总是需要一个索引 on(leftId, rightId)和一个 on (rightId, leftId)

于 2012-08-22T11:54:43.313 回答
0

根据我的经验,我经常通过加入一个包含所需值的临时表获得更好的结果,而不是在值列表增加时使用 IN。有 100 个值,我首先是习惯性的,但可能会在它上线之前写下两者并比较计划和统计数据。

于 2012-08-22T10:47:24.283 回答