【问题标题】:Multiple Table Joins With One-to-Many Relationships具有一对多关系的多个表连接
【发布时间】:2012-06-15 17:31:18
【问题描述】:

使用 SQL Server 2008。

我有多个位置,每个位置都包含多个部门,每个部门都包含多个项目,这些项目可以进行零到多次扫描。每个扫描都与可能有也可能没有截止时间的特定操作相关。每个项目还属于属于特定作业的特定包。每个作业都包含一个或多个包,其中包含一个或多个项目。

+=============+                         +=============+
|  Locations  |                         |     Jobs    |
+=============+                         +=============+
      ^                                       ^
      |                                       |
+=============+     +=============+     +=============+
| Departments | <-- |    Items    | --> |   Packages  |
+=============+     +=============+     +=============+
                          ^
                          |
                    +=============+     +=============+
                    |    Scans    | --> | Operations  |
                    +=============+     +=============+

我试图做的是获取按位置和扫描日期分组的作业的扫描计数。棘手的部分是我只想按每个项目的日期/时间计算第一次扫描,其中操作的截止时间不为空。 (注意:扫描肯定不会按表中的日期/时间顺序排列。)

我的查询让我得到了正确的结果,但是当 Job 的 Item 数量达到 75,000 左右时,它的速度非常慢。我正在推动一个新的服务器——我知道我们的硬件缺乏——但是我想知道我在查询中是否有什么事情让它陷入困境

从我可以从执行计划中收集到的信息来看,查询的大部分成本似乎都在子查询中,以查找每个项目的第一次扫描。它对操作表索引(ID,Cutoff)进行索引扫描(0%),然后进行惰性假脱机(19%)。它对 Scans 表索引(ItemID、DateTime、OperationID、ID)进行索引查找 (61%)。随后的嵌套循环(内连接)只有 2%,Top 运算符为 0%。 (并不是说我真的了解我刚刚输入的大部分内容,但我正在努力提供尽可能多的信息.​​.....)

这是查询:

SELECT
    Departments.LocationID
    , DATEADD(dd, 0, DATEDIFF(dd, 0, Scans.DateTime))
    , COUNT(Scans.ItemID) AS [COUNT]
FROM
    Items           
    INNER JOIN Scans
        ON Scans.ID = 
    (
        SELECT TOP 1
            Scans.ID 
        FROM
            Scans
        INNER JOIN Operations
            ON Scans.OperationID = Operations.ID
        WHERE
            Operations.Cutoff IS NOT NULL
            AND Scans.ItemID = Items.ID             
        ORDER BY
            Scans.DateTime
    )
    INNER JOIN Operations
        ON Scans.OperationID = Operations.ID
    INNER JOIN Packages
        ON Items.PackageID = Packages.ID
    INNER JOIN Departments
        ON Items.DepartmentID = Departments.ID      
WHERE
    Packages.JobID = @ID        
GROUP BY
    Departments.LocationID 
    , DATEADD(dd, 0, DATEDIFF(dd, 0, Scans.DateTime));

这将返回这样的结果样本:

8   2012-06-08 00:00:00.000 11842
21  2012-06-07 00:00:00.000 502
11  2012-06-12 00:00:00.000 1841
15  2012-06-11 00:00:00.000 4314
16  2012-06-09 00:00:00.000 278
23  2012-06-12 00:00:00.000 1345
6   2012-06-06 00:00:00.000 2005
20  2012-06-08 00:00:00.000 352
14  2012-06-07 00:00:00.000 2408
8   2012-06-11 00:00:00.000 290
19  2012-06-10 00:00:00.000 85
20  2012-06-11 00:00:00.000 5484
7   2012-06-10 00:00:00.000 2389
16  2012-06-06 00:00:00.000 6762
18  2012-06-09 00:00:00.000 4473
14  2012-06-10 00:00:00.000 2364
1   2012-06-11 00:00:00.000 1531
22  2012-06-08 00:00:00.000 14534
5   2012-06-10 00:00:00.000 11908
9   2012-06-12 00:00:00.000 47
19  2012-06-07 00:00:00.000 559
7   2012-06-07 00:00:00.000 2576

这是执行计划(不确定自原始帖子以来我进行了哪些更改,但成本百分比略有不同。但瓶颈似乎仍然在同一区域):

【问题讨论】:

  • 你能告诉我们查询执行计划吗? (作为图像)
  • 您使用的是哪个版本的 SQL Server?

标签: sql-server sql-server-2008 tsql join


【解决方案1】:

我对将其标记为答案有点怀疑,因为我确信我们仍然可以从查询中挤出一点汁液。但这确实将我的测试运行时间从 22 秒缩短到 6 秒(在 Scans 上添加了索引:OperationID,包括 DateTime 和 ItemID):

WITH CTE AS 
(
    SELECT
        Items.ItemID AS ID          
        , Scans.DateTime AS [DateTime]
        , Operations.Cutoff AS Cutoff           
        , ROW_NUMBER() OVER (PARTITION BY Items.ID ORDER BY Scans.DateTime) AS RN
        FROM
            Items
            INNER JOIN Scans            
                ON Items.ID = Scans.ItemID
            INNER JOIN Operations
                ON Scans.OperationID = Operations.ID
            INNER JOIN Packages
                ON Items.PackageID = Packages.ID
        WHERE
            Operations.Cutoff IS NOT NULL
            AND Packages.JobID = @ID                        
)
SELECT
    Departments.LocationID
    , CTE.DateTime
    , COUNT(Items.ID) AS COUNT
FROM
    Items           
    INNER JOIN CTE
        ON Items.ID = CTE.ID
        AND CTE.RN = 1
    INNER JOIN Packages
        ON Items.PackageID = Packages.ID
    INNER JOIN Departments
        ON Items.DepartmentID = Departments.ID      
WHERE
    Packages.JobID = @ID
GROUP BY
    Departments.LocationID 
    , CTE.DateTime

【讨论】:

    【解决方案2】:

    很难确定,但这样的事情可能会表现得更好。我用ROW_NUMBER 调用替换了您的嵌套查找。原始查询中的问题是嵌套查找 - 它会杀死您。

    注意我面前没有 SQL,所以我无法测试它,但我认为它在逻辑上是等价的。

    SELECT
        Departments.LocationID
        , DATEADD(dd, 0, DATEDIFF(dd, 0, Scans.DateTime))
        , COUNT(Scans.ItemID) AS [COUNT]
    FROM
        Items           
        INNER JOIN Scans
            ON Scans.ItemID = Items.ID
        INNER JOIN Operations
            ON Scans.OperationID = Operations.ID
        INNER JOIN Packages
            ON Items.PackageID = Packages.ID
        INNER JOIN Departments
            ON Items.DepartmentID = Departments.ID      
    WHERE
        Operations.Cutoff IS NOT NULL
        AND
        Packages.JobID = @ID
        AND
        ROW_NUMBER () OVER (PARTITION BY Items.ID ORDER BY Scans.DateTime) = 1
    GROUP BY
        Departments.LocationID 
        , DATEADD(dd, 0, DATEDIFF(dd, 0, Scans.DateTime));
    

    【讨论】:

    • 不幸的是,当我尝试在 SELECT 或 ORDER BY 子句之外使用窗口函数时,SSMS 向我咆哮。好消息是我开始“谷歌搜索”ROW_NUMBER,遇到了 CTE,我将测试查询从 22 秒缩短到 7 秒。感谢您将我推向正确的方向。
    • 太棒了。您应该将您的解决方案添加为答案并接受它。
    • 我认为你不能将ROW_NUMBER() 放在WHERE 子句中。
    【解决方案3】:

    我很好奇 - 你能运行CROSS APPLY 版本吗?

    SELECT
        Departments.LocationID
        , DATEADD(dd, 0, DATEDIFF(dd, 0, CA_Scans.DateTime))
        , COUNT(CA_Scans.ItemID) AS [COUNT]
    FROM
        Items 
        CROSS APPLY
        (
            SELECT TOP 1
                Scans.ID,
                Scans.OperationID,
                Scans.DateTime
            FROM
                Scans
            INNER JOIN Operations
                ON Scans.OperationID = Operations.ID
            WHERE
                Operations.Cutoff IS NOT NULL
                AND Scans.ItemID = Items.ID             
            ORDER BY
                Scans.DateTime
        ) CA_Scans
        INNER JOIN Operations
            ON CA_Scans.OperationID = Operations.ID
        INNER JOIN Packages
            ON Items.PackageID = Packages.ID
        INNER JOIN Departments
            ON Items.DepartmentID = Departments.ID      
    WHERE
        Packages.JobID = @ID        
    GROUP BY
        Departments.LocationID 
        , DATEADD(dd, 0, DATEDIFF(dd, 0, CA_Scans.DateTime));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-10
      • 2017-09-05
      • 1970-01-01
      • 1970-01-01
      • 2015-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多