【问题标题】:What's the best way to get intersected data from one table?从一张表中获取相交数据的最佳方法是什么?
【发布时间】:2013-04-16 00:27:49
【问题描述】:

假设我有下表

CREATE TABLE [dbo].[TestData](
    [ID] [bigint] NOT NULL,
    [InstanceID] [int] NOT NULL,
    [Field] [int] NULL,
    [UserID] [bigint] NOT NULL
) ON [PRIMARY]

GO
INSERT [dbo].[TestData] ([ID], [InstanceID], [Field], [UserID]) 
VALUES (1, 1, NULL, 1000),(2, 1, NULL, 1002),(3, 1, NULL, 1000),
    (4, 1, NULL, 1003),(5, 2, NULL, 1002), (6, 2, NULL, 1005),
    (7, 2, NULL, 1006),(8, 2, NULL, 1007),(9, 3, NULL, 1002),
    (10, 3, NULL, 1006),(11, 3, NULL, 1009),(12, 3, NULL, 1010),
    (13, 1, NULL, 1006),(14, 2, NULL, 1002),(15, 3, NULL, 1003)
GO

我寻找编写查询的最佳实践,以使用 UserID 获取两个实例之间的完整交叉数据行

例如,InstanceID 1 和 2 之间相交的 UserIDs 是( 1002 , 1006 ),为了得到结果,我用以下两种不同的方式写了查询:

Select * From TestData
Where UserID in 
( 
    Select T1.UserID From TestData T1 Where InstanceID = 1
        Intersect
    Select T2.UserID From TestData T2 Where InstanceID = 2
)
and InstanceID in (1,2) Order By 1

第二

Select * From TestData
Where UserID in 
( 
    Select Distinct T1.UserID 
    From TestData T1 join TestData T2 on T1.UserID = T2.UserID
    Where T1.InstanceID = 1 and T2.InstanceID = 2
)
and InstanceID in (1,2) Order By 1

所以结果会是

以上查询之一是获得结果的最佳方式吗??

【问题讨论】:

    标签: sql sql-server database performance


    【解决方案1】:

    使用EXISTS 比使用IN 更好。使用IN 子查询时,将处理整个结果集。使用EXISTS,它只会搜索匹配的内容。至于你的问题,我认为INTERSECT 实现只是简单地进行连接,所以应该没有区别。

    编辑:Here 的帖子说,对于 INEXISTS,优化器也会同样对待它们(截至 2008 年)。所以几乎我的猜测以及我刚刚读到的内容都归结为:它们会执行相同的操作,因为优化器知道。

    【讨论】:

    • 在简化阶段,查询优化器并不总是以相同的方式处理 IN 和 EXISTS。即使在测试上述代码时,我在 EXISTS 和 IN 之间进行测试时也收到了两个不同的计划。但是,当我在 ID 字段上创建 PRIMARY KEY 时,我从查询优化器获得了相同的结果,而无需对代码进行额外更改。 EXISTS 绝对是两者中更安全的一个。
    • 我想我只是假设了 ID 字段的主键。我倾向于EXISTS,但我认为他们那里的quuuury优化人员足够聪明,知道你的意思。
    【解决方案2】:

    如果您要使用 EXISTS 语句,以下是查询示例:

    SELECT * 
    FROM TestData td
    WHERE td.InstanceID IN (1, 2)
    AND EXISTS
        (SELECT 1
        FROM TestData sub
        WHERE td.UserID = sub.UserID
        AND sub.InstanceID = 2)
    AND EXISTS
        (SELECT 1
        FROM TestData sub
        WHERE td.UserID = sub.UserID
        AND sub.InstanceID = 1)
    ORDER BY 1;
    

    对于提供的示例数据,三种解决方案中的任何一种都没有明显的性能差异。不过,我同意 Scotch 的观点,即在特定场景下使用 EXISTS 语句将有助于提高 IN 语句的性能。

    提高性能的最佳方法是使用 PRIMARY KEY 创建表。将 ID 字段设置为 PRIMARY KEY 将使性能提高 50%,因为查询的最高成本是对数据进行排序。

    【讨论】:

      【解决方案3】:

      您也可以通过聚合和连接来做到这一点:

      select td.*
      from TestData td join
           (select td.userid
            from TestData
            group by td.userId
            having sum(case when InstanceId = 1 then 1 else 0 end) > 0 and
                   sum(case when InstanceId = 2 then 1 else 0 end) > 0
          ) td2
          on td.userid = td2.userid
      

      聚合的优点是having 子句使其在您可以表示的条件方面非常灵活。如果您在userId, InstanceId 上有索引,性能将是最好的。

      【讨论】:

      • 您确定使用聚合将获得最佳性能吗?因为我认为使用“相交”会更好..你能解释一下吗?
      • @AmrBadawy 。 . .不,我不确定哪个性能最好。为此,您需要测试不同的解决方案。我更喜欢聚合解决方案,因为它通常可以解决“集合中的”问题(这只是此类问题的一个示例)。
      【解决方案4】:

      该脚本被两个Index seek操作和一个Distinct排序操作使用。

      SELECT ID, InstanceID, Field, UserID
      FROM [dbo].[TestData] t
      WHERE InstanceID IN(1, 2) 
        AND EXISTS (
                    SELECT 1
                    FROM [dbo].[TestData] t2
                    WHERE InstanceID IN(1, 2) AND t.UserID = t2.UserID
                    HAVING COUNT(DISTINCT t2.InstanceID) = 2
                    )
      ORDER BY t.ID
      

      ;WITH cte AS
       (
        SELECT ID, InstanceID, Field, UserId
               ,COUNT(*) OVER(PARTITION BY InstanceID, UserID) AS cntInstanceUser
        FROM [dbo].[TestData] t
        WHERE InstanceID IN(1, 2)
        )
        SELECT c.ID, c.InstanceID, c.Field, c.UserID
        FROM cte c
        WHERE EXISTS (
                      SELECT 1
                      FROM cte c2 
                      WHERE c2.UserId = c.UserID
                      HAVING COUNT(*) != c.cntInstanceUser
                      )
        ORDER BY c.ID
      

      为了提高性能,请使用此索引:

      CREATE INDEX x ON [dbo].[TestData](InstanceID, UserID) INCLUDE(Id, Field)
      

      SQLFiddle上的演示

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多