最近遇到需要在不同類別各時間區段中,統計出每個類別最長不中斷的月數是多少。
資料大略如下圖:
因為程式老舊的關係所以只能在Stored Procedure上擴充,
雖然SQL也是可以寫迴圈來處理資料,但我盡量會避免在SQL上寫這種類似後端程式的處理。
因此花了點時間想該怎麼只靠SQL去整理出我要的資料。
最後終於寫出下面幾個SQL步驟來達到結果:
--1.為了使資料好處理,我首先就是從重疊區段中篩出關鍵點(起點、終點)資料
--找出日期接續起點和終點
SELECT RowNum =ROW_NUMBER() OVER (PARTITION BY CLASS ORDER BY DatePoint), *
INTO #TEMP
FROM
(
--接續起點
SELECT DISTINCT CLASS, DatePoint=STDT, PointType='SP' FROM TableA A
--條件1.日期起點不能在任何其他日期區段內,但起點相同時忽略(因為有可能發生長區間包短區間的狀況,ex:ID:1、2)
--因為是看日期接續,所以範圍會+1-1天來當條件
WHERE NOT EXISTS (SELECT TOP 1 1 FROM TableA WHERE ID<>A.ID AND CLASS=A.CLASS AND STDT<>A.STDT AND A.STDT BETWEEN STDT AND DATEADD(d,1, CONVERT(DATETIME, ENDT)))
--條件2.日期終點必須存在其他日期區段內
AND EXISTS (SELECT TOP 1 1 FROM TableA WHERE ID<>A.ID AND CLASS=A.CLASS AND A.ENDT BETWEEN DATEADD(d,-1, CONVERT(DATETIME, STDT)) AND ENDT)
UNION ALL
--接續終點(條件和起點相反)
SELECT DISTINCT CLASS, DatePoint=ENDT, PointType='EP' FROM TableA A
WHERE EXISTS (SELECT TOP 1 1 FROM TableA WHERE ID<>A.ID AND CLASS=A.CLASS AND A.STDT BETWEEN STDT AND DATEADD(d,1, CONVERT(DATETIME, ENDT)))
AND NOT EXISTS (SELECT TOP 1 1 FROM TableA WHERE ID<>A.ID AND CLASS=A.CLASS AND ENDT<>A.ENDT AND A.ENDT BETWEEN DATEADD(d,-1, CONVERT(DATETIME, STDT)) AND ENDT)
) A
上面SQL執行結果如下圖:
--2.將點連成線
--SP、EP連點計算
SELECT A.CLASS, ST=A.DatePoint, ED=B.DatePoint
, MouthCount= DATEDIFF(M, A.DatePoint, B.DatePoint)
--起終點必然成雙成對出現
FROM (SELECT * FROM #TEMP WHERE PointType='SP') A
LEFT JOIN (SELECT * FROM #TEMP WHERE PointType='EP') B ON A.CLASS=B.CLASS AND B.RowNum=A.RowNum+1
UNION ALL
--沒重疊區段也納入比較
SELECT A.CLASS, A.STDT, A.ENDT
, MouthCount= DATEDIFF(M, A.STDT, A.ENDT)
FROM TableA A
--起點不在任何區段內
WHERE NOT EXISTS (SELECT TOP 1 1 FROM TableA WHERE ID<>A.ID AND CLASS=A.CLASS AND A.STDT BETWEEN STDT AND DATEADD(d,1, CONVERT(DATETIME, ENDT)))
--終點也不在任何區段內
AND NOT EXISTS (SELECT TOP 1 1 FROM TableA WHERE ID<>A.ID AND CLASS=A.CLASS AND A.ENDT BETWEEN DATEADD(d,-1, CONVERT(DATETIME, STDT)) AND ENDT)
結果如下:
最後只要再透過MAX、GROUP BY處理,就可得到每個類別的最長不中斷月數。
結語:雖然之前有處理過類似的日期區間重疊問題(可參考這篇文章),但透過純SQL來處理倒是沒有,也算是不錯的經驗。
沒有留言:
張貼留言