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

私がしたいのは、GroupID のリストにある「グループ」へのメンバーシップを持たない SystemUserID のリストに一致するすべての「SystemUser」レコードを見つけることです。

そのため、ID の 2 つの個別のリストが 1 つのクエリで比較されます。現在これを行うと考えられる最速の方法は次のとおりです。

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」チェックはより効率的でしょうか? または、2 つのリストによる処理とフィルタリングのより良い方法を考えられますか?

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

クエリのテキストを書き直すことで問題を解決できるクエリはほとんどなく、あなたのクエリも例外ではありません。パフォーマンスの問題の原因はほとんどの場合、インデックスの欠落です。これも例外ではありません。

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. SystemUserIDindex on GroupMembership(結合用)

DDL (これは追加するためのものです!) は、2) と 3) を解決したことを明らかにしますが、1) は解決しませんでした。不足しているインデックスを追加します。

CREATE NONCLUSTERED INDEX idx_GroupMembership_GroupID ON GroupMembership(GroupID, SystemUserID)

経験則として: (leftId, rightId) 形式の多対多テーブルには、常に(leftId, rightId)onとon の両方のインデックスが必要(rightId, leftId)です。

于 2012-08-22T11:54:43.313 に答える
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 に答える
0

私の経験では、値リストが増加したときに IN を使用するよりも、目的の値を含む一時テーブルに参加する方がより良い結果が得られることがよくあります。値が 100 の場合、最初は習慣でその方法を習得していましたが、実際に公開する前に両方を書き留めて、計画と統計を比較することになるでしょう。

于 2012-08-22T10:47:24.283 に答える