【问题标题】:Generate Dates between date ranges生成日期范围之间的日期
【发布时间】:2011-12-11 02:37:26
【问题描述】:

我需要填充一个表格来存储两个给定日期之间的日期范围:09/01/11 - 10/10/11

所以在这种情况下,表格将从 2011 年 9 月 1 日开始并每天存储,直到 2011 年 10 月 10 日 我想知道在 SQL Server 中是否有一种巧妙的方法来执行此操作 - 我目前正在使用 SQL Server 2008。谢谢

【问题讨论】:

标签: sql sql-server tsql sql-server-2008 range


【解决方案1】:

易于使用 SQL 2005+;如果您有数字或计数表,则更容易。我在下面伪造了它:

DECLARE @StartDate DATE = '20110901'
  , @EndDate DATE = '20111001'

SELECT  DATEADD(DAY, nbr - 1, @StartDate)
FROM    ( SELECT    ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
          FROM      sys.columns c
        ) nbrs
WHERE   nbr - 1 <= DATEDIFF(DAY, @StartDate, @EndDate)

如果您有一个计数表,请将子查询替换为该表。没有递归。

【讨论】:

  • 在 Spark JDBC 和 CTE 中进行原型设计时非常有用,因为所有内容都被包装为子查询。
  • 这太棒了!工作!你能解释一下它是如何工作的吗? SELECT ROW_NUMBER() OVER (ORDER BY c.object_id) AS Nbr FROM sys.columns c 会发生什么
  • 该子查询正在根据数据库中的总列数生成一个数字表 (1-x)。您需要一个数字表才能使这个总查询起作用;子查询只是构建子查询的一种方法。
【解决方案2】:

如果您使用的是 SQL Server 2005 或更高版本,请尝试以下操作:

WITH Dates AS (
        SELECT
         [Date] = CONVERT(DATETIME,'09/01/2011')
        UNION ALL SELECT
         [Date] = DATEADD(DAY, 1, [Date])
        FROM
         Dates
        WHERE
         Date < '10/10/2011'
) SELECT
 [Date]
FROM
 Dates
 OPTION (MAXRECURSION 45)

一个很好的例子,你可以用 CTE 做一些很酷的事情。

【讨论】:

    【解决方案3】:

    -- 声明

    DECLARE @dates TABLE(dt datetime)    
    DECLARE @dateFrom datetime
    DECLARE @dateTo datetime
    
    SET @dateFrom = '2001/01/01'
    SET @dateTo = '2001/01/12'
    

    -- 查询:

    WHILE(@dateFrom < @dateTo)
    BEGIN
       SELECT @dateFrom = DATEADD(day, 1,@dateFrom)
       INSERT INTO @dates 
       SELECT @dateFrom
    END
    

    -- 输出

    SELECT * FROM @dates
    

    【讨论】:

    • 虽然在这种情况下它可能无关紧要,但我希望人们不要养成在基于集合的解决方案可用时使用它们的习惯,因为它可能会导致性能问题错误的情况。
    • @AbeMiessler - 公平地说,递归 CTE 仍然是一个循环。
    【解决方案4】:

    这里是一个不需要递归的解决方案,同时这个表值函数可以在很多查询中重复使用,不需要再次重复声明样板变量。对于那些不想递归的人来说,这是唯一的选择。

    创建这个简单的函数:

    CREATE FUNCTION [dbo].[GenerateDateRange]
    (@StartDate AS DATE,
     @EndDate AS   DATE,
     @Interval AS  INT
    )
    RETURNS @Dates TABLE(DateValue DATE)
    AS
    BEGIN
        DECLARE @CUR_DATE DATE
        SET @CUR_DATE = @StartDate
        WHILE @CUR_DATE <= @EndDate BEGIN
            INSERT INTO @Dates VALUES(@CUR_DATE)
            SET @CUR_DATE = DATEADD(DAY, @Interval, @CUR_DATE)
        END
        RETURN;
    END;
    

    然后选择:

    select *
    from dbo.GenerateDateRange('2017-01-03', '2017-12-01', 1)
    

    【讨论】:

    • 不同的是,这个小解决方案不需要递归,同时可以在许多查询中重复使用,而无需再次重复声明样板变量。对于那些不想递归的人,我的解决方案只是一种选择。我从来没有说过要成为那个人。
    • 该解决方案非常好,易于实施。谢谢你。
    【解决方案5】:

    我意识到这是一个旧线程,但我不得不承认我对这里给出的递归和循环解决方案过多感到沮丧。我想知道有多少人意识到递归只不过是一个非常昂贵的循环?我理解创建表值函数的愿望,但我建议以下方法效率更高,因为它是基于集合的,没有循环、递归或重复的单个插入语句:

    CREATE FUNCTION dbo.GenerateDateRange(@StartDate AS DATE, @EndDate AS DATE)
    RETURNS TABLE WITH SCHEMABINDING AS
        WITH e1(n) AS (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS x(n)) -- 16 records
            ,e2(n) AS (SELECT 1 FROM e1 a CROSS JOIN e1 b) -- 16^2 or 256 records (16*16)
            ,cteTally(n) AS (SELECT ROW_NUMBER() over (ORDER BY 1) AS n FROM e2 a CROSS JOIN e2 b) -- 16^4 or 65,536 records (256*256)
        SELECT DATEADD(DAY, n-1, @StartDate)
        FROM cteTally
        WHERE n <= DATEDIFF(DAY, @StartDate, @EndDate) + 1;
    GO
    

    【讨论】:

      【解决方案6】:

      使用 MVJ 的 F_TABLE_DATE 函数,简直太棒了:

      http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=61519

      一旦你实现了这个,只需传入开始和结束日期,你就可以插入所有日期。

      【讨论】:

        【解决方案7】:

        这是一个旧线程,但如果它可以帮助任何人,这是我在支持 CTE 的现代版本的 SQL Server 中使用的。这也为您提供了星期几,并且可以对其进行调整以提供您可能需要的其他值(即季度、月份等)。

        DECLARE @StartDate datetime
        DECLARE @EndDate datetime
        SET @StartDate = '1/1/2020'
        SET @EndDate = '12/31/2020'
        DECLARE @DayTable Table(theDate date, theDayOfWeek nvarchar(50));
        WITH DayTable AS (SELECT CAST(@StartDate AS DATETIME) theDate, DATENAME(dw, @StartDate) theDayOfWeek UNION ALL SELECT DATEADD(dd, 1, theDate), DATENAME(dw,DATEADD(dd, 1, theDate)) FROM DayTable s  WHERE DATEADD(dd, 1, theDate) <= CAST(@EndDate AS DATETIME)) 
        INSERT INTO @DayTable(theDate, theDayOfWeek) SELECT theDate, theDayOfWeek FROM DayTable OPTION (MAXRECURSION 365); 
        SELECT * FROM @DayTable
        

        【讨论】:

          【解决方案8】:

          如果由于某种原因你不能declare变量,比如在Looker中使用derived tables时,你可以这样:

          select
            dateadd(day, nbr - 1, convert(date, '2017-01-01')) as d
          from (
            select row_number() over (order by c.object_id) as nbr from sys.columns c
          ) nbrs
          where
            nbr - 1 <= datediff(
              day,
              convert(date, '2017-01-01'),
              convert(date, '2018-12-31')
            )
          

          顺便说一句,这就是您的 日期系列 视图在 LookerML 中的样子:

          view: date_series {
            derived_table: {
              sql:
                select
                  dateadd(day, nbr - 1, convert(date, '2017-01-01')) as d
                from (
                  select row_number() over (order by c.object_id) as nbr from sys.columns c
                ) nbrs
                where
                  nbr - 1 <= datediff(day, convert(date, '2017-01-01'), convert(date, '2018-12-31')) ;;
            }
          
            dimension: date {
              primary_key: yes
              type: date
              sql: ${TABLE}.d ;;
            }
          }
          

          【讨论】:

            【解决方案9】:

            使用@Abe Miesler 的答案,为了方便其他人,我将其构建到 SQL Server 2008 及更高版本的 TVF 中。它可能对其他人有所帮助 - 我必须找到一种将 CTE 包含在 TVF 中的方法!

                --Generate a range of dates with interval option, courtesy of Abe Miessler for the core query here!
            ALTER FUNCTION [dbo].[DateRange]
            (@startDate AS DATE,
             @EndDate AS   DATE,
             @interval AS  INT
            )
            RETURNS @Dates TABLE(dateValue DATE)
            AS
                 BEGIN
                     WITH Dates
                          AS (
                          SELECT [Date] = CONVERT( DATETIME, @startDate)
                          UNION ALL
                          SELECT [Date] = DATEADD(DAY, ISNULL(@interval, 1), [Date])
                          FROM Dates
                          WHERE Date < @EndDate)
                          INSERT INTO @Dates
                                 SELECT [Date]
                                 FROM Dates
                                 OPTION(MAXRECURSION 900);
                     RETURN;
                 END;
            

            【讨论】:

              【解决方案10】:
              Declare @StartDate datetime = '2015-01-01'
              Declare @EndDate datetime = '2016-12-01'
              declare @DaysInMonth int
              declare @tempDateRange Table
              (
              DateFrom datetime,
              DateThru datetime
              );
              
              While @StartDate<=@EndDate
              begin
                  SET @DaysInMonth=DAY(DATEADD(DD,-1,DATEADD(MM,DATEDIFF(MM,-1,@StartDate),0)))
              
                  IF DAY(@StartDate)=1 
                      SET @EndDate=DATEADD(DAY,14,@StartDate)
                  ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=30
                      SET @EndDate=DATEADD(DAY,14,@StartDate)
                  ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=31
                      SET @EndDate=DATEADD(DAY,15,@StartDate)
                  ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=28
                      SET @EndDate=DATEADD(DAY,12,@StartDate)
                  ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=29
                      SET @EndDate=DATEADD(DAY,13,@StartDate)
              
                  INSERT INTO @tempDateRange (DateFrom,DateThru)
                  VALUES 
                   (
                      @StartDate,
                      @EndDate
                   )
              
                  SET @StartDate=DATEADD(DAY,1,@EndDate)
              
                  IF @EndDate< '2016-12-31'
                   IF DAY(@StartDate)=1 
                      SET @EndDate=DATEADD(DAY,14,@StartDate)
                   ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=30
                      SET @EndDate=DATEADD(DAY,14,@StartDate)
                   ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=31
                      SET @EndDate=DATEADD(DAY,15,@StartDate)
                   ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=28
                      SET @EndDate=DATEADD(DAY,12,@StartDate)
                   ELSE IF DAY(@StartDate)=16 AND @DaysInMonth=29
                      SET @EndDate=DATEADD(DAY,13,@StartDate)
              end ;
              
              select * from @tempDateRange
              
              +++++++++++++++++++++++++++++
              Result:
              DateFrom |DateThru
              

              【讨论】:

              • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
              【解决方案11】:
              CREATE table #ProductSales (ProjectID Int, ProjectName varchar(100), TotalBillableFees Money, StartDate Date, EndDate Date, DataDate Date)
              
                Insert into #ProductSales
                Values
                (373104,'Product Sales - Flex Creation Test',40000.00,'2019-04-01','2020-06-01','2019-08-01'),
                (375111,'Product Sales - SMART',40000.00,'2019-04-01','2019-09-01','2019-08-01')
              
                ;WITH Dates AS (
                      SELECT ProjectiD
                      ,Convert(decimal(10,2),TotalBillableFees/IIF(DATEDIFF(MONTH,StartDate,EndDate)=0,1,DATEDIFF(MONTH,StartDate,EndDate))) AS BillableFeesPerMonths,EndDate
                       ,[Date] = CONVERT(DATETIME,EOMONTH(StartDate))
                       FROM #ProductSales
                      UNION ALL SELECT ProjectiD,BillableFeesPerMonths,EndDate,
                       [Date] = DATEADD(MONTH, 1, [Date])
                      FROM
                       Dates
                      WHERE
                       Date < EOMONTH(EndDate)
              ) SELECT ProjectID,BillableFeesPerMonths,
               CAST([Date] as Date) Date
              FROM
               Dates
               OPTION (MAXRECURSION 45)
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-06-17
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-07-01
                相关资源
                最近更新 更多