簡體   English   中英

在 T-SQL 中生成日期序列

[英]Generate date sequence in T-SQL

在購買記錄中,我想在基准日期之外生成 +/-12 個月的序列

Client Date 
Joe    2020-03-15
Maria  2019-11-01

所以期望的結果是

Client Date 
Joe    2019-03-15
Joe    2019-04-15
.
.
.
Joe    2020-03-15
.
.
.
Joe    2021-02-15
Joe    2021-03-15
Maria  2018-11-01
Maria  2018-12-01
.
.
.
Maria  2019-11-01
.
.
.
Maria  2020-10-01
Maria  2020-11-01

如何做到這一點?

一種選擇使用遞歸查詢:

with cte as (
    select client, dateadd(month, -12, date) date, 0 lvl from mytbale
    union all
    select client, dateadd(month, 1, date), lvl + 1 from cte where lvl < 24
)
select * from cte order by client, date

另一種選擇是與CROSS JOIN配合使用的臨時 Tally Table

例子

Select A.Client
      ,[Date] = date,DateAdd(MONTH,N,[Date])
 From  YourTable A
 Cross Join  ( Select Top (12+1) N=-1+Row_Number() Over (Order By (Select Null)) From  master..spt_values n1)  B

退貨

Client  Date
Joe     2020-03-15
Joe     2020-04-15
Joe     2020-05-15
Joe     2020-06-15
Joe     2020-07-15
Joe     2020-08-15
Joe     2020-09-15
Joe     2020-10-15
Joe     2020-11-15
Joe     2020-12-15
Joe     2021-01-15
Joe     2021-02-15
Joe     2021-03-15
Maria   2019-11-01
Maria   2019-12-01
Maria   2020-01-01
Maria   2020-02-01
Maria   2020-03-01
Maria   2020-04-01
Maria   2020-05-01
Maria   2020-06-01
Maria   2020-07-01
Maria   2020-08-01
Maria   2020-09-01
Maria   2020-10-01
Maria   2020-11-01

編輯:如果您想要 +/- 12 個月,只需稍作調整

 ...
 Cross Join  ( Select Top (24+1) N=-13+Row_Number() Over (Order By (Select Null)) From  master..spt_values n1)  B

這是一個稍微不同的解決方案,它使用了 Tally 表的本質。 我發布它的原因是因為很多人不允許他們的 DBA 使用主數據庫中的東西,也因為需要一系列值是很常見的事情。 以下方法也會導致序列生成中沒有邏輯讀取。

也就是說,“Tally Table”經常被稱為 T-SQL 的“瑞士軍刀”。 十多年前,Erland Sommarskog 通過 MS“CONNECT”(“Feedback”的舊名稱)請求了一個內置的序列生成器,這一點非常重要。 不幸的是,除了保持請求開放十多年外,MS 什么也沒做。

考慮到這一點,這是我對 Itzik Ben-Gan 創造這樣一個 function 的出色工作的演繹。 它非常有意地只能從“0”或“1”開始,但 go 可以達到(實際上超過)您可能需要的任何正 INT 值。 如果您需要它以不同於“0”或“1”的數字開頭,那么在 function 之外進行一點 integer 數學運算即可實現。 我沒有在 function 中構建該功能的原因是因為從“0”或“1”開始的序列是最常見的用法,我不想因為極端用法而懲罰性能甚至一兩毫秒我把這個 function 通過。

這是 function ......每個人都應該在每個數據庫或公眾可以使用的“實用程序”數據庫中都有類似的東西。

 CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
 Purpose:
 Return a column of BIGINTs from @ZeroOrOne up to and including @MaxN with a max value of 10 Quadrillion.

 Usage:
--===== Syntax example
 SELECT t.N
   FROM dbo.fnTally(@ZeroOrOne,@MaxN) t
;
 @ZeroOrOne will internally conver to a 1 for any number other than 0 and a 0 for a 0.
 @MaxN has an operational domain from 0 to 4,294,967,296. Silent truncation occurs for larger numbers.

 Please see the following notes for other important information

 Notes:
 1. This code works for SQL Server 2008 and up.
 2. Based on Itzik Ben-Gan's cascading CTE (cCTE) method for creating a "readless" Tally Table source of BIGINTs.
    Refer to the following URL for how it works.
    https://www.itprotoday.com/sql-server/virtual-auxiliary-table-numbers
 3. To start a sequence at 0, @ZeroOrOne must be 0. Any other value that's convertable to the BIT data-type
    will cause the sequence to start at 1.
 4. If @ZeroOrOne = 1 and @MaxN = 0, no rows will be returned.
 5. If @MaxN is negative or NULL, a "TOP" error will be returned.
 6. @MaxN must be a positive number from >= the value of @ZeroOrOne up to and including 4,294,967,296. If a larger
    number is used, the function will silently truncate after that max. If you actually need a sequence with that many
    or more values, you should consider using a different tool. ;-)
 7. There will be a substantial reduction in performance if "N" is sorted in descending order.  If a descending sort is
    required, use code similar to the following. Performance will decrease by about 27% but it's still very fast 
    especially compared with just doing a simple descending sort on "N", which is about 20 times slower.
    If @ZeroOrOne is a 0, in this case, remove the "+1" from the code.

    DECLARE @MaxN BIGINT; 
     SELECT @MaxN = 1000;
     SELECT DescendingN = @MaxN-N+1 
       FROM dbo.fnTally(1,@MaxN);

 8. There is no performance penalty for sorting "N" in ascending order because the output is implicity sorted by
    ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
 9. This will return 1-10,000,000 to a bit-bucket variable in about 986ms.
    This will return 0-10,000,000 to a bit-bucket variable in about 1091ms.
    This will return 1-4,294,967,296 to a bit-bucket variable in about 9:12( mi:ss).

 Revision History:
 Rev 00 - Unknown     - Jeff Moden 
        - Initial creation with error handling for @MaxN.
 Rev 01 - 09 Feb 2013 - Jeff Moden 
        - Modified to start at 0 or 1.
 Rev 02 - 16 May 2013 - Jeff Moden 
        - Removed error handling for @MaxN because of exceptional cases.
 Rev 03 - 07 Sep 2013 - Jeff Moden 
        - Change the max for @MaxN from 10 Billion to 10 Quadrillion to support an experiment. 
          This will also make it much more difficult for someone to actually get silent truncation in the future.
 Rev 04 - 04 Aug 2019 - Jeff Moden
        - Enhance performance by making the first CTE provide 256 values instead of 10, which limits the number of
          CrossJoins to just 2. Notice that this changes the maximum range of values to "just" 4,294,967,296, which
          is the entire range for INT and just happens to be an even power of 256. Because of the use of the VALUES
          clause, this code is "only" compatible with SQLServer 2008 and above.
        - Update old link from "SQLMag" to "ITPro". Same famous original article, just a different link because they
          changed the name of the company (twice, actually).
        - Update the flower box notes with the other changes.
**********************************************************************************************************************/
        (@ZeroOrOne BIT, @MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS 
 RETURN WITH
  H2(N) AS ( SELECT 1 
               FROM (VALUES
                     (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    )V(N))            --16^2 or 256 rows
, H4(N) AS (SELECT 1 FROM H2 a, H2 b) --16^4 or 65,536 rows
, H8(N) AS (SELECT 1 FROM H4 a, H4 b) --16^8 or 4,294,967,296 rows
            SELECT N = 0 WHERE @ZeroOrOne = 0 UNION ALL
            SELECT TOP(@MaxN)
                   N = ROW_NUMBER() OVER (ORDER BY N)
              FROM H8
;

ps 我通常不會對對象使用“匈牙利符號”,但我也有一個 Tally 表,你不能有兩個同名的對象。

一旦你有了類似的東西,需要數字序列的問題就變得容易、快速、資源低廉。 例如,下面是我如何使用 function 對 OP 問題的解決方案進行編碼。

--===== Solve the problem using the fnTally function as a numeric sequence generator starting
     -- at 0 and ending at the +/- 12 month differences for each Client's base date.
DECLARE @OffsetMonths INT = 12 --Just to make it a bit more flexible
;
 SELECT  d.Client
        ,[Date] = DATEADD(mm,t.N-@OffsetMonths,d.[Date])
   FROM #TestTable d
  CROSS APPLY dbo.fnTally(0,@OffsetMonths*2) t
  ORDER BY d.Client,[Date]
;

如果您想測試該代碼,這是我為測試表構建的。

--===== Create and populate a test table.
     -- This is NOT a part of the solution.
     -- We're just creating test data here.
   DROP TABLE IF EXISTS #TestTable
;
 CREATE TABLE #TestTable
        (
         Client VARCHAR(20)
        ,[Date] DATE
        )
;
 INSERT INTO #TestTable
        (Client,[Date])
 VALUES  ('Joe','2020-03-15')
        ,('Maria','2019-11-01')
;

如果您想了解更多關於“Tally Tables”和相關功能如何工作的信息,這里是我的文章的鏈接,這篇文章太長了,無法在此處發布。

“數字”或“Tally”表:它是什么以及它如何替換循環

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM