SQL返回缺少的行

时间:2011-02-23 00:07:24

标签: sql sql-server gaps-and-islands

我有以下架构,我稍微简化了一下:

CREATE TABLE [dbo].[Header] (
    [HeaderId] [int] IDENTITY(1,1) NOT NULL,
    [StaffId] [int] NOT NULL,
    [WeekEnding] [smalldatetime] NOT NULL,
    ...
)

CREATE TABLE [dbo].[Staff] (
    [StaffId] [int] NOT NULL,
    [FirstWeekEnding] [smalldatetime] NULL,
    ...
)

Header表上的StaffId是外键。

Header表跟踪与员工相关的数据(在子表中未显示),如果子表中的数据存在,则对于给定的周,将有“周末”的条目。在这种情况下,“周末”始终是星期日。因此,样本数据可能如下所示:

HeaderId    StaffId    WeekEnding
---------------------------------
1           1          13/02/2011
2           1          20/02/2011
etc...

员工表上的“FirstWeekEnding”值是他们开始在Header表中跟踪信息的第一个日期。

我的问题

鉴于每个工作人员的第一周结束日期,如何构建一个查询,该查询将从Header表中提供截至当前日期的所有MISSING记录?

例如,给出以下数据:

StaffId     FirstWeekEnding
---------------------------
1           02/01/2011

HeaderId    StaffId    WeekEnding
---------------------------------
1           1          02/01/2011
2           1          09/01/2011
3           1          16/01/2011
4           1          13/02/2011
5           1          20/02/2011

结果将是:

StaffId    WeekEnding
---------------------
1          23/01/2011
1          30/01/2011
1          06/02/2011

理想情况下,查询应该处理多个工作人员,按其StaffId分组。

3 个答案:

答案 0 :(得分:3)

您需要一种方法来生成日期系列。请参阅示例http://syntaxhelp.com/SQLServer/Recursive_CTE。然后搜索日期系列中没有匹配记录的条目。

DECLARE @startDate DATETIME, @endDate DATETIME
SELECT @startDate = '2011-01-02', @endDate = GETDATE()

;WITH DateSeries AS (
    SELECT @startDate AS dt
    UNION ALL
    SELECT dt + 7 FROM DateSeries      -- using 7 for weekly interval
    WHERE dt + 7 <= @endDate
)
SELECT
    *
FROM
    DateSeries ds
LEFT JOIN
    (your data here) t
ON
    ds.dt = t.WeekEnding
WHERE
    t.WeekEnding IS NULL

答案 1 :(得分:2)

假设您正在使用SQL 2005+,您可以为每个员工生成所有周,并尝试加入CTE中的Header表(类似,但可能不是这样):

;WITH cte AS 
( SELECT StaffId, FirstWeekEnding AS WeekEnding
  FROM STAFF
  UNION ALL
  SELECT StaffId, DATEADD(D, 7, WeekEnding) FROM cte
  WHERE DATEADD(D, 7, WeekEnding) <= GETDATE()
)
SELECT StaffId, WeekEnding
FROM cte LEFT JOIN Header ON cte.StaffId = Header.StaffId AND cte.WeekEnding = Header.WeekEnding
WHERE Header.WeekEnding IS NULL
OPTION (MAXRECURSION 32767)

答案 2 :(得分:1)

好的,所以我建议创建一个Calendar表并加入。

这是最终查询:

select StaffID, BaseDate
From (
select 
 StaffID, 
 BaseDate, 
 (
  Select count(*) 
  from Header h 
  where h.WeekEnding = c.BaseDate And h2.StaffID = h.StaffID
 ) as Count
From Header h, Calendar c ) as Subquery
Where Count = 0

日历:

create table Calendar (
BaseDate datetime primary key,
DayOfWeek varchar(10) not null,
WeekOfYear int not null,
MonthOfYear varchar(10) not null,
Quarter int not null
/* Add any other useful columns */
)
go

declare @d datetime
set @d = '20090101'
while @d < '20250101'
begin
insert into dbo.Calendar values (
@d,
datename(dw, @d),
datepart(ww, @d),
datename(mm, @d),
datepart(qq, @d))
set @d = dateadd(dd, 1, @d)
end
go

select *
from dbo.Calendar
where DayOfWeek = 'Sunday' and
BaseDate between '20090101' and '20250101'
go