查询CTE

时间:2018-03-19 07:57:30

标签: sql-server

这完全是我对CTE缺乏完全了解 - 只是刚开始使用它们。我有以下数据:

create table tblHolidays
(
    HolidayId int identity(101, 1) Primary Key, HolidayDate datetime
)
go
insert into tblHolidays values
    ('2015-01-10'), ('2015-01-09'), ('2015-01-08'), ('2015-01-07'),
    ('2015-02-19'), ('2015-03-11'), ('2015-04-11')
go

和以下代码(尝试检索非假日的前一天):

with CTE1 as
(
select  *,
        dateadd(dd, -1, HolidayDate) as PrevDay,
        row_number() over(order by HolidayDate desc) as RN
from    tblHolidays
),
CTE2 as
(
select  *,
        case
            when PrevDay = lead(HolidayDate) over(order by (select null)) 
            then 0 else 1
            --when PrevDay = lead(HolidayDate) over(order by HolidayDate desc) then 0 else 1
        end as Foo
from    CTE1 as C
)
select top 1 PrevDay, HolidayDate from CTE2 where PrevDay < cast('2015-01-10' as datetime) and Foo = 1

执行时,我收到各种结果,我不知道为什么。我期望的输出是:

PrevDay     HolidayDate
2015-01-06  2015-01-07

AND,它是最初插入的上面的结果集。但是,如果我将另一条记录添加到tblHolidays并将2015-01-06设置为假日,从而想要检索2015-01-05的前一天,我没有得到它,我仍然得到上述结果。

但是,如果我将RND列替换为RN,我会得到正确的结果!我不明白这个。我知道还有其他方法可以做到这一点,但我真的想要理解为什么它的行为方式如此。我认为CTE的执行顺序基本上缺少一些东西。

2 个答案:

答案 0 :(得分:0)

让我建议另一种方法。

而不是设置一个假期日使用期限。接下来,udf内联函数模拟句点表:

-- holidays periods
create function dbo.getHolidays()
returns table
as
return
(
    with rst as
    (
        select HolidayDate,
               iif(lag(HolidayDate) over (order by HolidayDate) = dateadd(dd, -1, HolidayDate), null, 1) reset
        from   tblHolidays
    ), g as
    (
        select HolidayDate, 
               count(reset) over (order by HolidayDate) as grp
        from   rst
    )
    select min(HolidayDate) as StartDate, max(HolidayDate) as EndDate
    from   g
    group by grp
);

它返回:

select * from dbo.getHolidays();
GO

StartDate           | EndDate            
:------------------ | :------------------
06/01/2015 00:00:00 | 10/01/2015 00:00:00
19/02/2015 00:00:00 | 19/02/2015 00:00:00
11/03/2015 00:00:00 | 11/03/2015 00:00:00
11/04/2015 00:00:00 | 11/04/2015 00:00:00

现在,您可以通过这种方式轻松检查某个日期是否属于某个假期:

declare @dateSearch datetime;
set @dateSearch = '20150110'

select * 
from   dbo.getHolidays()
where  @dateSearch >= startDate 
and    @dateSearch <= endDate;
GO

StartDate           | EndDate            
:------------------ | :------------------
06/01/2015 00:00:00 | 10/01/2015 00:00:00

dbfiddle here

答案 1 :(得分:0)

错误出现在这段代码中:lead(HolidayDate) over(order by (select null))。 使用窗口函数时,如果在任何子句中指定select null,查询可能会随机运行 - 每次运行时会产生不同的结果 - 这就是您现在面临的问题。

order by (select null) - 顺序是表的物理顺序,每次运行此类查询时都可以获得不同的结果。

partition by (select null) - 没有分区。它比制定order by (select null)条款(明确 - 它根本没有危险)更危险,因为这种查询的结果不会是随机的。

您必须指定正确的order by子句,如下所示: lead(HolidayDate) over(order by HolidayDate)。这将根据您的需要提供输出。