我知道通常将当前日期声明为变量很有用:
DECLARE @CurrentDate as DateTime
SET @CurrentDate = Getdate()
我的理解是,这一次对当前日期/时间进行了采样,并将其记录为静态变量。这对于确保查询期间的“当前时间”是不变的,并且避免重复使用Getdate()而不是获取变量的值而言更为昂贵。
我的问题是...。在似乎只一次获取日期的递归WITH子句的情况下,这是必需的吗?
例如,考虑以下代码:
WITH CalendarSequence as(
SELECT Getdate() as RollingDate
UNION ALL
SELECT DateAdd(month, -1, RollingDate) as RollingDate
FROM CalendarSequence
WHERE DateAdd(month, -1, RollingDate) > Convert(date, '2016-01-01')
)
SELECT
Year(CalendarSequence.RollingDate)*100+Month(CalendarSequence.RollingDate) as MissingYearMonth
FROM CalendarSequence
LEFT OUTER JOIN TableName
ON Year(CalendarSequence.RollingDate)*100+Month(CalendarSequence.RollingDate) = TableName.YearMonthField
WHERE TableName.YearMonthField is NULL
此查询生成一个临时的日期表,并将其与任意表进行比较以突出显示没有活动/数据的单个月份。
对于这段特定的代码,多次使用Getdate()并没有太大关系(除非查询执行过程中月份发生变化!),但是对于类似的查询而言,并非普遍如此。我提出这个问题的部分动机是为了更好地了解WITH函数的作用以及它是否具有令人惊讶的行为。
答案 0 :(得分:3)
执行摘要::如果您想为日期/时间使用单个值,请将其捕获到变量中并根据需要使用。它使您的意图清晰明了,避免了软件更新和其他细微之处可能引起的问题。
下面的代码经过SQL Server 2008 R2的测试,证明并非所有select
语句都相等。尽管每个实例(可能还有所有实例)的GetDate()
似乎都是runtime constant,但问题比人们想象的要微妙得多。
GetDate()
在所有列和行中可能都是恒定的。
-- Constant across all columns and rows.
with Murphy as (
select GetDate() as A, GetDate() as B, 1 as Rows
union all
select GetDate(), GetDate(), Rows + 1
from Murphy
where A = B and Rows < 1000000 )
select Min( A ) as MinA, Max( A ) as MaxA, Min( B ) as MinB, Max( B ) as MaxB
from Murphy
option ( MaxRecursion 0 );
更高效的版本使用cross join
代替递归。
-- Constant across all columns and rows.
declare @Limit as Int = 100000;
with Ten ( Number ) as
( select * from ( values (0), (1), (2), (3), (4), (5), (6), (7), (8), (9) ) as Digits( Number ) ),
TenUp2 ( Number ) as ( select 42 from Ten as L cross join Ten as R ),
TenUp4 ( Number ) as ( select 42 from TenUp2 as L cross join TenUp2 as R ),
TenUp8 ( Number ) as ( select 42 from TenUp4 as L cross join TenUp4 as R ),
Numbers ( Number, A, B ) as ( select top (@Limit) Row_Number() over ( order by ( select NULL ) ),
GetDate(), GetDate() from TenUp8 )
select Min( A ) as MinA, Max( A ) as MaxA, Min( B ) as MinB, Max( B ) as MaxB
from Numbers;
然后,也许不同的实例可能会分开。
-- Fails (and does not generate an execution plan).
declare @A as DateTime = GetDate();
declare @B as DateTime = @A;
declare @Trials as Int = 0;
while @A = @B
begin
select @A = GetDate(), @B = GetDate(), @Trials += 1;
if @Trials % 1000 = 0
print @Trials;
end
select @A as A, @B as B, @Trials as Trials;
因此,您认为只是set
冒充为select
,而真实 select
却会产生执行计划,因此工作方式会有所不同。
-- Fails.
declare @A as DateTime = GetDate();
declare @B as DateTime = @A;
declare @Trials as Int = 0;
while @A = @B
begin
select @A = GetDate(), @B = GetDate(), @Trials += 1
from ( values ( 42 ) ) as PH( A );
if @Trials % 1000 = 0
print @Trials;
end
select @A as A, @B as B, @Trials as Trials;
如果值来自表值构造函数怎么办?
-- Fails.
declare @A as DateTime = GetDate();
declare @B as DateTime = @A;
declare @Trials as Int = 0;
select @A = GetDate(), @B = @A;
while @A = @B
begin
select @A = A, @B = B, @Trials += 1
from ( values ( GetDate(), GetDate() ) ) as PH( A, B )
if @Trials % 1000 = 0
print @Trials;
end
select @A as A, @B as B, @Trials as Trials;
好吧,所有失败均来自为变量赋值的select
语句。让我们通过在表中插入行并稍后进行检查来消除这种情况。 (注意:此示例在带有SQL Server 2017的SQL Fiddle上运行时不会失败。)
-- Fails on SQL Server 2008 R2, but not on SQL Server 2017.
declare @Samples as Table ( A DateTime, B DateTime );
declare @Trials as Int = 0;
while @Trials < 100000
begin
insert into @Samples ( A, B ) values ( GetDate(), GetDate() )
set @Trials += 1;
end
select A, B
from @Samples
where A != B;
select Min( A ) as MinA, Max( A ) as MaxA, Min( B ) as MinB, Max( B ) as MaxB
from @Samples;
答案 1 :(得分:2)
我在SQL Server 2016中运行了两种方法。我没有发现任何区别。但是,正如@Cato在评论中提到的那样,最好将其声明为变量并传递该变量,而不是等待优化器对其进行处理。
DECLARE @currentDate DATE = GETDATE()
;WITH CalendarSequence as(
SELECT @currentDate as RollingDate
UNION ALL
SELECT DateAdd(month, -1, RollingDate) as RollingDate
FROM CalendarSequence
WHERE DateAdd(month, -1, RollingDate) > Convert(date, '2018-01-01')
)
SELECT * FROM CalendarSequence
;WITH CalendarSequence as(
SELECT Getdate() as RollingDate
UNION ALL
SELECT DateAdd(month, -1, RollingDate) as RollingDate
FROM CalendarSequence
WHERE DateAdd(month, -1, RollingDate) > Convert(date, '2018-01-01')
)
SELECT * FROM CalendarSequence
他们两个都有相同的执行计划。完全没有变化。