T-SQL에서 이전 달의 데이터를 기반으로 누락 된 달의 값을 결정하는 방법

StackOverflow https://stackoverflow.com/questions/808356

문제

특정 시점에서 발생하는 일련의 거래가 있습니다.

CREATE TABLE Transactions (
    TransactionDate Date NOT NULL,
    TransactionValue Integer NOT NULL
)

데이터는 다음과 같습니다.

INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('1/1/2009', 1)
INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('3/1/2009', 2)
INSERT INTO Transactions (TransactionDate, TransactionValue)
VALUES ('6/1/2009', 3)

TransactionValue가 어떤 종류의 수준을 설정한다고 가정하면 트랜잭션 사이에 레벨이 무엇인지 알아야합니다. T-SQL 쿼리 세트의 맥락에서 이것을 필요로하므로 다음과 같은 결과 세트를 얻을 수있는 것이 가장 좋습니다.

Month   Value
1/2009  1
2/2009  1
3/2009  2
4/2009  2
5/2009  2
6/2009  3

매월 우리는 거래에 지정된 값을 얻거나 가장 최근의 비 널 값을 얻는 방법에 유의하십시오.

내 문제는 이것을하는 방법이 거의 없다는 것입니다! 나는 "중간"레벨 SQL 개발자 일뿐 아니라 전에는 이런 식으로 본 적이없는 것을 기억하지 못합니다. 당연히 프로그램에서 원하는 데이터를 만들거나 커서를 사용할 수 있지만 더 나은 세트 지향적 인 방법이 있는지 알고 싶습니다.

SQL Server 2008을 사용하고 있으므로 새로운 기능이 도움이되면 듣고 싶습니다.

추신 : 누군가이 질문을 더 나은 방법이나 더 나은 제목 줄을 생각할 수 있다면, 나는 그것을 크게 감사합니다. 절름발이이지만 "확산"이 내가 생각해 낼 수있는 최선이라고 결정하는 데 꽤 오랜 시간이 걸렸습니다. "번짐"이 더 나빠졌습니다.

도움이 되었습니까?

해결책

순차적 정수를 1에서 백만 정도의 숫자 테이블을 구축하는 것으로 시작했습니다. 당신이 그것을 매달린 후에는 정말 편리합니다.

예를 들어, 2008 년에 매월 1 위를 차지하는 방법은 다음과 같습니다.

select firstOfMonth = dateadd( month, n - 1, '1/1/2008')
from Numbers
where n <= 12;

이제 OUTER APPLIC을 사용하여 해당하는 것과 같은 각 날짜에 대한 최신 거래를 찾을 수 있습니다.

with Dates as (
    select firstOfMonth = dateadd( month, n - 1, '1/1/2008')
    from Numbers
    where n <= 12
)
select d.firstOfMonth, t.TransactionValue
from Dates d
outer apply (
    select top 1 TransactionValue
    from Transactions
    where TransactionDate <= d.firstOfMonth
    order by TransactionDate desc
) t;

이것은 당신이 찾고있는 것을 제공해야하지만 숫자 테이블을 만드는 가장 좋은 방법을 찾기 위해 Google을 조금만 찾아야 할 수도 있습니다.

다른 팁

여기 내가 생각해 낸 것입니다

declare @Transactions table (TransactionDate datetime, TransactionValue int)

declare @MinDate datetime
declare @MaxDate datetime
declare @iDate datetime
declare @Month int
declare @count int
declare @i int
declare @PrevLvl int

insert into @Transactions (TransactionDate, TransactionValue)
select '1/1/09',1

insert into @Transactions (TransactionDate, TransactionValue)
select '3/1/09',2

insert into @Transactions (TransactionDate, TransactionValue)
select '5/1/09',3


select @MinDate = min(TransactionDate) from @Transactions
select @MaxDate = max(TransactionDate) from @Transactions

set @count=datediff(mm,@MinDate,@MaxDate)
set @i=1
set @iDate=@MinDate


while (@i<=@count)
begin

    set @iDate=dateadd(mm,1,@iDate)

    if (select count(*) from @Transactions where TransactionDate=@iDate) < 1
    begin

        select @PrevLvl = TransactionValue from @Transactions where TransactionDate=dateadd(mm,-1,@iDate)

        insert into @Transactions (TransactionDate, TransactionValue)
        select @iDate, @prevLvl

    end


    set @i=@i+1
end

select *
from @Transactions
order by TransactionDate

세트 기반 방식으로 수행하려면 모든 데이터 또는 정보에 대한 세트가 필요합니다. 이 경우 "몇 달이 있습니까?"라는 간과 된 데이터가 있습니다. "캘린더"테이블과 데이터베이스의 "숫자"테이블을 유틸리티 테이블로 사용하는 것이 매우 유용합니다.

다음은 이러한 방법 중 하나를 사용하는 솔루션입니다. 첫 번째 코드는 캘린더 테이블을 설정합니다. 커서 또는 수동으로 또는 무엇이든 사용하여 채울 수 있으며 비즈니스에 필요한 날짜 범위 (1900-01-01로 또는 1970-01-01로 돌아가서 미래로 돌아갈 수 있습니다. 원하다). 비즈니스에 유용한 다른 열을 추가 할 수도 있습니다.

CREATE TABLE dbo.Calendar
(
     date           DATETIME     NOT NULL,
     is_holiday     BIT          NOT NULL,
     CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED (date)
)

INSERT INTO dbo.Calendar (date, is_holiday) VALUES ('2009-01-01', 1)  -- New Year
INSERT INTO dbo.Calendar (date, is_holiday) VALUES ('2009-01-02', 1)
...

이제이 테이블을 사용하면 질문이 사소 해집니다.

SELECT
     CAST(MONTH(date) AS VARCHAR) + '/' + CAST(YEAR(date) AS VARCHAR) AS [Month],
     T1.TransactionValue AS [Value]
FROM
     dbo.Calendar C
LEFT OUTER JOIN dbo.Transactions T1 ON
     T1.TransactionDate <= C.date
LEFT OUTER JOIN dbo.Transactions T2 ON
     T2.TransactionDate > T1.TransactionDate AND
     T2.TransactionDate <= C.date
WHERE
     DAY(C.date) = 1 AND
     T2.TransactionDate IS NULL AND
     C.date BETWEEN '2009-01-01' AND '2009-12-31'  -- You can use whatever range you want

John Gibb는 이미 받아 들여진 훌륭한 답변을 게시했지만 다음으로 약간 확장하고 싶었습니다.

  • 1 년 제한을 제거하고
  • 날짜 범위를보다 명백한 방식으로 노출시키고
  • 별도의 숫자 테이블이 필요하지 않습니다.

이 약간의 변형은 a를 사용합니다 재귀적인 공통 테이블 표현식 Daterange에서 정의 된 날짜 또는 날짜에 매월 첫 번째 날짜를 나타내는 날짜 세트를 설정합니다. 스택 오버플로 (!)를 방지하기 위해 MaxRecursion 옵션을 사용하십시오. 예상되는 최대 개월 수를 수용하기 위해 필요에 따라 조정하십시오. 또한, 주, 분기, 심지어 일상조차도 대체 날짜 조립 로직을 추가하는 것을 고려하십시오.

with 
DateRange(FromDate, ToDate) as (
  select 
    Cast('11/1/2008' as DateTime), 
    Cast('2/15/2010' as DateTime)
),
Dates(Date) as (
  select 
    Case Day(FromDate) 
      When 1 Then FromDate
      Else DateAdd(month, 1, DateAdd(month, ((Year(FromDate)-1900)*12)+Month(FromDate)-1, 0))
    End
  from DateRange
  union all
  select DateAdd(month, 1, Date)
  from Dates
  where Date < (select ToDate from DateRange)
)
select 
  d.Date, t.TransactionValue
from Dates d
outer apply (
  select top 1 TransactionValue
  from Transactions
  where TransactionDate <= d.Date
  order by TransactionDate desc
) t
option (maxrecursion 120);

이 유형의 분석을 자주 수행하면이 목적을 위해 정리 한이 SQL 서버 기능에 관심이있을 수 있습니다.

if exists (select * from dbo.sysobjects where name = 'fn_daterange') drop function fn_daterange;
go

create function fn_daterange
   (
   @MinDate as datetime,
   @MaxDate as datetime,
   @intval  as datetime
   )
returns table
--**************************************************************************
-- Procedure: fn_daterange()
--    Author: Ron Savage
--      Date: 12/16/2008
--
-- Description:
-- This function takes a starting and ending date and an interval, then
-- returns a table of all the dates in that range at the specified interval.
--
-- Change History:
-- Date        Init. Description
-- 12/16/2008  RS    Created.
-- **************************************************************************
as
return
   WITH times (startdate, enddate, intervl) AS
      (
      SELECT @MinDate as startdate, @MinDate + @intval - .0000001 as enddate, @intval as intervl
         UNION ALL
      SELECT startdate + intervl as startdate, enddate + intervl as enddate, intervl as intervl
      FROM times
      WHERE startdate + intervl <= @MaxDate
      )
   select startdate, enddate from times;

go

이것에 대한 답이었습니다 의문, 또한 샘플 출력도 있습니다.

내 휴대 전화에서 Bol에 액세스 할 수 없으므로 이것은 거친 가이드입니다 ...

먼저, 데이터가없는 달 동안 누락 된 행을 생성해야합니다. 원하는 시간대가 있거나 프로그래밍 방식으로 생성 된 데이터 세트 (저장 Proc 또는 Rike)에서 외부 조인을 고정 테이블 또는 온도 테이블에 사용할 수 있습니다.

둘째, Max (value) Over (파티션 조항)와 같은 새로운 SQL 2008 'Analytic'함수를 살펴보고 이전 값을 얻을 수 있습니다.

(Oracle 이이 작업을 수행 할 수 있다는 것을 알고 있습니다. 왜냐하면 거래 날짜 사이에 복합이자 계산을 계산하기 위해 필요했기 때문입니다 - 실제로 동일한 문제)

이것이 올바른 방향으로 당신을 가리키길 바랍니다 ...

(온도 테이블에 던지고 그 위에 저주를 피하십시오. 너무 조잡한 !!!)

----- 대체 방법 ------

select 
    d.firstOfMonth,
    MONTH(d.firstOfMonth) as Mon,
    YEAR(d.firstOfMonth) as Yr, 
    t.TransactionValue
from (
    select 
        dateadd( month, inMonths - 1, '1/1/2009') as firstOfMonth 
        from (
            values (1), (2), (3), (4), (5), (7), (8), (9), (10), (11), (12)
        ) Dates(inMonths)
) d
outer apply (
    select top 1 TransactionValue
    from Transactions
    where TransactionDate <= d.firstOfMonth
    order by TransactionDate desc
) t
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top