저장 프로 시저를 사용하여 날짜 범위에서 매일 행을 만드는 방법은 무엇입니까?


11

주어진 날짜 범위에서 매일 테이블에 행을 만드는 저장 프로 시저를 만들고 싶습니다. 저장 프로시 저는 사용자가 원하는 날짜 범위의 시작 날짜와 종료 날짜라는 두 가지 입력을 허용합니다.

그래서 다음과 같은 테이블이 있다고 가정 해 봅시다.

SELECT Day, Currency
FROM ConversionTable

Day는 DateTime이고 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 년에 도입 된이 새로운 기능이 있으면 테스트 시스템을 업그레이드 할 수 있습니다.

답변:


15

한 가지 옵션은 재귀 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)
;

( MAXRECURSION아래 Scott Hodgin의 의견 덕분에 힌트가 추가되었습니다.)


아, 네! 나는 "숫자"테이블 접근법 (Scott Hodgin의 답변)에 대한 생각을주었습니다. 며칠 동안 성능이 더 좋아질 수 있습니다. 재사용 가능한 좋은 접근 방식 (John C의 답변)은 항상 좋습니다. 이것은 내가 생각할 수있는 가장 단순하고 가장 간단한 대답이었습니다.
RDFozz

1
그냥 기억 - 넓은 날짜 범위가 :)에 전달 될 때 나는 타격으로 SP에 대한 증오 - 당신이 MAXRECURSION을 지정하지 않는 경우 재귀 (100)의 기본 제한이
스콧 Hodgin을

2
@Scott Hodgin 당신이 맞아요. MAXRECURSION해결할 힌트를 쿼리에 추가했습니다 .
RDFozz

이 방법에는 다음 날짜의 EndDate 값을 포함하는 의도적이거나 의도하지 않은 동작에 시간이 포함됩니다. 이 동작이 바람직하지 않은 경우 WHERE를 DATEADD (DAY, 1, theDate) <@EndDate
Chris Porter

1
@ChrisPorter-훌륭한 포인트! 그러나 확인 DATEADD(DAY, 1, theDate) < @EndDate하면 두 datetime 값의 시간 구성 요소가 동일한 경우 범위의 끝을 얻지 못합니다. 나는 대답을 적절하게 수정했지만을 사용했습니다 <= @EndDate. 범위 끝 값을 포함 하지 않으려 는 경우 < @EndDate실제로 정확합니다.
RDFozz

6

다른 옵션은 Table-Valued-Function을 사용하는 것입니다. 이 방법은 매우 빠르며 유연성이 조금 더 좋습니다. 날짜 / 시간 범위, 날짜 부분 및 증분을 제공합니다. 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) 
*/

@RobV 매우 사실입니다. 오래 전에 배운 것은 진정한 대답이 없습니다.
John Cappelletti

답장을 보내 주셔서 감사합니다. 이 작업을 수행하는 여러 가지 방법이있는 것 같습니다 :) 다른 사람이 원하는 결과를 정확하게 해결하는 방식으로 내 질문에 대답했지만 더 유연하다고 생각합니다.
Rob V

@JohnCappelletti 이것은 내가해야 할 일에 완벽하지만 주말과 공휴일을 제거하는 데 필요했습니다 ... 마지막 줄을 다음과 같이 변경하여 수행했습니다 .D <= @ R2 AND DATEPART (Weekday, D) in (1,7) 및 D NOT IN (휴일을 선택하십시오. 휴일은 휴일 날짜를 포함하는 테이블입니다.
MattE

@MattE 잘 했어요! 주말과 공휴일을 제외하려면 동일한 작업을 수행했을 것입니다. :)
John Cappelletti

3

예를 들어 날짜 차원 테이블을 만드는 방법대한 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]

이 유형의 논리를 저장 프로 시저에 넣고 필요한 다른 것을 추가 할 수 있어야합니다.


2

최근 Redshift에서 비슷한 문제를 해결해야했습니다. 읽기 권한 만 있었기 때문에 결과 집합의 시작점으로 날짜 범위에서 매 시간마다 행을 가져 오는 순수한 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;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.