2015-01-22 2 views
1

환경 : SQL Azure - 일부 이국적인 기능이 다소 제한적입니다.그룹화 된 행간 경과 일 수

자산에 물류 이벤트를 기록하는 테이블이 있는데이 테이블에서 자산이 시설에 있었던 일 수를 계산하고 싶습니다. 아래 표의 예를 참조하십시오.

AssetID LocationID SublocationID MoveDate 
CAR1 LOC1  SUB1   1/1/2015 01:01:01 
CAR1 LOC1  SUB2   1/3/2015 03:03:03 
CAR1 LOC1  SUB1   1/4/2015 04:04:04 
CAR1 LOC99  SUB99   1/5/2015 05:05:05 
CAR1 LOC1  SUB1   1/9/2015 09:09:09 

이 테이블은 위치/하위 위치에서 다른 위치로 이동하는 것을 기록합니다. 하위 위치는 신경 쓰지 않습니다. 자산이 각 위치에 몇 일 동안 있었는지보고해야합니다. 이 빨리 LOC1에서 자산 이동 LOC2 다시 LOC1을하는 것을 볼 수있는 데이터의 함정을 밝혀, 그러나

SELECT AssetID, 
     LocationID, 
     DATEDIFF(DAY, MIN(MoveDate), MAX(MoveDate)) 
FROM TABLE 
GROUP BY AssetID, LocationID 

: 처음에 나는이 길을 갔다. 2011 년 1 월 1 일부터 2015 년 1 월 9 일까지 LOC1의 모든 요일을 계산할 때 실제로는 LOC99에서 1/5와 1/9 사이의 시간을 보냈습니다.

이 작업을 수행하기위한 순수한 SQL 방법이 있습니까?

+0

LAG 창 기능을 사용하여이 작업을 수행 할 수 있지만 Azure 데이터베이스에서 지원되는지 여부는 알 수 없습니다. 그렇지 않으면 CTE를 사용해야하거나 하위 쿼리 –

+0

명을 수행해야 할 수도 있습니다. 도움을 많이 주셔서 감사합니다. 불행히도 SQL Azure에는 LEAD 또는 LAG 기능이 없습니다. 간단한 조인은 트릭을합니다 !!! –

+0

다음 질문은 1/6/2015-1/8/2015 기간에 대해 해당 자산이 각 위치에 머무는 기간을 물음표에 추가하는 방법을 파악하는 것입니다. 나는 그것을 파악하기 위해 저장된 proc 파일로 파열해야 할 수도 있습니다. –

답변

0

커서를 FAST_FORWARD 커서를 사용하여 날짜 순서대로 테이블을 반복하고 결과 테이블을 임시 테이블에 작성하십시오.

LEAD 또는 LAG으로 수행 할 수 있지만 Azure에서는 사용할 수 없습니다. 어떤 종류의 비 - 커서 T-SQL 솔루션은 의심의 여지가 없지만 성능이 커서보다 좋을지는 의심 스럽습니다. FAST_FORWARD이있는 커서는 일반적으로 상관 하위 쿼리가 포함 된 쿼리보다 성능이 좋습니다.

0

은 다음과 비슷한 모습이 될 것입니다

SELECT [details].[AssetId], 
     [details].[LocationId], 
     DATEDIFF(DAY, MIN([details].[MovedInDate]), [details].[MoveOutDate]) AS DaysIn 
FROM (
    SELECT DISTINCT movedInRow.[AssetId], [movedInRow].[LocationId], [movedInRow].[MoveDate] AS MovedInDate, ISNULL(nm.[MoveDate], GETDATE()) AS MoveOutDate 
    FROM [dbo].[t1] movedInRow 
     OUTER APPLY (
      SELECT TOP 1 [MoveDate] 
      FROM [dbo].[t1] 
      WHERE 
       [AssetId] = movedInRow.[AssetId] 
       AND [LocationId] != movedInRow.[LocationId] 
       AND [MoveDate] >= [movedInRow].[MoveDate] 
       ORDER BY [MoveDate] DESC 
     ) nm 
) AS details 
GROUP BY 
    [details].[AssetId], 
    [details].[LocationId], 
    [details].[MoveOutDate]; 

2 곳의 MOVEDATE이 어떤 이유에 대해 동일합니다,이 예제는 그 가능성을 확인하지 않을 가능성이있을 수 있습니다.

(LEAD 또는 LAG 같은) 윈도우 기능을 사용하지 않고
AssetId LocationId DaysIn 
CAR1 LOC1  4 
CAR1 LOC1  13 
CAR1 LOC99  4 
+0

나는이 수학이 옳다고 생각하지 않는다. – briskovich

+0

입니다. MoveDate 열에 항목을 옮긴 날짜가 들어 있다고 가정하면 처음 LOC1 위치에서 4 일, LOC 99에서 4 일을 보냈고, 오늘까지는 LOC1에 여전히 앉아 있습니다 (2015 년 1 월 22 일). 13 일입니다. –

0

및 코딩 모든 T-SQL은 당신은 재귀 CTE 사용하여 작업을 얻을 수없는 :

/*Create table and sample data*/ 

create table #mov (
    AssetID varchar(10), 
    LocationID varchar(10), 
    SublocationID varchar(10), 
    MoveDate datetime 
) 

insert into #mov 
select 'CAR1', 'LOC1',  'SUB1',   '1/1/2015 01:01:01' union all 
select 'CAR1', 'LOC1',  'SUB2',   '1/3/2015 03:03:03' union all 
select 'CAR1', 'LOC1',  'SUB1',   '1/4/2015 04:04:04' union all 
select 'CAR1', 'LOC99',  'SUB99',   '1/5/2015 05:05:05' union all 
select 'CAR1', 'LOC1',  'SUB1' ,   '1/9/2015 09:09:09' union all 
select 'CAR2', 'LOC1',  'SUB1',   '1/1/2015 01:01:01' union all 
select 'CAR2', 'LOC1',  'SUB2',   '1/3/2015 03:03:03' union all 
select 'CAR2', 'LOC1',  'SUB1',   '1/4/2015 04:04:04' union all 
select 'CAR2', 'LOC99',  'SUB99',   '1/5/2015 05:05:05' union all 
select 'CAR2', 'LOC1',  'SUB1' ,   '1/9/2015 09:09:09' 

/*Create CTEs*/ 
/*1. cteMov - adds the row number to the dataset*/ 
;with cteMov as (
    select AssetID, LocationID, MoveDate, row_number() over(partition by AssetID order by MoveDate) as rn 
    from #mov 
), 
/*recursive CTE to get records groups*/ 
rec as (
    select AssetID, LocationID, MoveDate, rn, 1 as rnk 
    from cteMov 
    where rn = 1 
    union all 
    select c.AssetID, c.LocationID, c.MoveDate, c.rn, case when c.LocationID = rec.LocationID then rec.rnk else rec.rnk + 1 end as rnk 
    from cteMov as c 
    join rec on c.AssetID = rec.AssetID and c.rn = rec.rn + 1 
) 
/*3. Final query*/ 
select 
    rec1.AssetID, rec1.LocationID, 
    datediff(dd, min(rec1.MoveDate), isnull(max(rec2.MoveDate), getdate())) as DaysSpent, 
    rec1.rnk 
from rec as rec1 
left join rec as rec2 on rec1.rnk = rec2.rnk - 1 
group by rec1.AssetID, rec1.LocationID, rec1.rnk 
order by rec1.AssetID, rec1.rnk 
option(MAXRECURSION 0) 

/*drop temp table */ 
drop table #mov 

결과는 다음과 같습니다

AssetID LocationID DaysSpent rnk 
---------- ---------- ----------- ----------- 
CAR1  LOC1  4   1 
CAR1  LOC99  4   2 
CAR1  LOC1  13   3 
CAR2  LOC1  4   1 
CAR2  LOC99  4   2 
CAR2  LOC1  13   3 
0

이전 응답의 샘플 데이터 사용 :

create table t1 (
    AssetID varchar(10), 
    LocationID varchar(10), 
    SublocationID varchar(10), 
    MoveDate datetime 
); 


insert into t1 
select 'CAR1', 'LOC1',  'SUB1',   '1/1/2015 01:01:01' union all 
select 'CAR1', 'LOC1',  'SUB2',   '1/3/2015 03:03:03' union all 
select 'CAR1', 'LOC1',  'SUB1',   '1/4/2015 04:04:04' union all 
select 'CAR1', 'LOC99',  'SUB99',   '1/5/2015 05:05:05' union all 
select 'CAR1', 'LOC1',  'SUB1' ,   '1/9/2015 09:09:09' union all 
select 'CAR2', 'LOC1',  'SUB1',   '1/1/2015 01:01:01' union all 
select 'CAR2', 'LOC1',  'SUB2',   '1/3/2015 03:03:03' union all 
select 'CAR2', 'LOC1',  'SUB1',   '1/4/2015 04:04:04' union all 
select 'CAR2', 'LOC99',  'SUB99',   '1/5/2015 05:05:05' union all 
select 'CAR2', 'LOC1',  'SUB1' ,   '1/9/2015 09:09:09'; 

select * from t1; 

╔═════════╦════════════╦═══════════════╦════════════════════════════════╗ 
║ ASSETID ║ LOCATIONID ║ SUBLOCATIONID ║   MOVEDATE   ║ 
╠═════════╬════════════╬═══════════════╬════════════════════════════════╣ 
║ CAR1 ║ LOC1  ║ SUB1   ║ January, 01 2015 01:01:01+0000 ║ 
║ CAR2 ║ LOC1  ║ SUB1   ║ January, 01 2015 01:01:01+0000 ║ 
║ CAR2 ║ LOC1  ║ SUB2   ║ January, 03 2015 03:03:03+0000 ║ 
║ CAR1 ║ LOC1  ║ SUB2   ║ January, 03 2015 03:03:03+0000 ║ 
║ CAR1 ║ LOC1  ║ SUB1   ║ January, 04 2015 04:04:04+0000 ║ 
║ CAR2 ║ LOC1  ║ SUB1   ║ January, 04 2015 04:04:04+0000 ║ 
║ CAR2 ║ LOC99  ║ SUB99   ║ January, 05 2015 05:05:05+0000 ║ 
║ CAR1 ║ LOC99  ║ SUB99   ║ January, 05 2015 05:05:05+0000 ║ 
║ CAR1 ║ LOC1  ║ SUB1   ║ January, 09 2015 09:09:09+0000 ║ 
║ CAR2 ║ LOC1  ║ SUB1   ║ January, 09 2015 09:09:09+0000 ║ 
╚═════════╩════════════╩═══════════════╩════════════════════════════════╝  

리드() 분석 기능이 지원되는 경우, (단순성과 성능면에서 모두) 바람직한 해결책은 다음과 같습니다

select AssetID, LocationID, 
     sum(datediff(dd,MoveDate,isnull(nextMoveDate,getDate()))) daysAtLoc 
from (
    select AssetID, LocationID, MoveDate, 
      lead(MoveDate) over (partition by AssetID 
           order by MoveDate) nextMoveDate 
    from t1 
    ) t2 
group by AssetID, LocationID 
order by AssetID, LocationID; 

╔═════════╦════════════╦═══════════╗ 
║ ASSETID ║ LOCATIONID ║ DAYSATLOC ║ 
╠═════════╬════════════╬═══════════╣ 
║ CAR1 ║ LOC1  ║  18 ║ 
║ CAR1 ║ LOC99  ║   4 ║ 
║ CAR2 ║ LOC1  ║  18 ║ 
║ CAR2 ║ LOC99  ║   4 ║ 
╚═════════╩════════════╩═══════════╝ 

순수한 SQL 솔루션 : 어떤 분석, 아니 재귀 CTE, 더 OUTER가 적용되지 않습니다/correlated-subqueries; 단순한 조인. 나는 Azure-SQL으로 일한 적이 없지만, 이것이 지원되지 않는다면 (또한 여전히 SQL이라고 불린다) 꽤 놀랄 것이다.

select AssetID, LocationID, 
     sum(datediff(dd,MoveDate,isnull(nextMoveDate,getdate()))) daysAtLoc 
from (
    select t1.AssetID, LocationID, MoveDate, 
      min(nextMoveDate) nextMoveDate 
    from t1 
      left outer join 
       (select AssetID, MoveDate nextMoveDate 
       from t1) n 
       on t1.AssetId = n.AssetID 
        and MoveDate < nextMoveDate) 
    group by t1.AssetID, LocationID, MoveDate 
    ) t2 
group by AssetID, LocationID 
order by AssetID, LocationID 


╔═════════╦════════════╦═══════════╗ 
║ ASSETID ║ LOCATIONID ║ DAYSATLOC ║ 
╠═════════╬════════════╬═══════════╣ 
║ CAR1 ║ LOC1  ║  18 ║ 
║ CAR1 ║ LOC99  ║   4 ║ 
║ CAR2 ║ LOC1  ║  18 ║ 
║ CAR2 ║ LOC99  ║   4 ║ 
╚═════════╩════════════╩═══════════╝ 

성능 경고 - n은 자산 당 최대 이동 수이고, m은 자산 수입니다. 분석 함수 버전은 m * (n log n)의 Big-O 성능을 가져야합니다. pure-SQL 버전은 Big *가 m * (n * n)이어야합니다.따라서 일정한 자산 풀을 추적하면서 시간이 지남에 따라 움직임이 점점 더 많아지면 (자산 당 이동 수가 꾸준히 증가하게 됨) 쿼리가 기하 급수적으로 느려집니다. 오랜 기간 동안 쿼리를 수행하고 개별 자산에 대해 수십 또는 수천 개의 동작을 기록하는 경우 월간 배치로 계산 한 다음 그 결과를 합산해야 할 수 있습니다. 즉, 엄청난 양의 자산이 있고 각 인스턴스의 움직임이 상대적으로 적다면 pure-SQL 버전은 Analytic Function 버전과 함께 수행해야합니다.

- EDIT 1 : 오리지널 SQL 용액에 고정 오타 (추가 괄호)

- EDIT 2 일자 범위를 지원할 용액 확장이 - 또한 용액의 안정성을 확인하기 위해 입력 데이터를 조금 불통.

create table t1 (
    AssetID varchar(10), 
    LocationID varchar(10), 
    SublocationID varchar(10), 
    MoveDate datetime, 
    primary key (AssetId, MoveDate)); 

insert into t1 
select 'CAR1', 'LOC1', 'SUB1', '01/01/2015 00:00:00' union 
select 'CAR1', 'LOC1', 'SUB2', '01/03/2015 03:03:03' union 
select 'CAR1', 'LOC1', 'SUB1', '01/04/2015 04:04:04' union 
select 'CAR1', 'LOC99', 'SUB99', '01/05/2015 05:05:05' union 
select 'CAR1', 'LOC1', 'SUB1' , '01/09/2015 09:09:09' union 
select 'CAR2', 'LOC1', 'SUB2', '01/03/2015 03:03:03' union 
select 'CAR2', 'LOC1', 'SUB1', '01/04/2015 04:04:04' union 
select 'CAR2', 'LOC99', 'SUB99', '01/05/2015 05:05:05' union 
select 'CAR2', 'LOC1', 'SUB1' , '01/09/2015 09:09:09' union 
select 'CAR3', 'LOC2', 'SUB1' , '01/15/2015 15:15:15' 
; 

╔═════════╦════════════╦═══════════════╦════════════════════════════════╗ 
║ ASSETID ║ LOCATIONID ║ SUBLOCATIONID ║   MOVEDATE   ║ 
╠═════════╬════════════╬═══════════════╬════════════════════════════════╣ 
║ CAR1 ║ LOC1  ║ SUB1   ║ January, 01 2015 00:00:00+0000 ║ 
║ CAR1 ║ LOC1  ║ SUB2   ║ January, 03 2015 03:03:03+0000 ║ 
║ CAR1 ║ LOC1  ║ SUB1   ║ January, 04 2015 04:04:04+0000 ║ 
║ CAR1 ║ LOC99  ║ SUB99   ║ January, 05 2015 05:05:05+0000 ║ 
║ CAR1 ║ LOC1  ║ SUB1   ║ January, 09 2015 09:09:09+0000 ║ 
║ CAR2 ║ LOC1  ║ SUB2   ║ January, 03 2015 03:03:03+0000 ║ 
║ CAR2 ║ LOC1  ║ SUB1   ║ January, 04 2015 04:04:04+0000 ║ 
║ CAR2 ║ LOC99  ║ SUB99   ║ January, 05 2015 05:05:05+0000 ║ 
║ CAR2 ║ LOC1  ║ SUB1   ║ January, 09 2015 09:09:09+0000 ║ 
║ CAR3 ║ LOC2  ║ SUB1   ║ January, 15 2015 15:15:15+0000 ║ 
╚═════════╩════════════╩═══════════════╩════════════════════════════════╝ 

은 물론, 당신은 그냥 동시에 조건의 다양성을 테스트하기 위해 그렇게하고 있어요 dt_ranges에 대한 테이블을 사용할 필요가 없습니다. 나는 [currentstart, nextstart]의 관점에서 날짜 범위를 작업하는 편이 낫다. 예를 들어, 겹치지 않을 SQL을 작성하는 것이 훨씬 쉬워진다. 월별 보고서.

create table dt_range 
    (thisStartDate date, 
    nextStartDate date, 
    primary key (thisStartDate,nextStartDate)); 

insert into dt_range 
select '01-dec-2014','01-jan-2015' union 
select '01-jan-2015','01-feb-2015' union 
select '02-jan-2015','09-jan-2015' union 
select '01-feb-2015','01-mar-2015' ; 

╔═══════════════╦═══════════════╗ 
║ THISSTARTDATE ║ NEXTSTARTDATE ║ 
╠═══════════════╬═══════════════╣ 
║ 2014-12-01 ║ 2015-01-01 ║ 
║ 2015-01-01 ║ 2015-02-01 ║ 
║ 2015-01-02 ║ 2015-01-09 ║ 
║ 2015-02-01 ║ 2015-03-01 ║ 
╚═══════════════╩═══════════════╝ 

그리고 쿼리 :

select thisStartDate, nextStartDate, t.AssetID, ArrivalLocation, 
     round(sum(datediff(ss,ArrivalTime, DepartureTime))/(24.0*60*60),1) DaysAtLoc 
from (  
select thisStartDate, nextStartDate, t.AssetID, ArrivalLocation, ArrivalTime, 
     coalesce(min(MoveDate),nextStartDate) DepartureTime 
from (
select assetsInRange.thisStartDate, assetsInRange.nextStartDate, assetsInRange.assetID, 
     coalesce(ArrivalLocation,InitialLocation) ArrivalLocation, 
     coalesce(ArrivalTime,assetsInRange.thisStartDate) ArrivalTime 
from 
     (
     select thisStartDate, nextStartDate, assetID 
     from dt_range 
       join t1 on MoveDate < nextStartDate 
     group by thisStartDate, nextStartDate, assetID   
    ) assetsInRange 
    left outer join 
     (
     select thisStartDate, nextStartDate, assetID, 
       max(MoveDate) precedingDtRangeMoveDt 
     from dt_range 
       join t1 
        on MoveDate < thisStartDate 
     group by thisStartDate, nextStartDate, assetID 
    ) 
     precedingMoveDt 
     on (assetsInRange.assetID = precedingMoveDt.assetID) 
    left outer join 
     (
     select AssetID, MoveDate precedingDtRangeMoveDt, LocationID initialLocation 
     from t1 
    ) 
     precedingMoveLoc 
     on (precedingMoveDt.assetID = precedingMoveLoc.AssetID 
      and precedingMoveDt.precedingDtRangeMoveDt = precedingMoveLoc.precedingDtRangeMoveDt) 
    left outer join 
     (
     select AssetId, LocationId ArrivalLocation, MoveDate ArrivalTime 
     from t1 
    ) 
     arrivals 
     on assetsInRange.AssetID = arrivals.AssetId 
       and ArrivalTime >= assetsInRange.thisStartDate 
       and ArrivalTime < assetsInRange.nextStartDate 
    group by assetsInRange.thisStartDate, assetsInRange.nextStartDate, assetsInRange.AssetId, 
      coalesce(ArrivalLocation,InitialLocation) , 
      coalesce(ArrivalTime,assetsInRange.thisStartDate) 
) t 
left join t1 on t.assetID = t1.assetID 
      and t1.MoveDate > ArrivalTime 
      and t1.MoveDate < nextStartDate 
group by thisStartDate, nextStartDate, t.AssetID, ArrivalLocation, ArrivalTime 
) t 
group by thisStartDate, nextStartDate, t.AssetID, ArrivalLocation 
order by 1, 3; 

그리고 결과 :

╔═══════════════╦═══════════════╦═════════╦═════════════════╦═══════════╗ 
║ THISSTARTDATE ║ NEXTSTARTDATE ║ ASSETID ║ ARRIVALLOCATION ║ DAYSATLOC ║ 
╠═══════════════╬═══════════════╬═════════╬═════════════════╬═══════════╣ 
║ 2015-01-01 ║ 2015-02-01 ║ CAR1 ║ LOC1   ║ 26.8  ║ 
║ 2015-01-01 ║ 2015-02-01 ║ CAR1 ║ LOC99   ║ 4.2  ║ 
║ 2015-01-01 ║ 2015-02-01 ║ CAR2 ║ LOC1   ║ 24.7  ║ 
║ 2015-01-01 ║ 2015-02-01 ║ CAR2 ║ LOC99   ║ 4.2  ║ 
║ 2015-01-01 ║ 2015-02-01 ║ CAR3 ║ LOC2   ║ 16.4  ║ 
║ 2015-01-02 ║ 2015-01-09 ║ CAR1 ║ LOC1   ║ 2.1  ║ 
║ 2015-01-02 ║ 2015-01-09 ║ CAR1 ║ LOC99   ║ 3.8  ║ 
║ 2015-01-02 ║ 2015-01-09 ║ CAR2 ║ LOC1   ║ 2.1  ║ 
║ 2015-01-02 ║ 2015-01-09 ║ CAR2 ║ LOC99   ║ 3.8  ║ 
║ 2015-02-01 ║ 2015-03-01 ║ CAR1 ║ LOC1   ║ 28  ║ 
║ 2015-02-01 ║ 2015-03-01 ║ CAR2 ║ LOC1   ║ 28  ║ 
║ 2015-02-01 ║ 2015-03-01 ║ CAR3 ║ LOC2   ║ 28  ║ 
╚═══════════════╩═══════════════╩═════════╩═════════════════╩═══════════╝  

주 - 나는 자산에 대한 최초의 기록은 이전에 어떤 위치에 존재하지 않는 나타냅니다 가정 .. 따라서 2014 년 이전 날짜가있는 자산이 없으므로 2014 년 12 월 -18 월 테스트 월이 결과에 표시되지 않습니다.

+0

순수한 SQL 솔루션을 좋아합니다. 고마워! 날짜 범위를 갖도록 수정하는 방법이 있습니까? 그래서 두 날짜 사이에 자산이 어디에 있었는지, 심지어 그 범위 동안 이동 이벤트가 없었더라도 ... 나는 이것을 위해 proc를 저장해야 할 것이라고 생각하고 있습니다 ... 다시 한번 감사드립니다! –

+0

예 - 서식을 쉽게 사용할 수 있도록 기본 답변에 추가하겠습니다. – KevinKirkpatrick