【问题标题】:SQL Server: How to select all days in a date range even if no data exists for some daysSQL Server:即使某些天不存在数据,如何选择日期范围内的所有天
【发布时间】:2011-05-05 14:55:42
【问题描述】:

我有一个应用需要显示过去 30 天的活动条形图。即使当天没有活动,图表也需要显示所有天。

例如:

DATE       COUNT
==================
1/1/2011   5 
1/2/2011   3 
1/3/2011   0
1/4/2011   4
1/5/2011   0
etc....

我可以在查询后进行后处理以找出缺少的日期并添加它们,但我想知道在 SQL Server 中是否有更简单的方法。非常感谢

【问题讨论】:

  • 提示:数字(又名日期)表

标签: sql-server tsql


【解决方案1】:

您可以使用递归 CTE 构建您的 30 天列表,然后将其加入您的数据中

--test
select cast('05 jan 2011' as datetime) as DT, 1 as val into #t
union all select CAST('05 jan 2011' as datetime), 1 
union all select CAST('29 jan 2011' as datetime), 1 

declare @start datetime = '01 jan 2011'
declare @end   datetime = dateadd(day, 29, @start)

;with amonth(day) as
(
    select @start as day
        union all
    select day + 1
        from amonth
        where day < @end
)
select amonth.day, count(val)
    from amonth 
    left join #t on #t.DT = amonth.day
group by amonth.day


>>

2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 2
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 0
...

【讨论】:

  • 如果天数大于 100,这将不起作用。我收到错误“最大递归深度为 100”。是的,答案回答了问题,但可能会导致错误。
  • 那么您的问题与 OP 中的问题不同,它具有绝对 30 项窗口,对此解决方案很好且不会出错。如果您想增加递归限制,只需告诉它这样做; option (maxrecursion 100)
【解决方案2】:

使用 CTE:

WITH DateTable
AS
(
    SELECT CAST('20110101' AS Date) AS [DATE]
    UNION ALL
    SELECT DATEADD(dd, 1, [DATE])
    FROM DateTable
    WHERE DATEADD(dd, 1, [DATE]) < cast('20110201' as Date)
)
SELECT dt.[DATE], ISNULL(md.[COUNT], 0) as [COUNT]
FROM [DateTable] dt
LEFT JOIN [MyData] md
ON md.[DATE] = dt.[DATE]

这是假设一切都是日期;如果是 DateTime,则必须截断(使用 DATEADD(dd, 0, DATEDIFF(dd, 0, [DATE])))。

【讨论】:

    【解决方案3】:

    @Alex K. 的回答是完全正确的,但它不适用于不支持递归公用表表达式的版本(如我正在使用的版本)。在这种情况下,以下将完成这项工作。

    DECLARE @StartDate datetime = '2015-01-01'
    DECLARE @EndDate datetime = SYSDATETIME()
    
    ;WITH days AS
    (
      SELECT DATEADD(DAY, n, DATEADD(DAY, DATEDIFF(DAY, 0, @StartDate), 0)) as d
        FROM ( SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
                n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
               FROM sys.all_objects ORDER BY [object_id] ) AS n
    )
    select days.d, count(t.val)
        FROM days LEFT OUTER JOIN yourTable as t
        ON t.dateColumn >= days.d AND t.dateColumn < DATEADD(DAY, 1, days.d)
    GROUP BY days.d
    ORDER BY days.d;
    

    【讨论】:

      【解决方案4】:

      我的场景比 OP 示例要复杂一些,所以我想分享一下以帮助遇到类似问题的其他人。我需要按日期对销售订单进行分组,而订单是按日期时间存储的。

      因此,在“天数”查找表中,我无法真正将时间存储为“00:00:00.000”的日期时间并获得任何匹配项。因此,我存储为字符串,并尝试直接加入转换后的值。

      这没有返回任何零行,解决方案是执行一个子查询,返回已经转换为字符串的日期。

      示例代码如下:

      declare @startDate datetime = convert(datetime,'09/02/2016')
      declare @curDate datetime = @startDate
      declare @endDate datetime = convert(datetime,'09/09/2016')
      declare @dtFormat int = 102;
      DECLARE @null_Date varchar(24) = '1970-01-01 00:00:00.000'
      
      /* Initialize #days table */
      select CONVERT(VARCHAR(24),@curDate, @dtFormat) as [Period] into #days
      
      /* Populate dates into #days table */
      while (@curDate < @endDate )
      begin
          set @curDate = dateadd(d, 1, @curDate)
          insert into #days values (CONVERT(VARCHAR(24),@curDate, @dtFormat))
      end
      
      /* Outer aggregation query to group by order numbers */
      select [Period], count(c)-case when sum(c)=0 then 1 else 0 end as [Orders],
      sum(c) as [Lines] from
      (
          /* Inner aggregation query to sum by order lines */ 
          select
              [Period], sol.t_orno, count(*)-1 as c   
              from (
                  /* Inner query against source table with date converted */
                  select convert(varchar(24),t_dldt, @dtFormat) as [shipdt], t_orno
                      from salesorderlines where t_dldt > @startDate
              ) sol
              right join #days on shipdt = #days.[Period]     
              group by [Period], sol.t_orno
      ) as t
      group by Period
      order by Period desc
      
      drop table #days
      

      示例结果:

      Period      Orders  Lines
      2016.09.09  388     422
      2016.09.08  169     229
      2016.09.07  1       1
      2016.09.06  0       0
      2016.09.05  0       0
      2016.09.04  165     241
      2016.09.03  0       0
      2016.09.02  0       0
      

      【讨论】:

        【解决方案5】:

        要么定义一个包含日期的静态表,要么动态创建一个临时表\表变量来存储您正在使用的活动表中(包括)最小和最大日期之间的每个日期。

        在两个表之间使用外连接以确保日期表中的每个日期都反映在输出中。

        如果您使用静态日期表,您可能希望将输出的日期范围限制在图表中所需的范围内。

        【讨论】:

          【解决方案6】:

          没有 Transact-SQL:MS SQL 2005 - 获取一个月中所有日期的列表:

          在我的例子中,“20121201”是一个预定义的值。


           SELECT TOp (Select Day(DateAdd(day, -Day(DateAdd(month, 1,
           '20121201')), 
                                    DateAdd(month, 1, '20121201')))) DayDate FROM ( SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY (SELECT
           NULL))-1,'20121201') as DayDate FROM sys.objects s1 CROSS JOIN
           sys.objects s2 ) q
          

          【讨论】:

            【解决方案7】:

            递归 CTE 最多可以工作 80 年,这已经足够了:

            DECLARE @dStart DATE,
                    @dEnd DATE
            SET @dStart = GETDATE ()
            SET @dEnd = DATEADD (YEAR, 80, @dStart)
            ;WITH CTE AS
            (
                SELECT @dStart AS dDay
                UNION ALL
                SELECT DATEADD (DAY, 1, dDay)
                FROM CTE
                WHERE dDay < @dEnd
            )
            SELECT * FROM CTE
            OPTION (MaxRecursion 32767)
            

            【讨论】:

              【解决方案8】:

              create a numbers table 并像这样使用它:

              declare @DataTable table (DateColumn datetime)
              insert @DataTable values ('2011-01-09')
              insert @DataTable values ('2011-01-10')
              insert @DataTable values ('2011-01-10')
              insert @DataTable values ('2011-01-11')
              insert @DataTable values ('2011-01-11')
              insert @DataTable values ('2011-01-11')
              
              declare @StartDate  datetime
              SET @StartDate='1/1/2011'
              
              select
                  @StartDate+Number,SUM(CASE WHEN DateColumn IS NULL THEN 0 ELSE 1 END)
                  FROM Numbers
                      LEFT OUTER JOIN @DataTable ON DateColumn=@StartDate+Number
                  WHERE Number>=1 AND Number<=15
                  GROUP BY @StartDate+Number
              

              输出:

              ----------------------- -----------
              2011-01-02 00:00:00.000 0
              2011-01-03 00:00:00.000 0
              2011-01-04 00:00:00.000 0
              2011-01-05 00:00:00.000 0
              2011-01-06 00:00:00.000 0
              2011-01-07 00:00:00.000 0
              2011-01-08 00:00:00.000 0
              2011-01-09 00:00:00.000 1
              2011-01-10 00:00:00.000 2
              2011-01-11 00:00:00.000 3
              2011-01-12 00:00:00.000 0
              2011-01-13 00:00:00.000 0
              2011-01-14 00:00:00.000 0
              2011-01-15 00:00:00.000 0
              2011-01-16 00:00:00.000 0
              
              (15 row(s) affected)
              

              【讨论】:

                【解决方案9】:

                也许是这样的: 创建 DaysTable 计算 30 天。 而DataTable包含“day”列和“count”列。 然后离开加入他们。

                WITH    DaysTable (name) AS (
                        SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 -- .. And so on to 30
                    ),
                    DataTable (name, value) AS (        
                        SELECT  DATEPART(DAY, [Date]), [Count]
                        FROM    YourExampleTable
                        WHERE   [Date] < DATEADD (day , -30 , getdate())
                    )
                SELECT  DaysTable.name, DataTable.value
                FROM    DaysTable LEFT JOIN
                        DataTable ON DaysTable.name = DataTable.name
                ORDER BY DaysTable.name
                

                【讨论】:

                  【解决方案10】:

                  对于那些有递归过敏的人

                  select SubQ.TheDate
                  from 
                  (
                      select DATEADD(day, a.a + (10 * b.a) + (100 * c.a), DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0) - 30) AS TheDate
                      from 
                      (
                          (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
                          cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
                          cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
                      ) 
                      WHERE a.a + (10 * b.a) + (100 * c.a) < 30
                  ) AS SubQ
                  ORDER BY TheDate
                  

                  【讨论】:

                    【解决方案11】:

                    试试看。

                    DECLARE @currentDate DATETIME = CONVERT(DATE, GetDate())
                    DECLARE @startDate   DATETIME = DATEADD(DAY, -DAY(@currentDate)+1, @currentDate)
                    
                    ;WITH fnDateNow(DayOfDate) AS
                    (
                        SELECT @startDate AS DayOfDate
                            UNION ALL
                        SELECT DayOfDate + 1 FROM fnDateNow WHERE DayOfDate < @currentDate
                    ) SELECT fnDateNow.DayOfDate FROM fnDateNow
                    

                    【讨论】:

                      【解决方案12】:

                      DECLARE @StartDate DATE = '20110101', @NumberOfYears INT = 1;
                      
                      DECLARE @CutoffDate DATE = DATEADD(YEAR, @NumberOfYears, @StartDate);
                      
                      
                      CREATE TABLE Calender
                      (
                        [date]       DATE
                      );
                      
                      
                      INSERT Calender([date]) 
                      SELECT d
                      FROM
                      (
                        SELECT d = DATEADD(DAY, rn - 1, @StartDate)
                        FROM 
                        (
                          SELECT TOP (DATEDIFF(DAY, '2011-01-01', '2011-12-31')) 
                            rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
                          FROM sys.all_objects AS s1
                          CROSS JOIN sys.all_objects AS s2
                          ORDER BY s1.[object_id]
                        ) AS x
                      ) AS y;
                      
                      
                      create table test(a date)
                      
                      insert into test values('1/1/2011')
                      insert into test values('1/1/2011')
                      insert into test values('1/1/2011')
                      insert into test values('1/1/2011')
                      insert into test values('1/1/2011')
                      
                      insert into test values('1/2/2011')
                      insert into test values('1/2/2011')
                      insert into test values('1/2/2011')
                      
                      insert into test values('1/4/2011')
                      insert into test values('1/4/2011')
                      insert into test values('1/4/2011')
                      insert into test values('1/4/2011')
                      
                      select c.date as DATE,count(t.a) as COUNT from calender c left join test t on c.date = t.a group by c.date

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2020-04-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-11-06
                        • 1970-01-01
                        相关资源
                        最近更新 更多