쿼리 질문 드립니다.(시작일과 종료일이 있는 데이터간 비는 기간 생성하기) 0 3 1,172

by 아이쪼아라 [SQL Query] [2015.12.03 22:37:03]


안녕하세요.

몇일 쿼리로 고생하다가 질문 드립니다.

아래 데이터를 기초로

No 상품명 시작일 종료일 금액
1 A 20150916 20150922 4000
2 A 20150918 20150918 8000
3 A 20150923 99991231 7000
4 A 20151014 20151014 3000

다음과 같은 데이터를 만들어야 합니다.

No 상품명 시작일 종료일 금액
1 A 20150916 20150917 4000
2 A 20150918 20150918 8000
3 A 20150919 20150922 4000
4 A 20150923 20151013 7000
5 A 20151014 20151014 3000
6 A 20151015 99991231 7000

예를 들어 1번 행과 같이 9/16 ~ 9/22 데이터가 있고

2번 9/18 ~ 9/18 데이터가 있다면

2번이 1번 시작일 ~ 종료일에 포함되는 데이터 이기 때문에

9/16 ~ 9/17, 9/18 ~ 9/18, 9/19 ~ 9/22 이렇게 3개행으로 쪼개져야 합니다.

 

설명이 좀 부족한것 같다는 생각이 듭니다만.... 표현이 잘 안되네요 ㅠ.ㅠ

temp테이블을 이용하거나, 프로시저로 작성해되 되는데

어떻게 해야할지 감이 안잡히네요...

비슷한 경우를 해결해 보신 분이 계시면 도움 부탁드립니다.

 

by 마농 [2015.12.04 13:31:28]

비슷한 유형의 문제를 풀어본적이 있습니다.
http://www.gurubee.net/lecture/2848
그런데 요구조건이 조금 다르네요.
같은 방식으로 풀기는 어려울것 같아,
날짜별로 분리 한뒤 다시 그룹바이 하는 방식으로 풀어봤습니다.
99991231 에 대한 부담으로 20201231 로 변경하여 처리했습니다.
 

WITH t AS
(
SELECT 1 no, 'A' cd, '20150916' sdt, '20150922' edt, 4000 amt FROM dual
UNION ALL SELECT 2, 'A', '20150918', '20150918', 8000 FROM dual
UNION ALL SELECT 3, 'A', '20150923', '99991231', 7000 FROM dual
UNION ALL SELECT 4, 'A', '20151014', '20151014', 3000 FROM dual
UNION ALL SELECT 5, 'A', '20151114', '20151116', 5000 FROM dual
)
SELECT cd
     , TO_CHAR(MIN(dt), 'yyyymmdd') sdt
     , REPLACE(TO_CHAR(MAX(dt), 'yyyymmdd'), '20201231', '99991231') edt
     , amt
  FROM (SELECT cd, dt, no, amt
             , dt - ROW_NUMBER() OVER(PARTITION BY cd, no, amt ORDER BY dt) grp
          FROM (SELECT cd
                     , sdt + lv - 1 dt
                     , MAX(no) no
                     , MAX(amt) KEEP(DENSE_RANK LAST ORDER BY no) amt
                  FROM (SELECT no, cd, amt
                             , TO_DATE(sdt, 'yyyymmdd') sdt
                             , TO_DATE(DECODE(edt, '99991231', '20201231', edt), 'yyyymmdd') edt
                          FROM t
                        )
                     , (SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 9999)
                 WHERE lv <= edt - sdt + 1
                 GROUP BY cd, sdt + lv - 1
                )
        )
 GROUP BY cd, grp, no, amt
 ORDER BY cd, sdt
;

 


by 아이쪼아라 [2015.12.04 13:40:52]

정말 감사합니다~!!!

아직 원본 데이터에 적용하진 않았지만, 답변하신 예문보고 공부하겠습니다.


by 마농 [2015.12.04 16:05:52]

http://www.gurubee.net/lecture/2848
링크의 방법(선분이력을 점이력으로 변경하여 처리하는 방법)을 적용해 봤습니다.
이 방법을 적용하기 위해 원본데이터를 적용가능한 형태(tmp)로 우선 가공했습니다.

WITH t AS
(
SELECT 1 no, 'A' cd, '20150916' sdt, '20150922' edt, 4000 amt FROM dual
UNION ALL SELECT 2, 'A', '20150918', '20150918', 8000 FROM dual
UNION ALL SELECT 3, 'A', '20150923', '99991231', 7000 FROM dual
UNION ALL SELECT 4, 'A', '20151014', '20151014', 3000 FROM dual
UNION ALL SELECT 5, 'A', '20151114', '20151119', 5000 FROM dual
UNION ALL SELECT 6, 'A', '20151115', '20151115', 6000 FROM dual
)
, tmp AS
(
SELECT a.no, a.cd
     , TO_DATE(a.sdt, 'yyyymmdd') sdt
     , TO_DATE(a.edt, 'yyyymmdd') edt
     , a.amt - NVL(MAX(b.amt) KEEP(DENSE_RANK LAST ORDER BY b.no), 0) amt
  FROM t a
     , t b
 WHERE a.cd = b.cd(+)
   AND a.sdt > b.sdt(+)
   AND a.sdt < b.edt(+)
 GROUP BY a.no, a.cd, a.sdt, a.edt, a.amt
)
SELECT cd
     , TO_CHAR(sdt, 'yyyymmdd') sdt
     , NVL(TO_CHAR(edt, 'yyyymmdd'), '99991231') edt
     , amt
  FROM (SELECT cd, sdt
             , LEAD(sdt - 1) OVER(ORDER BY sdt) edt
             , SUM(SUM(amt)) OVER(ORDER BY sdt) amt
          FROM (SELECT cd
                     , DECODE(lv, 1, sdt, edt + 1) sdt
                     , DECODE(lv, 1, amt, -amt) amt
                  FROM tmp
                     , (SELECT LEVEL lv FROM dual CONNECT BY LEVEL <= 2)
                 WHERE lv = 1 OR edt != '99991231'
                )
         GROUP BY cd, sdt
        )
 WHERE amt != 0
;

 

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입