【问题标题】:SQL CTE for a numbers table is fast for static value, but slow for table value数字表的 SQL CTE 对静态值来说很快,但对表值来说很慢
【发布时间】:2020-10-21 21:04:27
【问题描述】:

我正在尝试根据该表中的列多次输出该表的值。

我尝试使用 CTE 即时制作数字表:

WITH cte AS
(
    SELECT
        ROW_NUMBER() OVER (ORDER BY (select 0)) AS i
    FROM
        sys.columns c1 CROSS JOIN sys.columns c2 CROSS JOIN sys.columns c3
)
select *
from myTable, cte
WHERE i <= myTable.timesToRepeatColumn
  and myTable.id = '209386'

这个 SQL 似乎需要永远运行,所以它似乎试图在加入之前运行整个 CTE。

如果我将myTable.timesToRepeatColumn 替换为静态值(例如 10000),则查询几乎会立即返回。因此,在完全交叉加入 CTE 表之前,它似乎正在执行 where i

我如何告诉 SQL 像使用静态数字一样先执行 where 语句?

【问题讨论】:

  • BTW myTable.timesToRepeatColumn 我的测试行的值为 3,因此它应该几乎可以立即运行。

标签: sql sql-server query-optimization common-table-expression


【解决方案1】:

您可以使用递归 cte 来实现您的目标

WITH cte AS (
SELECT
  *
, timesToRepeatColumn as level
FROM
    myTablewhere 
WHERE myTable.id = '209386'
UNION ALL
SELECT
  *
, level -1  as level
FROM
    cte 
WHERE 
    level > 0
)


SELECT * FROM cte

【讨论】:

  • 我必须在此处指定不使用的列 * 否则我会收到错误 All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists. 这还包括由于某种原因级别为 0 的行,但我可以将最后的 where 子句更改为级别 > 1 解决这个问题
  • @BrianHeward 你明白了;)
  • 请注意,递归 CTE 也会对性能造成影响,因为它们是循环,例如,请参阅 stackoverflow.com/questions/64472923/… 中的 cmets。如果您可以通过集合逻辑来做到这一点,它通常会提供更好的性能。当然,在迭代次数很少的情况下(我认为这里就是这种情况),性能影响可以忽略不计(例如,15 毫秒和 10 毫秒之间的差异......除非你'非常频繁地重新运行它)
【解决方案2】:

SQL Server 中的 CTE 不一定“独立”运行。 SQL(在 SQL Server 等中)是声明性的,这意味着你告诉它你想要什么,而不是如何去做。

如果查询优化器确定它可以通过做一些不同的事情来做得更好,它会。

一个很好的例子是

IF EXISTS(SELECT * FROM test) PRINT 'X';
IF (SELECT COUNT(*) FROM test) > 0 PRINT 'Y';
IF (SELECT COUNT(*) FROM test) > 1 PRINT 'Z';

如果它按照你说的去做,那么第二个和第三个的查询计划基本上是一样的。但是,当您运行它时,第一个和第二个的查询计划是相同的;第三个不同。

当您硬编码该值(例如 10,000)时,查询优化器可以使用该硬编码值来确定要执行的操作。在这种情况下,它可能确定它不需要运行完整的 CTE,只需运行它直到获得 10,000 行。

但是,如果您使用一个可以变化的值(例如,myTable.timesToRepeatColumn),那么查询优化器通常会制定一个查询计划来表示 any 值。因此,它制定了一个不适合您的情况的查询计划 - 可能在使用之前在内存中创建完整的 CTE。如果 sys.columns 有 100 行,那就是它创建的 100^3 行。如果是 1000,则为 1000^3,例如 1,000,000,000。您的行数可能超过 1000 行。

【讨论】:

  • 是的,这对于为什么它做错事是有道理的......但是我怎样才能让它使用更好的查询计划而不需要运行完整的 cte?跨度>
  • 您可以使用更快的方法来创建数字表 - 例如,请参阅 stackoverflow.com/questions/1393951/… 或我通常使用的 stackoverflow.com/questions/64383302/… (速度快但可能不如其他一些方法快) .如果它在存储过程/脚本(不是视图)中,您也可以尝试首先使用 PK 创建一个临时数字表,例如CREATE TABLE #nums (id int PRIMARY KEY),然后填充/使用它。
  • 或者,在您的 CTE 中 - 您可以尝试首先将行数限制为最大值(例如,如果您不需要超过 1000 行,请将 SELECT TOP 1000 放在前面。也可以尝试生成没有排序的数字,例如,通过使用自动递增字段 - 它有机会提供帮助。
  • 错误,我在这里使用的系统实际上与链接帖子中的方法 6 相同,方法 7 并没有比 6 大幅提高速度。问题不在于数字表的速度有多快生成。问题是知道何时停止生成数字。当我指定硬编码的最大值时,我的代码不到一秒钟。问题是我不知道我什么时候写代码那个最大值应该是多少。
  • @Brian - 尝试将 TOP 部分放入例如 SELECT TOP 10000 ROW_NUMBER() ...
猜你喜欢
  • 1970-01-01
  • 2020-08-12
  • 2016-02-11
  • 2015-07-12
  • 2012-03-24
  • 1970-01-01
  • 2021-01-07
  • 1970-01-01
  • 2011-12-16
相关资源
最近更新 更多