Sql 在月的最后一天填写缺失数据的行

Sql 在月的最后一天填写缺失数据的行,sql,sql-server,Sql,Sql Server,我有一张看起来像的桌子 UserID LastDayofMonth Count 1234 2015-09-30 00:00:00 12 1237 2015-09-30 00:00:00 5 3233 2015-09-30 00:00:00 3 8336 2015-09-30 00:00:00 22 1234

我有一张看起来像的桌子

UserID        LastDayofMonth              Count
1234          2015-09-30 00:00:00         12
1237          2015-09-30 00:00:00         5
3233          2015-09-30 00:00:00         3
8336          2015-09-30 00:00:00         22
1234          2015-10-31 00:00:00         8
1237          2015-10-31 00:00:00         5
3233          2015-10-31 00:00:00         7
8336          2015-11-30 00:00:00         52
1234          2015-11-30 00:00:00         8
1237          2015-11-30 00:00:00         5
3233          2015-11-30 00:00:00         7
大约有10000行。正如您在示例中所看到的,UserID 8336没有10月31日的记录,日期是每月的,但总是每月的最后一天,我想保留这一天。如何返回一个包含记录的表,该表将填充四个月的记录,以便像8336这样的用户获得如下记录

8336          2015-10-31 00:00:00         0

我有一个日历表,上面有我可以使用的所有日期。

如果我理解正确,您需要每个用户和每个月末的记录。并且,如果记录当前不存在,则需要0的值

这是一个分两步的过程。首先使用交叉连接生成所有行。然后使用left join获取值

因此:


此解决方案使用两个CTE,不知道您的日历表布局。与Gordon Linoff相比,此解决方案的唯一优势在于,它不假设每个月至少有一个用户。我为您的示例提供了测试数据,并提供了7月份的额外记录,完全跳过了8月份

/************** TEST DATA ******************/
IF OBJECT_ID('MonthlyUserCount','U') IS NULL
BEGIN
    CREATE TABLE MonthlyUserCount
    (
          UserID INT
        , LastDayofMonth DATETIME
        , [Count] INT
    )

    INSERT MonthlyUserCount
    VALUES (1234,'2015-07-31 00:00:00',12),--extra record
           (1234,'2015-09-30 00:00:00',12),
           (1237,'2015-09-30 00:00:00',5),
           (3233,'2015-09-30 00:00:00',3),
           (8336,'2015-09-30 00:00:00',22),
           (1234,'2015-10-31 00:00:00',8),
           (1237,'2015-10-31 00:00:00',5),
           (3233,'2015-10-31 00:00:00',7),
           (8336,'2015-11-30 00:00:00',52),
           (1234,'2015-11-30 00:00:00',8),
           (1237,'2015-11-30 00:00:00',5),
           (3233,'2015-11-30 00:00:00',7)
END
/************ END TEST DATA ***************/

DECLARE @Start DATETIME;
DECLARE @End DATETIME;

--establish a date range
SELECT @Start = MIN(LastDayofMonth) FROM MonthlyUserCount;
SELECT @End   = MAX(LastDayofMonth) FROM MonthlyUserCount;

--create a custom calendar of days using the date range above and identify the last day of the month
--if your calendar table does this already, modify the next cte to mimic this functionality
WITH cteAllDays AS
(
    SELECT @Start AS [Date], CASE WHEN DATEPART(mm, @Start) <> DATEPART(mm, @Start+1) THEN 1 ELSE 0 END [Last]
    UNION ALL
    SELECT [Date]+1, CASE WHEN DATEPART(mm,[Date]+1) <> DatePart(mm, [Date]+2) THEN 1 ELSE 0 END 
    FROM cteAllDays
    WHERE [Date]< @End
),
--cte using calendar of days to associate every user with every end of month
cteUserAllDays AS
(
    SELECT DISTINCT m.UserID, c.[Date] LastDayofMonth
    FROM MonthlyUserCount m, cteAllDays c
    WHERE [Last]=1      
)
--left join the cte to evaluate the NULL and present a 0 count for that month
SELECT c.UserID, c.LastDayofMonth, ISNULL(m.[Count],0) [Count]
FROM cteUserAllDays c 
    LEFT JOIN MonthlyUserCount m ON m.UserID = c.UserID
        AND m.LastDayofMonth =c.LastDayofMonth
ORDER BY c.LastDayofMonth, c.UserID
OPTION ( MAXRECURSION 0 )

哇,这真是太干净了,比我试图做的日历表的糟糕连接更有意义。谢谢。交叉连接的用法很好;我打算用CTE做同样的事情。这假设每个月至少有一个用户。
/************** TEST DATA ******************/
IF OBJECT_ID('MonthlyUserCount','U') IS NULL
BEGIN
    CREATE TABLE MonthlyUserCount
    (
          UserID INT
        , LastDayofMonth DATETIME
        , [Count] INT
    )

    INSERT MonthlyUserCount
    VALUES (1234,'2015-07-31 00:00:00',12),--extra record
           (1234,'2015-09-30 00:00:00',12),
           (1237,'2015-09-30 00:00:00',5),
           (3233,'2015-09-30 00:00:00',3),
           (8336,'2015-09-30 00:00:00',22),
           (1234,'2015-10-31 00:00:00',8),
           (1237,'2015-10-31 00:00:00',5),
           (3233,'2015-10-31 00:00:00',7),
           (8336,'2015-11-30 00:00:00',52),
           (1234,'2015-11-30 00:00:00',8),
           (1237,'2015-11-30 00:00:00',5),
           (3233,'2015-11-30 00:00:00',7)
END
/************ END TEST DATA ***************/

DECLARE @Start DATETIME;
DECLARE @End DATETIME;

--establish a date range
SELECT @Start = MIN(LastDayofMonth) FROM MonthlyUserCount;
SELECT @End   = MAX(LastDayofMonth) FROM MonthlyUserCount;

--create a custom calendar of days using the date range above and identify the last day of the month
--if your calendar table does this already, modify the next cte to mimic this functionality
WITH cteAllDays AS
(
    SELECT @Start AS [Date], CASE WHEN DATEPART(mm, @Start) <> DATEPART(mm, @Start+1) THEN 1 ELSE 0 END [Last]
    UNION ALL
    SELECT [Date]+1, CASE WHEN DATEPART(mm,[Date]+1) <> DatePart(mm, [Date]+2) THEN 1 ELSE 0 END 
    FROM cteAllDays
    WHERE [Date]< @End
),
--cte using calendar of days to associate every user with every end of month
cteUserAllDays AS
(
    SELECT DISTINCT m.UserID, c.[Date] LastDayofMonth
    FROM MonthlyUserCount m, cteAllDays c
    WHERE [Last]=1      
)
--left join the cte to evaluate the NULL and present a 0 count for that month
SELECT c.UserID, c.LastDayofMonth, ISNULL(m.[Count],0) [Count]
FROM cteUserAllDays c 
    LEFT JOIN MonthlyUserCount m ON m.UserID = c.UserID
        AND m.LastDayofMonth =c.LastDayofMonth
ORDER BY c.LastDayofMonth, c.UserID
OPTION ( MAXRECURSION 0 )