【问题标题】:Rewrite SQL Query- I need to replace NOT IN with Join重写 SQL 查询 - 我需要用 Join 替换 NOT IN
【发布时间】:2013-06-21 07:08:52
【问题描述】:

我的生产环境中有一个查询需要很长时间才能执行。我没有写这个查询,但我必须找到一种方法让它更快,因为它目前正在导致一个很大的性能问题。我需要用 Left Join 替换 NOT IN 但不知道如何重写它。现在好像在关注

SELECT TOP 1 IT.ITEMID
FROM   (SELECT CAST(ITEMID AS NUMERIC) + 1 ITEMID
        FROM   Items
        WHERE  ISNUMERIC(ITEMID) = 1
               AND CAST(ITEMID AS NUMERIC) >= 50000) IT
WHERE  IT.ITEMID NOT IN (SELECT CAST(ITEMID AS NUMERIC) ITEMID
                         FROM   Items
                         WHERE  ISNUMERIC(ITEMID) = 1)
ORDER  BY IT.ITEMID 

请建议我应该如何使用 Left Join 重写它以获得更好的性能。非常感谢任何帮助/指导。

【问题讨论】:

  • 如果您没有可供查询使用的索引,NOT INLEFT JOIN / IS NULL(或到 NOT EXISTS)将无济于事。
  • A LEFT JOIN / IS NULL 通常比 NOT IN/NOT EXISTS 慢。正如@ypercube 所说,这是一个索引问题,CAST 和谓词上的功能无助于使索引使用无效。
  • 另外,ISNUMERIC(ITEMID) = 1 AND CAST(ITEMID AS NUMERIC) >= 50000 不会做你希望它做的事情,因为 a) 无法保证评估谓词的顺序,b) 仅仅因为 ISNUMERIC 返回 1,所以不保证您可以将值转换为 NUMERIC
  • 例如,尝试以下两个选择语句:select ISNUMERIC('0d5'),然后是select CAST('0d5' as NUMERIC)
  • 所以看起来这个查询应该找到ITEMID序列中的最小间隙,即>50000?您使用的是哪个版本的 SQL Server?为什么需要这个?

标签: sql-server performance tsql sql-server-2008-r2


【解决方案1】:

试试这个 -

;WITH cte AS 
(
     SELECT DISTINCT ITEMID = 
                CASE WHEN ISNUMERIC(ITEMID) = 1 
                    THEN ITEMID 
                END
     FROM Items
)
SELECT TOP 1 ITEMID = ITEMID + 1
FROM cte t
WHERE ITEMID >= 50000
     AND NOT EXISTS(
          SELECT 1
          FROM cte t2
          WHERE t.ITEMID + 1 = t2.ITEMID
     )
ORDER BY t.ITEMID

【讨论】:

  • 可能的变化:在 cte 内移动 WHERE ITEMID >= 50000
  • 对于非数字源数据,此查询仍可能失败。 SQL Fiddle 你不能依赖CAST 发生在WHERE 之后
  • @Devart 谢谢我对您的解决方案的结果感到满意。它已经改进了很多。我知道我不能期待更多。虽然有非数字数据的空间,但我到目前为止还没有使用它,并且出于性能原因将来不会使用它。相应地对其进行了编辑并完善了。
  • @MartinSmith 非常感谢您的想法。你太棒了,帮了大忙
【解决方案2】:

如 cmets 中所述,NOT EXISTS 版本的查询在 SQLServer 中通常比 LEFT JOIN 更快 - 为了完整起见,以下是两个版本:

现有查询的左连接变体:

with cte as
(SELECT CAST(it.ITEMID AS NUMERIC) ITEMID 
 FROM Items
 WHERE ISNUMERIC(ITEMID) = 1)
select top 1 i.ITEMID + 1 ITEMID
FROM cte i
LEFT JOIN cte ni ON i.ITEMID + 1 = ni.ITEMID
WHERE i.ITEMID >= 50000 AND ni.ITEMID IS NULL

不存在现有查询的变体:

with cte as
(SELECT CAST(it.ITEMID AS NUMERIC) ITEMID 
 FROM Items
 WHERE ISNUMERIC(ITEMID) = 1)
select top 1 i.ITEMID + 1 ITEMID
FROM cte i
WHERE i.ITEMID >= 50000 AND NOT EXISTS
(SELECT NULL 
 FROM cte ni 
 WHERE i.ITEMID + 1 = ni.ITEMID)

【讨论】:

  • 非常感谢,我对比了一下,发现你说的完全正确。
【解决方案3】:

正如@gbn 指出的那样,CAST 和谓词上的函数无论如何都会使索引无效,因此将其从NOT IN 转换为LEFT JOIN / IS NULLNOT EXISTS 是没有意义的。而NOT EXISTS 在 SQL-Server 中的性能通常优于 LEFT NULL

NOT IN 不建议使用,因为存在空值(在比较的列中或由表达式产生)时的问题(错误的、意外的结果)以及由于列/表达式的可空性导致的低效计划。

ISNUMERIC() 并不总是按照您的想法行事(正如 @ Damien_The_Unbeliever 在另一条评论中指出的那样。)在某些情况下 IsNumeric 结果为 1 但转换失败。

因此,明智的做法是 - 在我看来 - 在表中添加另一列并将(可以转换的值)转换为数字并将它们存储在该列中。然后,您可以编写查询而不进行强制转换,并且可以使用该列上的索引。

如果您无法以任何方式更改表格(通过添加新列或物化视图),那么您可以尝试测试其他答案提供的各种重写。

【讨论】:

  • 感谢@ypercube 的宝贵建议。不幸的是,我无法更改基础表。我所要做的就是找到一种方法来使该查询变得更好。
  • NULL 不会被CAST 拒绝,并且大多数计算表达式的结果被SQL Server 视为NULL-able,因此NOT IN 计划将具有额外的低效率与NOT EXISTS 相比(影响计划的是列的NULL 能力而不是NULLs 的存在)
  • @MartinSmith 正确,可空性会影响计划。我只是指有空值时可能出现的错误结果。我也会补充的。
  • @MartinSmith 使用“拒绝”我的意思是空值不会通过CAST(ITEMID AS NUMERIC) >= 50000 条件,因此查询将显示与不存在版本相同的结果。我错了吗?
  • @ypercube - 尽管有WHERE,计算表达式的结果仍被视为NULL-able。它实际上可以返回NULL,就像这里的SET ANSI_WARNINGS, ARITHABORT OFF; WITH T(ITEMID) AS(SELECT 1E50) SELECT CAST(ITEMID AS NUMERIC) FROM T WHERE ISNUMERIC(ITEMID) = 1
【解决方案4】:

我同意 @ypercube 的观点,即明智的做法是修复您的架构。

如果由于某种原因这不是一个选项,则可能在运行时将整个事物具体化到索引临时表中,这将是一项糟糕的工作。

CREATE TABLE #T
(
ITEMID NUMERIC(18,0) PRIMARY KEY
                     WITH ( IGNORE_DUP_KEY = ON)
)    

INSERT INTO #T  
SELECT CASE WHEN ISNUMERIC(ITEMID) = 1 THEN ITEMID END
FROM Items
WHERE CASE WHEN ISNUMERIC(ITEMID) = 1 THEN ITEMID END >= 50000  


SELECT TOP 1 ITEMID+1
FROM #T T1
WHERE NOT EXISTS (SELECT * FROM #T T2 WHERE T2.ITEMID = T1.ITEMID +1)
ORDER BY ITEMID

【讨论】:

  • 谢谢,我没有测试过,但想在星期一做
  • @Aryan - 是的,如果没有对您的数据进行实际测试,很难知道哪个会执行得更好,如果自加入的 CTE 正在进行合并连接,我希望这会更好,因为他们必须这样做进行全面扫描并排序两次。如果嵌套循环可能取决于在找到TOP 1 之前需要扫描多少行。
猜你喜欢
  • 1970-01-01
  • 2011-08-06
  • 2015-02-23
  • 1970-01-01
  • 2014-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多