特定の日付範囲で毎日テーブルに行を作成するストアドプロシージャを作成したいと思います。ストアドプロシージャは、ユーザーが希望する日付範囲の開始日と終了日という2つの入力を受け入れます。
だから、私はそのようなテーブルがあるとしましょう:
SELECT Day, Currency
FROM ConversionTable
DayはDateTimeであり、Currencyは単なる整数です。
話を簡単にするために、挿入された各行のCurrency列を常に1にしたいとします。したがって、誰かが「2017年3月5日」を開始日として入力し、「2017年4月11日」を終了日として入力した場合、次の行を作成します。
2017-03-05 00:00:00, 1
2017-03-06 00:00:00, 1
...
2017-04-11 00:00:00, 1
これを行うためにストアドプロシージャをコーディングする最良の方法は何ですか?私のテスト環境ではSQL Server 2008 R2を使用していますが、実際の環境ではSQL Server 2012を使用しているため、このタスクを簡単にする2012年に導入された新機能があれば、テストマシンをアップグレードできます。
1つのオプションは再帰CTEです。
DECLARE @StartDate datetime = '2017-03-05'
,@EndDate datetime = '2017-04-11'
;
WITH theDates AS
(SELECT @StartDate as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM theDates
WHERE DATEADD(day, 1, theDate) <= @EndDate
)
SELECT theDate, 1 as theValue
FROM theDates
OPTION (MAXRECURSION 0)
;
(下記のScott Hodginのコメントのおかげで、MAXRECURSION
ヒントが追加されました。)
別のオプションは、テーブル値関数を使用することです。このアプローチは非常に高速で、もう少し柔軟性があります。日付/時間範囲、DatePart、および増分を指定します。また、CROSS APPLYに含める利点もあります。
例
Select * from [dbo].[udf-Range-Date]('2017-03-05','2017-04-11','DD',1)
返品
RetSeq RetVal
1 2017-03-05 00:00:00.000
2 2017-03-06 00:00:00.000
3 2017-03-07 00:00:00.000
4 2017-03-08 00:00:00.000
5 2017-03-09 00:00:00.000
...
36 2017-04-09 00:00:00.000
37 2017-04-10 00:00:00.000
38 2017-04-11 00:00:00.000
興味がある場合はUDF
CREATE FUNCTION [dbo].[udf-Range-Date] (@R1 datetime,@R2 datetime,@Part varchar(10),@Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case @Part When 'YY' then DateDiff(YY,@R1,@R2)/@Incr When 'QQ' then DateDiff(QQ,@R1,@R2)/@Incr When 'MM' then DateDiff(MM,@R1,@R2)/@Incr When 'WK' then DateDiff(WK,@R1,@R2)/@Incr When 'DD' then DateDiff(DD,@R1,@R2)/@Incr When 'HH' then DateDiff(HH,@R1,@R2)/@Incr When 'MI' then DateDiff(MI,@R1,@R2)/@Incr When 'SS' then DateDiff(SS,@R1,@R2)/@Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,@R1 Union All Select N,Case @Part When 'YY' then DateAdd(YY, N*@Incr, @R1) When 'QQ' then DateAdd(QQ, N*@Incr, @R1) When 'MM' then DateAdd(MM, N*@Incr, @R1) When 'WK' then DateAdd(WK, N*@Incr, @R1) When 'DD' then DateAdd(DD, N*@Incr, @R1) When 'HH' then DateAdd(HH, N*@Incr, @R1) When 'MI' then DateAdd(MI, N*@Incr, @R1) When 'SS' then DateAdd(SS, N*@Incr, @R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=@R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
例として 日付ディメンションテーブルの作成方法 に関するAaron Bertrandの投稿を使用して、私はこれを思いつきました:
DECLARE @StartDate DATE ='2017-03-05 00:00:00'
DECLARE @EndDate DATE ='2017-04-11 00:00:00'
Declare @DateTable table ([date] DATE PRIMARY KEY);
-- use the catalog views to generate as many rows as we need
INSERT @DateTable ([date])
SELECT d
FROM (
SELECT d = DATEADD(DAY, rn - 1, @StartDate)
FROM (
SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate)) rn = ROW_NUMBER() OVER (
ORDER BY s1.[object_id]
)
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
-- on my system this would support > 5 million days
ORDER BY s1.[object_id]
) AS x
) AS y;
SELECT *
FROM @DateTable
ORDER BY [date]
このタイプのロジックをストアード・プロシージャーに入れて、必要なものを何でも追加できるはずです。
Redshiftでは最近、同様の問題を解決する必要がありました。読み取りアクセスしかできなかったため、結果セットの開始点として、日付範囲の1時間ごとに行を取得する純粋なSQLベースのソリューション(ストアドプロシージャなし)が必要でした。他の人がこれをよりエレガントにして目的に合わせて変更できると確信していますが、必要な人のために、これが私のハッキングされた解決策です:
with hours as
(select 0 clockhour union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 10 union select 11 union select 12
union select 13 union select 14 union select 15 union select 16 union select 17 union select 18 union select 19 union select 20 union select 21 union select 22 union select 23)
, days as
(select *
from
(select to_number(n0.number || n1.number, '99') daynum
from
(select 0 as number union select 1 union select 2 union select 3) as n0
cross join
(select 1 as number union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 0) as n1)
where daynum between 1 and 31)
, months as
(select 1 as monthnum, 'jan' as themonth, 31 as numdays union select 2, 'feb', 28 union select 3, 'mar', 31 union select 4, 'apr', 30 union select 5, 'may', 31 union select 6, 'jun', 30
union select 7, 'jul', 31 union select 8, 'aug', 31 union select 9, 'sep', 30 union select 10, 'oct', 31 union select 11, 'nov', 30 union select 12, 'dec', 31)
, years as
(select century || decade || yr as yr
from
(select 19 century union select 20)
cross join
(select 0 decade union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9)
cross join
(select 0 yr union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9))
select cast(daynum || '-' || themonth || '-' || yr || ' ' || clockhour || ':00:00' as timestamp) dayhour
from hours
cross join days
cross join months
cross join years
where cast(daynum || '-' || themonth || '-' || yr || ' ' || clockhour || ':00:00' as timestamp)
between date_trunc('month', dateadd('month', -$MONTHS_AGO, getdate()))
and date_trunc('month', dateadd('month', $MONTHS_AHEAD, getdate()))
and daynum <= numdays
order by yr, monthnum, daynum, clockhour;