我有以下代码,使用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
)
提前致谢,对不起我的英文
答案 0 :(得分:1)
这个查询使用两个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),后面指定下一个值,即int大于前一个值。评估递归CTE有somewhat special semantics所以阅读它们是个好主意。最后,WHERE
指定停止条件,以便递归不会永远持续。
所有这一切都是生成一个等同于物理“数字”表,只有一列从1开始的数字。
最后的SELECT
使用numbers
CTE的结果生成一堆日期。
请注意,最后的OPTION (MAXRECURSION 0)
也与递归CTE相关。这将禁用服务器范围的递归深度限制,以便在范围很长或者令人烦恼的DBA设置非常低的默认限制时,生成数字的查询不会停止。
答案 1 :(得分:1)
totalMonths
查询计算标量结果(单个值),指示需要生成的月数。仅仅使用内联而不是使用命名的CTE可能更有意义。
numbers
生成一系列行,其中一列名为num
,从1
开始,结束于totalM + 1
,这是在上一步中计算出来的。它可以通过交叉连接来引用该值。由于只有一行,它基本上只是将一列水平附加到表中。该查询是递归的,因此每次传递都会在结果中添加1到最后添加的行(实际上只是一列),直到以前添加的行的值超过totalM
。 union
的前半部分是起始值;下半部分通过from numbers
引用本身,并以一种循环递增地构建结果。
输出来自numbers
输入。从每个num
中减去一个,得出从0
到totalM
的范围,该值被视为要添加到开始日期的月数。日期值将转换为长度为6的varchar
,这意味着包含该日期的最后两个字符将被截断。
假设@date_start
是2016年1月31日,而@date_end
是2016年3月1日。实际日期值从未进行任何比较,因此3月31日生成日期值并不重要序列,但也落后于传递的@date_end
值。可以选择相应开始和结束月份中的任何日期以生成相同的序列。
答案 2 :(得分:0)
SELECT 1 num
是你的递归CTE的起点,也就是第一次teration中的(数字n)。在第二次迭代中,第一次的输出
SELECT n.num+1 num FROM numbers n, totalMonths c
WHERE n.num <= c.totalM
变成数字(n),依此类推。