需要帮助来理解 SQL 查询

Need help to understand SQL query

我有以下代码使用 CTE 获取两个日期范围之间的月份

declare
    @date_start DateTime,
    @date_end DateTime

;WITH totalMonths AS 
(
    SELECT 
        DATEDIFF(MONTH, @date_start, @date_end) totalM
),
numbers AS 
(
    SELECT 1 num

    UNION ALL

    SELECT n.num + 1 num 
    FROM numbers n, totalMonths c
    WHERE n.num <= c.totalM
)
SELECT 
    CONVERT(varchar(6), DATEADD(MONTH, numbers.num - 1, @date_start), 112)  
FROM 
    numbers 
OPTION (MAXRECURSION 0);

这有效,但我不明白它是如何工作的

特别是这部分

numbers AS 
(
    SELECT 1 num

    UNION ALL

    SELECT n.num + 1 num 
    FROM numbers n, totalMonths c
    WHERE n.num <= c.totalM
)

提前致谢,对不起我的英语

SELECT 1 num

是递归 CTE 的起点,即第一个 teration.In 中的 (numbers n) 第二个迭代是第一个

的输出
SELECT n.num+1 num FROM numbers n, totalMonths c
WHERE n.num <= c.totalM

变成数字(n)等等

此查询使用两个 CTE,一个是递归的,从无到有生成值列表(SQL 不太擅长这样做)。

totalMonths AS (SELECT DATEDIFF(MONTH, @date_start, @date_end) totalM),

这部分基本上是一种将 DATEDIFF 的结果绑定到名称 totalM 的复杂方法。如果您可以声明一些东西,这可以作为一个变量实现:

DECLARE @totalM int = DATEDIFF(MONTH, @date_start, @date_end);

那么你当然会用@totalM来引用这个值。

numbers AS (
    SELECT 1 num
    UNION ALL
    SELECT n.num+1 num FROM numbers n, totalMonths c
    WHERE n.num<= c.totalM
)

这部分本质上是一个简单的循环,使用递归来生成从 1 到 totalMonths 的数字。第一个 SELECT 指定第一个值 (1),之后的一个指定下一个值,该值比前一个值大。评估递归 CTE 有 somewhat special semantics,因此阅读它们是个好主意。最后 WHERE 指定停止条件,这样递归就不会永远继续下去。

所有这一切所做的是生成一个等同于物理 "numbers" table 的等价物,其中只有一列是从 1 开始的数字。

最后的 SELECT 使用 numbers CTE 的结果生成一堆日期。

注意最后的OPTION (MAXRECURSION 0)也和递归CTE有关。这将禁用服务器范围的递归深度限制,以便生成查询的数字不会在范围很长时突然停止,或者麻烦的 DBA 设置了非常低的默认限制。

totalMonths 查询的计算结果为标量结果(单值),指示需要生成的月数。内联执行此操作而不是使用命名的 CTE 可能更有意义。

numbers 生成一个行序列,其中有一列名为 num,从 1 开始,到 totalM + 1 结束,这是在上一步中计算的。它能够通过交叉连接引用此值。由于只有一行,它基本上只是将该列水平附加到 table 。该查询是递归的,因此每次通过将 1 添加到最后添加的行(实际上只是一列),直到 先前添加的行 的值超过 totalMunion的前半部分是起始值;后半部分通过 from numbers 引用 自身 并在某种循环中逐步构建结果。

输出来自 numbers 输入。从每个 num 中减去一个,给出从 0totalM 的范围,并且该值被视为要添加到开始日期的月数。日期值被转换为长度为 6 的 varchar,这意味着包含日期的最后两个字符被截断。

假设 @date_start 是 2016 年 1 月 31 日,@date_end 是 2016 年 3 月 1 日。实际日期值从来没有任何比较,因此生成 3 月 31 日并不重要在序列中,但也晚于传递的 @date_end 值。可以选择相应开始和结束月份中的任何日期来生成相同的序列。