按月的最后一天填充缺失数据的行

时间:2015-12-16 21:33:45

标签: 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          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

(约10,000行)。正如您在示例中所看到的,UserID 8336没有10月31日的记录(日期是每月,但始终是该月的最后一天,我想保留)。如何返回一个表格,其中包含四个月内填写记录的记录,以便像8336这样的用户获得

等记录
8336          2015-10-31 00:00:00         0

我有一个日历表,其中包含我可以使用的所有日期。

2 个答案:

答案 0 :(得分:2)

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

这是两个步骤。首先使用cross join生成所有行。然后使用left join获取值。

所以:

select u.userId, l.LastDayofMonth, coalesce(t.cnt, 0) as cnt
from (select distinct userId from t) u cross join
     (select distinct LastDayofMonth from t) l left join
     t
     on t.userId = u.userId and t.LastDayofMonth = l.LastDayofMonth;

答案 1 :(得分:0)

此解决方案使用了几个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 )