web-dev-qa-db-ja.com

SQL再帰とcte-日付の時系列

開始日のサブセットの時系列日付(たとえば10)のベクトルを作成する必要があります。

次のステートメントは、'2010-01-01'から始まる10個の連続する日付を返します。

;with cte as
(select 1 i union all
 select i+1 i from cte where i < 10)
select '2010-01-01' as start_date, dateadd(d, i-1, '2010-01-01') AS trial_date
INTO #tmp_trialdates
FROM cte

複数の開始日(必ずしも連続的ではない)のテーブル(または他のデータセット)のクエリを組み込み、各開始日の時系列日付を生成し、1つの出力テーブルにデータを入力する方法について、誰かが何か提案はありますか?

1
serge

このためには、再帰CTEのアンカーセットを変更する必要があるため、各行は、再帰部分を開始する前に、他のテーブルの日付で始まります。

DECLARE @AmountDays INT = 10

;WITH GeneratedDays AS
(
    -- Anchor
    SELECT
        DateSeriesIdentifier = T.DateSeriesIdentifier,
        Date = T.Date, -- Assuming it's DATE data type
        RecursionLevel = 1
    FROM
        OtherTable AS T

    UNION ALL

    -- Recursion
    SELECT
        DateSeriesIdentifier = G.DateSeriesIdentifier,
        Date = DATEADD(DAY, 1, G.Date),
        RecursionLevel = G.RecursionLevel + 1
    FROM
        GeneratedDays AS G
    WHERE
        G.RecursionLevel + 1 <= @AmountDays
)
SELECT
    G.DateSeriesIdentifier,
    G.Date,
    G.RecursionLevel
FROM
    GeneratedDays AS G
ORDER BY
    G.DateSeriesIdentifier,
    G.RecursionLevel

これは、日付をテストするために使用したセットの結果です2016-02-042018-01-19および2018-08-30

DateSeriesIdentifier    Date            RecursionLevel
21                      2016-02-04      1
21                      2016-02-05      2
21                      2016-02-06      3
21                      2016-02-07      4
21                      2016-02-08      5
21                      2016-02-09      6
21                      2016-02-10      7
21                      2016-02-11      8
21                      2016-02-12      9
21                      2016-02-13      10
620646                  2018-01-19      1
620646                  2018-01-20      2
620646                  2018-01-21      3
620646                  2018-01-22      4
620646                  2018-01-23      5
620646                  2018-01-24      6
620646                  2018-01-25      7
620646                  2018-01-26      8
620646                  2018-01-27      9
620646                  2018-01-28      10
639701                  2018-08-30      1
639701                  2018-08-31      2
639701                  2018-09-01      3
639701                  2018-09-02      4
639701                  2018-09-03      5
639701                  2018-09-04      6
639701                  2018-09-05      7
639701                  2018-09-06      8
639701                  2018-09-07      9
639701                  2018-09-08      10
2
EzLo

ここに1つの試みがあります。開始日と必要な期間の日数をinitに適切に追加します。

with init(start_date,num_days) as ( select cast('2010-01-01' as date), 1
                                    union all
                                    select cast('2017-11-12' as date), 4)
   , iter(start_date, num_days, dt) as (
       select start_date, num_days, start_date as dt from init
       union all
       select start_date, num_days, dateadd(DAY, 1, dt) 
       from iter where dt < dateadd(DAY, num_days-1, start_date)
)
select start_date,num_days from iter
order by start_date, dt;
1
Lennart

再帰的なCTEを使用して最終的な日付範囲を生成する代わりに、別のアプローチを使用します。 CTEは特定の再帰制限に制限されています。つまり、この制限よりも長い日付範囲は生成されません(デフォルトでは100に設定されていると思います)。この場合、100日を超えるデータ範囲を作成できないことを意味します。

私の提案は、CTEを使用していわゆるTally Tableを生成し、それをCROSS APPLYで使用して目的の結果を取得することです。これにより、「無制限」のデータ範囲をいくつでも作成できます。

整数の範囲を生成するテーブル値関数を作成してみましょう。

CREATE FUNCTION dbo.fnt_Numbers(@startValue int, @amount int)
RETURNS TABLE AS
RETURN 
WITH lv0 AS (SELECT 0 g UNION ALL SELECT 0)
    ,lv1 AS (SELECT 0 g FROM lv0 a CROSS JOIN lv0 b) -- 4
    ,lv2 AS (SELECT 0 g FROM lv1 a CROSS JOIN lv1 b) -- 16
    ,lv3 AS (SELECT 0 g FROM lv2 a CROSS JOIN lv2 b) -- 256
    ,lv4 AS (SELECT 0 g FROM lv3 a CROSS JOIN lv3 b) -- 65,536
    ,Numbers AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS num FROM lv4)
SELECT @startValue + num - 1 AS n
FROM Numbers
WHERE num <= @amount;

次のように使用できます。

SELECT n FROM dbo.fnt_Numbers(0, 10);

結果の例:

Sample result

次に、このコードを使用して、必要な時間範囲を生成できます。

CREATE TABLE #Date (
StartDate date NOT NULL,
DaysAmount int NOT NULL 
);

--Example data
INSERT INTO #Date (StartDate,DaysAmount)
VALUES ('2018-08-31', 10), ('2000-10-10', 5);

--Review table content
SELECT StartDate, DaysAmount 
FROM #Date;

--Generate ranges
SELECT Dates.*
FROM #Date
CROSS APPLY (SELECT DATEADD(day,n, StartDate) AS [Date] , n FROM fnt_Numbers(0, DaysAmount)) AS Dates
ORDER BY StartDate, n;

結果:

Final result

1
Marek Masko