【问题标题】:Improve SQL query performance. UNION vs OR in this situation提高 SQL 查询性能。 UNION vs OR 在这种情况下
【发布时间】:2021-10-28 18:18:59
【问题描述】:

问题

因此,我在使用此 SQL 查询时面临的情况是,运行大约需要 12 秒,这使得屏幕超级慢。

目标

进行必要的更改以提高性能并使其更快。我正在考虑使用 UNION 而不是 Where 子句中的 OR?

SELECT Tool.*, Interview.*
    FROM Tool
INNER JOIN Interview ON Interview.Id = Tool.InterviewId
WHERE (Tool.ToolTypeId = @ToolTypeId 
AND Tool.Is_Active = 1
AND Tool.InterviewId = @InterviewId
AND Tool.ToolId = @ToolId
AND Tool.CustomerId = @CustomerId)

OR Tool.Id = (
    SELECT TOP 1 SubTool.Id
            FROM Tool SubTool
    INNER JOIN Interview subInterview ON subInterview.Id = SubTool.ToolId 
            WHERE SubTool.ToolTypeId = @ToolTypeId
            AND SubTool.Is_Active = 1  
            AND SubTool.InterviewId != @InterviewId  
            AND SubTool.ToolId = @ToolId
            AND subTool.CustomerId = @CustomerId
            AND convert(datetime, subTool.DateTime, 120) < @ToolDateTime
            ORDER BY subTool.DateTime DESC, subTool.StartDate DESC, 
                subTool.EndDate, subTool.Id DESC
)
                              
    ORDER BY Tool.StartDate, Tool.Id

注意:我认为在这种情况下,实际的查询输出不是必需的,因为我们正在寻找一些可能会影响性能的结构性问题。

【问题讨论】:

  • 为了提高性能,我们需要使用“粘贴计划”查看执行计划。你还说联合可能会改进它,那么当你尝试它时发生了什么?确保它是union all,而不是union。
  • 将尝试访问数据库以便能够检索它,我会尽可能发布它。我只是认为可以确定一些可能影响性能的查询问题,顺便说一下,使用 try_Convert 而不是 convert 也会提高性能,对吧?
  • SQL 不是这样工作的,用于构建查询的语法并不代表 SQL Server 构建的执行计划。它使用包括索引、统计数据等在内的一系列因素来确定如何最好地满足您的要求。因此,了解正在发生的事情的唯一方法是检查执行计划。虽然您使用联合的想法是解决此问题的常见方法,但您只有在尝试时才会知道它是否有帮助。
  • convert(datetime, Tool.DateTime, 120) &lt; @ToolDateTime 看起来效率低下,为什么它的列已经不是datetime 了?那个子查询也是如此。为了更好地帮助性能,我们需要查看表和索引以及执行计划
  • 关于日期时间作为字符串的主题。由于样式 120 是 yyyy-mm-dd hh:mi:ss (24h),因此字符串已经采用了这样的格式,它的排序顺序与转换为 DATETIME 后的顺序相同。这意味着您可以将参数@ToolDateTime 转换为字符串并使用&lt; 而无需转换DateTime 列,并获得相同的结果。这将是 SARGable 并可能降低一些成本。 (尽管我相信执行计划的这方面可能对整个运行时的贡献很小。)

标签: sql sql-server tsql database-performance


【解决方案1】:

我建议改写查询以消除WHERE 子句中的子查询。

如果你不考虑条件在结果集中寻找一行,你可以使用:

SELECT TOP (1) Tool.*, Interview.*
FROM Tool JOIN
     Interview
     ON Interview.Id = Tool.InterviewId
WHERE Tool.ToolTypeId = @ToolTypeId AND
      Tool.Is_Active = 1
      Tool.ToolId = @ToolId AND
      Tool.CustomerId = @CustomerId AND
      (Tool.InterviewId = @InterviewId OR
       convert(datetime, Tool.DateTime, 120) < @ToolDateTime
      )
ORDER BY (CASE WHEN Tool.InterviewId = @InterviewId THEN 1 ELSE 2 END),
         Tool.DateTime DESC, sTool.StartDate DESC, Tool.EndDate, Tool.Id DESC;

您的最终ORDER BY 表明您期望第一个条件不止一行。因此,您可以使用子查询和窗口函数:

SELECT ti.*
FROM (SELECT Tool.*, Interview.*,
             COUNT(*) OVER (PARTITION BY (CASE WHEN Tool.InterviewId = @InterviewId THEN 1 ELSE 0 END)) as cnt_interview_match,
             ROW_NUMBER() OVER (ORDER BY subTool.DateTime DESC, subTool.StartDate DESC, subTool.EndDate, subTool.Id DESC) as seqnum
      FROM Tool JOIN
           Interview
           ON Interview.Id = Tool.InterviewId
      WHERE Tool.ToolTypeId = @ToolTypeId AND
            Tool.Is_Active = 1
            Tool.ToolId = @ToolId AND
            Tool.CustomerId = @CustomerId AND
            (Tool.InterviewId = @InterviewId OR
             convert(datetime, Tool.DateTime, 120) < @ToolDateTime
            )
      ) ti
WHERE InterviewId = @InterviewId OR
      (cnt_interview_match = 0 AND seqnum = 1);

请注意,子查询要求列具有不同的名称,因此您可能需要修改它。

然后,您需要在TOOL(ToolTypeId, Is_Active, ToolId, CustomerId, InterviewId, DateTime) 上建立索引。我假设Interview(Id) 已经被索引为表的主键。

【讨论】:

  • 我将尝试使用 Window 函数的第二种方法,因为您对预期不止一行是正确的。一个问题,在外部查询中,您有 WHERE Tool.InterviewId = @InterviewId OR (cnt_interview_match = 0 AND seqnum = 1); .例如工具应该重命名为 ti 正确吗?不应该是 ti.interviewId 和 ti.cnt_interview_match 吗?无论如何,我仍然会尝试访问执行计划,以便我们清楚地了解 SQL Server 在后台执行的操作
  • @JoãoEs 。 . .那是一个错字。
【解决方案2】:

我对您的查询所做的(以及通过推断,您希望它做什么)的解释与 @GordonLinoff 的不同。


Gordon 的查询可以解释为...

  • 如果有Tool.InterviewId = @InterviewId的行...
    • 返回那些行并且只返回那些行
  • 如果没有这样的行要返回...
    • 返回其他InterviewId的最新行

我对您的查询的实际作用的理解是,您希望始终同时返回两种可能性。

  • 这种歧义是您真的应该包含示例数据的一个例子。

例如,这将是一个重新措辞或您现有的查询(根据您自己的建议使用UNION)...

WITH
  filter_tool_customer AS
(
    SELECT Tool.*, Interview.*
      FROM Tool
INNER JOIN Interview ON Interview.Id = Tool.InterviewId
     WHERE Tool.ToolTypeId = @ToolTypeId 
       AND Tool.Is_Active  = 1
       AND Tool.ToolId     = @ToolId
       AND Tool.CustomerId = @CustomerId
),
  matched AS
(
  SELECT *
    FROM filter_tool_customer
   WHERE InterviewId = @InterviewId
),
  mismatched AS
(
  SELECT TOP 1 *
    FROM filter_tool_customer
   WHERE InterviewId <> @InterviewId
     AND DateTime    <  CONVERT(VARCHAR(20), @ToolDateTime, 120)
ORDER BY DateTime DESC,
         StartDate DESC,
         EndDate,
         Id DESC
),
  combined AS
(
  SELECT * FROM matched
  UNION ALL
  SELECT * FROM mismatched
)
SELECT * FROM combined ORDER BY StartDate, Id

【讨论】:

    猜你喜欢
    • 2010-11-03
    • 1970-01-01
    • 1970-01-01
    • 2016-07-10
    • 1970-01-01
    • 2014-06-26
    • 2017-04-20
    • 1970-01-01
    • 2020-12-10
    相关资源
    最近更新 更多