SQL Server / T-SQL:查询优化帮助

Posted

技术标签:

【中文标题】SQL Server / T-SQL:查询优化帮助【英文标题】:SQL Server / T-SQL : query optimization assistance 【发布时间】:2016-08-12 18:36:45 【问题描述】:

我有这个 QA 逻辑,可以在 RoomID 中的每个 AuditID 中查找错误,以查看它们的 AuditType 是否从未标记为 Complete 或者它们是否具有两个完整状态。最后,它只选择有错误的RoomIDs 中最大的AuditDate,以避免显示相同RoomID 的多个实例,因为每个房间有很多审计。

问题在于 AUDIT 表非常大,需要很长时间才能运行。我想知道是否有任何方法可以更快地达到相同的结果。

提前谢谢你!

IF object_ID('tempdb..#AUDIT') is not null  drop table #AUDIT
IF object_ID('tempdb..#ROOMS') is not null  drop table #ROOMS
IF object_ID('tempdb..#COMPLETE') is not null  drop table #COMPLETE
IF object_ID('tempdb..#FINALE') is not null  drop table #FINALE

SELECT distinct 
    oc.HotelID, o.RoomID
INTO #ROOMS
FROM  dbo.[rooms] o
LEFT OUTER JOIN dbo.[hotels] oc on o.HotelID = oc.HotelID
WHERE 
    o.[status] = '2' 
    AND o.orderType = '2' 

SELECT 
    t.AuditID, t.RoomID, t.AuditDate, t.AuditType
INTO  
    #AUDIT
FROM 
    [dbo].[AUDIT] t
WHERE
    t.RoomID IN (SELECT RoomID FROM #ROOMS) 


SELECT 
    t1.RoomID, t3.AuditType, t3.AuditDate, t3.AuditID, t1.CompleteStatus
INTO 
    #COMPLETE
FROM
    (SELECT 
         RoomID,
         SUM(CASE WHEN AuditType = 'Complete' THEN 1 ELSE 0 END) AS CompleteStatus
     FROM 
         #AUDIT
     GROUP BY 
         RoomID) t1
INNER JOIN
    #AUDIT t3 ON t1.RoomID = t3.RoomID
WHERE 
    t1.CompleteStatus = 0
    OR t1.CompleteStatus > 1


SELECT 
    o.HotelID, o.RoomID, 
    a.AuditID, a.RoomID, a.AuditDate, a.AuditType, a.CompleteStatus,
    c.ClientNum
INTO
    #FINALE
FROM 
    #ROOMS O
LEFT OUTER JOIN 
    #COMPLETE a on o.RoomID = a.RoomID
LEFT OUTER JOIN 
    [dbo].[clients] c on o.clientNum = c.clientNum


SELECT
    t.*,
    Complete_Error_Status = CASE WHEN t.CompleteStatus = 0 
                                    THEN 'Not Complete'
                                 WHEN t.CompleteStatus > 1 
                                    THEN 'Complete More Than Once' 
                            END
FROM
    #FINALE t
INNER JOIN
    (SELECT 
         RoomID, MAX(AuditDate) AS MaxDate
     FROM
         #FINALE
     GROUP BY 
         RoomID) tm ON t.RoomID = tm.RoomID AND t.AuditDate = tm.MaxDate

【问题讨论】:

当您可以简单地通过WHERE AuditType = 'Complete' 过滤数据集时,为什么要使用SUM(CASE WHEN AuditType = 'Complete' THEN 1 ELSE 0 END) @Nicarus 我的思考过程是我正在查看所有在 roomID 中 AuditType = 'complete' 的实例。无论如何,如果您知道更好的方法..我在这里学习.. 考虑索引您的临时表。 WHERE EXISTS 通常(但不总是)比 IN 快。 OR 很慢,尽可能用 UNION ALL 替换。 @H.Ben - 我的意思是你没有限制数据集,但你只是对有限的数据集求和。这是不必要的。 您使用的是 2012 年或更高版本的 sql-server 哪个版本?通读此查询,您可能可以转储/不使用大量临时表,从而更有效地限制连接结果并使用 MAX() OVER 窗口函数,它应该执行得更好,但最终索引的内容和不索引的内容以及其他几件事也很关键 【参考方案1】:

您可以改进的部分是这一节。请参阅内联 cmets。

SELECT 
    t1.RoomID, t3.AuditType, t3.AuditDate, t3.AuditID, t1.CompleteStatus
INTO 
    #COMPLETE
FROM
    (SELECT 
         RoomID,
         COUNT(1) AS CompleteStatus
         -- Use the above along with the WHERE clause below
         -- so that you are aggregating fewer records and
         -- avoiding a CASE statement. Remove this next line.
         --SUM(CASE WHEN AuditType = 'Complete' THEN 1 ELSE 0 END) AS CompleteStatus
     FROM 
         #AUDIT
     WHERE
         AuditType = 'Complete'
     GROUP BY 
         RoomID) t1
INNER JOIN
    #AUDIT t3 ON t1.RoomID = t3.RoomID
WHERE
    t1.CompleteStatus = 0
    OR t1.CompleteStatus > 1

【讨论】:

我现在正在尝试这个,最后一部分是错误的,并且已经讨论过 =0 或 >1 不等于 >=0 后者包括 1 而 1 是唯一不应该出现错误的实例被提升。 对不起@Nicarus 它速度较慢.. 就像我之前说的
  审计表是问题所在:SELECT t.AuditID, t.RoomID, t.AuditDate, t.AuditType INTO # AUDIT FROM [dbo].[AUDIT] t WHERE t.RoomID IN(从 #ROOMS 中选择 RoomID)
你是如何测试的?你在使用查询计划器/分析器吗? 另外,你有AuditType的索引吗? 我正在将其转换为我的实际查询并在 SQL Server 2012 上执行它,我没有使用规划器/分析器,AuditType 上有一个非聚集索引【参考方案2】:

只是一个想法。简化您的代码和解决方案。您没有有效地将数据集过滤得更小,因此您继续查询占用大量资源的整个表,并且您的临时表正在成为那些没有索引(PK、FK、++??)的列的完整副本原始表利用。这绝不是一个完美的解决方案,但它是一个关于如何整合逻辑并减少整体数据集的想法。试一试,看看它是否更适合您。

请注意,这将返回尚未完成或多次完成审核的任何房间的最后审核记录。

;WITH cte AS (
    SELECT
       o.RoomId
       ,o.clientNum
       ,a.AuditId
       ,a.AuditDate
       ,a.AuditType
       ,NumOfAuditsComplete = SUM(CASE WHEN a.AuditType = 'Complete' THEN 1 ELSE 0 END) OVER (PARTITION BY o.RoomId)
       ,RowNum = ROW_NUMBER() OVER (PARTITION BY o.RoomId ORDER BY a.AuditDate DESC)
    FROm
       dbo.Rooms o
       LEFT JOIN dbo.Audit a
       ON o.RoomId = a.RoomId
WHERE
   o.[Status] = 2
   AND o.OrderType = 2
)

SELECT
    oc.HotelId
    ,cte.RoomId
    ,cte.AuditId
    ,cte.AuditDate
    ,cte.AuditType
    ,cte.NumOfAuditsComplete
    ,cte.clientNum
    ,Complete_Error_Status = CASE WHEN cte.NumOfAuditsComplete > 1 THEN 'Complete More Than Once' ELSE 'Not Complete' END
FROM
    cte
    LEFT JOIN dbo.Hotels oc
    ON cte.HotelId = oc.HotelId
    LEFT JOIN dbo.clients c
    ON cte.clientNum = c.clientNum
WHERE
    cte.RowNum = 1
    AND cte.NumOfAuditsComplete != 1

另外请注意我改变了你的

WHERE 
    o.[status] = '2' 
    AND o.orderType = '2'

WHERE 
    o.[status] = 2 
    AND o.orderType = 2

是没有单引号的数字。如果数据类型确实是 varchar,则将它们添加回来,但是当您将数值列作为 varchar 查询时,它将进行数据转换,并且可能无法利用您在表上构建的索引。

【讨论】:

这似乎更慢.. 抱歉 有时 ctes 可能是因为 sql 引擎并不总是正确地优化它们。我猜您可能会使用包含 AuditType 的审计索引。如果您将执行计划发布到您的查询中,并且我也很想看到这个,我相信人们将能够给您更具体的建议。如果您不知道如何获取它,请在查询窗口中右键单击并选择“包含实际执行计划”,它会在结果和消息旁边为您提供另一个选项卡 另外,如果您单击显示估计的执行计划,它通常会为您提供建议的缺失索引,这可能是您需要的。 感谢您的 cmets @Matt 就像我说的那样,AUDIT 表确实很大,并且需要永远搜索每个房间内的每个审核。 是的,我知道了,我有一个用于查询的 7 家酒店的事务性物业管理系统。构建索引将占用更多空间,但可以使其速度更快。因此,如果您在 Audit 上构建一个考虑 AuditType 的索引,那么您可以生成一个临时表,其中 roomid 作为主键(这是一个索引)来使用,但是当您返回获取最后一行时,您仍然会有一个 row_number 操作,它将再次命中那个大表来找到它。

以上是关于SQL Server / T-SQL:查询优化帮助的主要内容,如果未能解决你的问题,请参考以下文章

尝试在 T-SQL 查询中使用 Levenshtein 距离 - 请帮助优化

优化我的 T-SQL 查询以提高性能

如何优化这些 T-SQL 查询?

sql server T-SQL高级查询

T-SQL 查询优化

SQL Server T-SQL高级查询