当日期可能丢失时,以“周......”为一组获取数据

时间:2017-07-17 19:09:24

标签: sql-server tsql

我在包含日期的表中有数据,并希望按“周”计算行数(例如,“2017-05-01周”),其中结果包含周的日期(从星期一开始)和匹配行的计数 - 即使该周没有行。 (这都将在日期范围内。)

我可以通过分组DATEPART(wk, D)(其中D是日期列)来将事情分成几周,但我正在努力:

  1. 如何获取“周”日期并填写,

  2. 如何在数据中没有匹配行的一周内有一行

  3. 这是按周分组:

    SET DATEFORMAT ymd;
    SET DATEFIRST 1; -- Monday is first day of week
    
    DECLARE @startDate DATETIME = '2017-05-01';
    DECLARE @endDate DATETIME = '2017-07-01';
    
    SELECT      DATEPART(wk, D) AS [Week Number], COUNT(*) AS [Count]
    FROM        #temp
    GROUP BY    DATEPART(wk, D)
    ORDER BY    DATEPART(wk, D);
    

    这给了我:

    +−−−−−−−−−−−−−+−−−−−−−+
    | Week Number | Count |
    +−−−−−−−−−−−−−+−−−−−−−+
    | 19          |     5 |
    | 20          |    19 |
    | 22          |     8 |
    | 23          |    10 |
    | 24          |     5 |
    | 26          |     4 |
    +−−−−−−−−−−−−−+−−−−−−−+
    

    但理想情况下我想:

    +−−−−−−−−−−−−+−−−−−−−+
    | Week       | Count |
    +−−−−−−−−−−−−+−−−−−−−+
    | 2017-05-01 |     5 |
    | 2017-05-08 |    19 |
    | 2017-05-15 |     0 |
    | 2017-05-22 |     8 |
    | 2017-05-29 |    10 |
    | 2017-06-05 |     5 |
    | 2017-06-12 |     0 |
    | 2017-06-19 |     4 |
    | 2017-06-26 |     0 |
    +−−−−−−−−−−−−+−−−−−−−+
    

    我该怎么做?

    设置测试信息:

    SET DATEFIRST 1;
    SET DATEFORMAT ymd;
    
    CREATE TABLE #temp (
        D DATETIME
    );
    GO
    
    INSERT INTO #temp (D)
    VALUES      -- Week of 2017-05-01 (#19)
                ('2017-05-01'),('2017-05-01'),('2017-05-01'),
                ('2017-05-06'),('2017-05-06'),
                -- Week of 2017-05-08 (#20) - note no data actually on the 8th
                ('2017-05-10'),
                ('2017-05-11'),('2017-05-11'),('2017-05-11'),('2017-05-11'),('2017-05-11'),('2017-05-11'),
                ('2017-05-12'),('2017-05-12'),('2017-05-12'),('2017-05-12'),
                ('2017-05-13'),('2017-05-13'),('2017-05-13'),('2017-05-13'),('2017-05-13'),('2017-05-13'),('2017-05-13'),
                ('2017-05-14'),
                -- Week of 2017-05-15 (#21)
                -- (note we have no data for this week)
                -- Week of 2017-05-22 (#22)
                ('2017-05-22'),('2017-05-22'),('2017-05-22'),
                ('2017-05-23'),('2017-05-23'),('2017-05-23'),('2017-05-23'),('2017-05-23'),
                -- Week of 2017-05-29 (#23)
                ('2017-05-29'),('2017-05-29'),('2017-05-29'),
                ('2017-06-02'),('2017-06-02'),
                ('2017-06-03'),
                ('2017-06-04'),('2017-06-04'),('2017-06-04'),('2017-06-04'),
                -- Week of 2017-06-05 (#24) - note no data actually on the 5th
                ('2017-06-08'),('2017-06-08'),('2017-06-08'),
                ('2017-06-11'),('2017-06-11'),
                -- Week of 2017-06-12 (#25)
                -- (note we have no data for this week)
                -- Week of 2017-06-19 (#26)
                ('2017-06-19'),('2017-06-19'),('2017-06-19'),
                ('2017-06-20');
    GO
    

3 个答案:

答案 0 :(得分:3)

要执行此操作,您必须生成一个包含星期一日期及其周数的表格或CTE(如this answer所示,稍为我们需要在下面进行修改),然后LEFT JOINOUTER APPLY您的数据按周分组,使用周数:

SET DATEFORMAT ymd;
SET DATEFIRST 1;

DECLARE @startDate DATETIME = '2017-05-01';
DECLARE @endDate DATETIME = '2017-07-01';

;WITH Mondays AS (
    SELECT  @startDate AS D, DATEPART(WK, @startDate) AS W
    UNION ALL
    SELECT  DATEADD(DAY, 7, D), DATEPART(WK, DATEADD(DAY, 7, D))
    FROM    Mondays m
    WHERE   DATEADD(DAY, 7, D) < @endDate
)
SELECT      LEFT(CONVERT(NVARCHAR(MAX), Mondays.D, 120), 10) AS [Week Of], d.Count
FROM        Mondays
OUTER APPLY (
            SELECT  COUNT(*) AS [Count]
            FROM    #temp
            WHERE   DATEPART(WK, D) = W
            AND     D >= @startDate
            AND     D < @endDate
) d
ORDER BY    Mondays.D;

关于此的两点说明:

  1. 我假设我们可以确保@startDate是星期一,这可以在查询之外轻松完成,或者如果需要可以在T-SQL中使用简单循环完成(备份直到{{ 1}}是WEEKPART(WEEKDAY, @startDate))。 (或者最糟糕的情况是,我们可以生成所有日期,然后使用1过滤它们。)

  2. 我假设日期范围总是一年或更短;否则,我们会有重复的周数。如果日期范围可能超过一年,请将周数与我们仅使用上周数字的年份(例如WEEKPART(WEEKDAY, ...))合并。

答案 1 :(得分:1)

你可以使用它。

SET DATEFORMAT ymd;
SET DATEFIRST 1; -- Monday is first day of week

DECLARE @startDate DATETIME = '2017-05-01';
DECLARE @endDate DATETIME = '2017-07-01';

;WITH OrgResult AS ( -- Grouping result with missing week. Answer of the first question
    SELECT 
        DATEADD(DAY, 1 - DATEPART (WEEKDAY, D), D) [Week] -- Fist Day Of the Week
        , COUNT(*) [Count]
    FROM #temp
        WHERE D BETWEEN @startDate AND @endDate
    GROUP BY 
        DATEADD(DAY, 1 - DATEPART (WEEKDAY, D), D)
)
, Result AS -- Adds only missing weeks. Answer of the second question
(
    SELECT * FROM OrgResult
    UNION ALL
    SELECT DATEADD( DAY, 7, R.[Week] ), 0 [Count] 
    FROM Result R 
    WHERE NOT EXISTS( SELECT * FROM OrgResult O WHERE [Week] = DATEADD( DAY, 7, R.[Week] ) )
            AND DATEADD( DAY, 7, R.[Week] ) <= @endDate
)
SELECT * FROM Result
ORDER BY [Week]

结果:

Week        Count
----------- -----------
2017-05-01  5
2017-05-08  19
2017-05-15  0
2017-05-22  8
2017-05-29  10
2017-06-05  5
2017-06-12  0
2017-06-19  4
2017-06-26  0

答案 2 :(得分:0)

这是另一种方法。我把它包括在内,因为它会产生比递归CTE解决方案更少的读取,并且会很快

WITH E(N) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))x(x)),
iTally(N) AS 
(
  SELECT TOP (((DATEDIFF(day,@startdate, @endDate))/7)+1)
    (ROW_NUMBER() OVER (ORDER BY (SELECT 1))-1)
  FROM E a, E b, E c
)
SELECT WeekOf = DATEADD(WEEK,N,@startDate), [count] = COUNT(t.D)
FROM iTally i
LEFT JOIN #temp t ON t.D >= DATEADD(WEEK,N,@startDate) AND t.D < DATEADD(WEEK,N+1,@startDate)
GROUP BY DATEADD(WEEK,N,@startDate)
ORDER BY DATEADD(WEEK,N,@startDate); -- not required

结果:

WeekOf     count
---------- -----------
2017-05-01 5
2017-05-08 19
2017-05-15 0
2017-05-22 8
2017-05-29 10
2017-06-05 5
2017-06-12 0
2017-06-19 4
2017-06-26 0