-- 원본 데이터 CREATE TABLE TMP_MAS ( SEQ NUMBER , PRODUCT NUMBER , GUBUN VARCHAR2(10) , START_DT VARCHAR2(8) , END_DT VARCHAR2(8) , VALUE NUMBER ); INSERT INTO TMP_MAS (SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) VALUES (10, 10000, 'A', '20160516', '20160531', 40); INSERT INTO TMP_MAS (SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) VALUES (9, 10000, 'A', '20160501', '20160510', 30); INSERT INTO TMP_MAS (SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) VALUES (8, 10000, 'A', '20160501', '20160531', 20); INSERT INTO TMP_MAS (SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) VALUES (6, 10000, 'A', '20160501', '20160515', 30); INSERT INTO TMP_MAS (SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) VALUES (4, 10000, 'B', '20160501', '20160531', 45); INSERT INTO TMP_MAS (SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) VALUES (3, 10000, 'B', '20160510', '20160531', 70); INSERT INTO TMP_MAS (SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) VALUES (2, 10000, 'B', '20160501', '20160531', 71); COMMIT ; SELECT * FROM TMP_MAS; -- 결과 데이터 WITH RESULT AS ( SELECT 10 SEQ, 10000 PRODUCT, 'A' GUBUN, '20160516' START_DT, '20160531' END_DT, 40 VALUE, '20160516' RESULT_ST_DT, '20160531' RESULT_ED_DT FROM DUAL UNION ALL SELECT 9 SEQ, 10000 PRODUCT, 'A' GUBUN, '20160501' START_DT, '20160510' END_DT, 30 VALUE, '20160501' RESULT_ST_DT, '20160510' RESULT_ED_DT FROM DUAL UNION ALL SELECT 8 SEQ, 10000 PRODUCT, 'A' GUBUN, '20160501' START_DT, '20160531' END_DT, 20 VALUE, '20160511' RESULT_ST_DT, '20160515' RESULT_ED_DT FROM DUAL UNION ALL SELECT 4 SEQ, 10000 PRODUCT, 'B' GUBUN, '20160501' START_DT, '20160531' END_DT, 45 VALUE, '20160501' RESULT_ST_DT, '20160531' RESULT_ED_DT FROM DUAL ) SELECT * FROM RESULT ORDER BY PRODUCT ASC, GUBUN ASC, RESULT_ED_DT DESC ;
안녕하세요.
위 원본데이터에서 아래 결과 데이터를 출력하는 것이 목적입니다.
상당히 어려운 문제여서 고수님들의 도움을 기다립니다.
조건 1) 매월 1일부터 31일(매월 마지막)까지의 각각의 VALUE 값을 구하는 문제
조건 2) SEQ는 유니크하며 숫자가 클 수록 늦게 입력된 데이터
조건 3) SEQ가 큰 수부터 작은 수까지 중복되지 않은기간 사이의 VALUE를 구함
조건 4) 데이터가 중복인 경우 SEQ가 큰 ROW의 VALUE를 구함
조건 5) 1일 부터 31일까지 모든 기간의 데이터를 구하면 하위행(SEQ 가 작은 값)의 값은 버림
예) PRODUCT = 10000, GUBUN = A 인 경우
SEQ의 범위는 10 ~ 6까지이며 10부터 6까지 역순으로 값을 구해나감
SEQ = 10, 20160516 ~ 20160531 : 추출 O, 16일부터 31까지 VALUE = 40임
SEQ = 9, 20160501 ~ 20160510 : 추출 O, 01부터 10일까지 VALUE = 30임
SEQ = 8, 20160511 ~ 20160515 : 추출 O, 단, 포함관계인 01일~ 10일, 16일 ~ 31일은 SEQ=10, SEQ = 9에서 이미 구했으므로, 10일부터 15일까지의 VALUE = 20을 추출
SEQ = 6, : 추출 X, 01부터 31일까지의 모든 VALUE를 이미 추출하였음
추가설명 : 기간이 겹치는 경우, 하위행에서는 겹치는 날짜를 제거한 기간에 한하여 VALUE를 구합니다. SEQ = 8 의 기간은 SEQ= 10, SEQ=9에 의해서 01 ~ 10 / 16 ~ 31까지의 기간이 서로 겹칩니다. 이 경우 상위행에서 데이터가 추출되었으므로, 11 ~ 15기간동안만 VALUE = 20 으로 추출합니다.
예) PRODUCT = 10000, GUBUN = B 인 경우
SEQ의 범위는 4 ~ 2까지이며, 4부터 2까지 역순으로 값을 구해나감
SEQ = 4, 20160501 ~ 20160531 : 추출 O, 1일부터 31까지 VALUE = 45임
SEQ = 3 : 추출 X, 1일부터 31일까지 모든 VALUE를 이미 추출하였음
SEQ = 2 : 추출 X, 1일부터 31일까지 모든 VALUE를 이미 추출하였음
이게 최선인지는 장담하기 힘들지만 구해 지기는 하네요...ㅎ
WITH TMP_MAS(SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) AS ( SELECT 10, 10000, 'A', '20160516', '20160531', 40 FROM DUAL UNION ALL SELECT 9, 10000, 'A', '20160501', '20160510', 30 FROM DUAL UNION ALL SELECT 8, 10000, 'A', '20160501', '20160531', 20 FROM DUAL UNION ALL SELECT 6, 10000, 'A', '20160501', '20160515', 30 FROM DUAL UNION ALL SELECT 4, 10000, 'B', '20160501', '20160531', 45 FROM DUAL UNION ALL SELECT 3, 10000, 'B', '20160510', '20160531', 70 FROM DUAL UNION ALL SELECT 2, 10000, 'B', '20160501', '20160531', 71 FROM DUAL ) SELECT PRODUCT, GUBUN, MAX(SEQ) SEQ, MIN(TMP_DT) START_DT, MAX(TMP_DT) END_DT, VALUE FROM ( SELECT BB.PRODUCT, BB.GUBUN, AA.TMP_DT, BB.VALUE, BB.SEQ, ROW_NUMBER() OVER(PARTITION BY BB.PRODUCT, BB.GUBUN, AA.TMP_DT ORDER BY BB.SEQ DESC) RN FROM ( SELECT A.MON || LPAD(DAY, 2, '0') TMP_DT FROM ( SELECT SUBSTR(START_DT, 1, 6) MON FROM TMP_MAS GROUP BY SUBSTR(START_DT, 1, 6) ) A CROSS JOIN ( SELECT LEVEL DAY FROM DUAL CONNECT BY LEVEL <= 31 ) B ) AA INNER JOIN TMP_MAS BB ON ( AA.TMP_DT BETWEEN BB.START_DT AND BB.END_DT ) ) WHERE RN = 1 GROUP BY PRODUCT, GUBUN, VALUE
제가 value 가 같을 수도 있다는 부분을 간과 했네요 group by 부분에 seq 만 추가 하면 될듯 합니다
WITH TMP_MAS(SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE) AS ( SELECT 10, 10000, 'A', '20160516', '20160531', 40 FROM DUAL UNION ALL SELECT 9, 10000, 'A', '20160501', '20160510', 30 FROM DUAL UNION ALL SELECT 8, 10000, 'A', '20160501', '20160511', 20 FROM DUAL UNION ALL SELECT 6, 10000, 'A', '20160501', '20160515', 30 FROM DUAL UNION ALL SELECT 4, 10000, 'B', '20160501', '20160531', 45 FROM DUAL UNION ALL SELECT 3, 10000, 'B', '20160510', '20160531', 70 FROM DUAL UNION ALL SELECT 2, 10000, 'B', '20160501', '20160531', 71 FROM DUAL ) SELECT PRODUCT, GUBUN, SEQ, MIN(TMP_DT) START_DT, MAX(TMP_DT) END_DT, VALUE FROM ( SELECT BB.PRODUCT, BB.GUBUN, AA.TMP_DT, BB.VALUE, BB.SEQ, ROW_NUMBER() OVER(PARTITION BY BB.PRODUCT, BB.GUBUN, AA.TMP_DT ORDER BY BB.SEQ DESC, BB.VALUE) RN FROM ( SELECT A.MON || LPAD(DAY, 2, '0') TMP_DT FROM ( SELECT SUBSTR(START_DT, 1, 6) MON FROM TMP_MAS GROUP BY SUBSTR(START_DT, 1, 6) ) A CROSS JOIN ( SELECT LEVEL DAY FROM DUAL CONNECT BY LEVEL <= 31 ) B ) AA INNER JOIN TMP_MAS BB ON ( AA.TMP_DT BETWEEN BB.START_DT AND BB.END_DT ) ) WHERE RN = 1 GROUP BY PRODUCT, GUBUN, SEQ, VALUE ORDER BY PRODUCT, GUBUN, SEQ DESC
퇴근시간이라 막 만들다보니 과정이 엉망이고 비효율이 엄청나지만 답은 나옵니다만..
조금 수정하셔서 사용하시면 될 것 같기도 합니다;; 주말 잘보내세요~~
WITH T(SEQ,PRODUCT,GUBUN,START_DT,END_DT,VALUE,DT, RN, SEQNO) AS ( SELECT SEQ, PRODUCT, GUBUN, START_DT, END_DT, VALUE, DT, RN, 1 SEQNO FROM ( SELECT SEQ,PRODUCT,GUBUN,START_DT,END_DT,VALUE, TRUNC(TO_DATE(START_DT,'yyyymmdd'),'MM') + LV - 1 DT, RN FROM (SELECT SEQ,PRODUCT,GUBUN,START_DT,END_DT,VALUE , ROW_NUMBER() OVER(PARTITION BY PRODUCT, GUBUN ORDER BY SEQ DESC) RN FROM TMP_MAS) , (SELECT LEVEL LV FROM DUAL CONNECT BY LEVEL <= 31) WHERE RN = 1 ) WHERE DT BETWEEN TO_DATE(START_DT,'yyyymmdd') AND TO_DATE(END_DT,'yyyymmdd') UNION ALL SELECT B.SEQ, B.PRODUCT, B.GUBUN, B.START_DT, B.END_DT, B.VALUE, TRUNC(TO_DATE(B.START_DT,'yyyymmdd'),'MM') + LV - 1 , B.RN, ROW_NUMBER() OVER(PARTITION BY B.PRODUCT, B.GUBUN ORDER BY B.SEQ) SEQNO FROM T A , (SELECT SEQ,PRODUCT,GUBUN,START_DT,END_DT,VALUE , ROW_NUMBER() OVER(PARTITION BY PRODUCT, GUBUN ORDER BY SEQ DESC) RN FROM TMP_MAS) B , (SELECT LEVEL LV FROM DUAL CONNECT BY LEVEL <= 31) WHERE A.PRODUCT = B.PRODUCT AND A.GUBUN = B.GUBUN AND A.RN + 1 = B.RN AND A.SEQNO = 1 AND TRUNC(TO_DATE(B.START_DT,'yyyymmdd'),'MM') + LV - 1 BETWEEN B.START_DT AND B.END_DT AND NOT EXISTS (SELECT 1 FROM TMP_MAS C WHERE PRODUCT = A.PRODUCT AND GUBUN = A.GUBUN AND SEQ > B.SEQ AND TRUNC(TO_DATE(B.START_DT,'yyyymmdd'),'MM') + LV - 1 BETWEEN C.START_DT AND C.END_DT) ) SELECT MAX(SEQ) SEQ, PRODUCT, GUBUN, MAX(START_DT) START_DT, MAX(END_DT) END_DT, MAX(VALUE) VALUE , TO_CHAR(MIN(DT),'YYYYMMDD') RESULT_ST_DT , TO_CHAR(MAX(DT),'YYYYMMDD') RESULT_ED_DT FROM T GROUP BY PRODUCT, GUBUN, RN ORDER BY PRODUCT ASC, GUBUN ASC, TO_NUMBER(SEQ) DESC
월욜 출근해서 보니 다시 봐도 엄청 비효율적이네요ㅋ 재귀 구문에서 group by가 안 되는군요.
group by 해서 min, max 만들면서 가면 좀 효율적일 것 같았는데.. Kyle님 답변달아주신 게 훨씬 효율적이네요^^
추가)비효율적이고 한눈에 들어오지도 않지만 비효율을 조금 줄이고 SEQ 누락에 대한 처리를 했습니다^^; 역시 쿼리 짤 때는 처음에 방향이 중요하네요. 재귀해보자 생각한 게 잘못이네요ㅋ
위 답변을 달아주신 Kyle님과 마찬가지로,
SEQ=8 을 2016-05-01 ~ 2016-0511으로 변경할 경우 결과가 아래와 같이 나와야 하는데 다르게 나옵니다.
쉬운 쿼리가 아니라서 저도 아직 해결을 못했습니다.
프로시저로 해봐야 하는 건지 고민이 되는 군요
seq=10, 2016-05-16 ~ 2016-05-31, 40
seq=9, 2016-05-01 ~ 2016-05-10, 30
seq=8, 2016-05-11 ~ 2016-05-11, 20
seq=6, 2016-05-12 ~ 2016-05-15, 30
seq=4, 2016-05-01 ~ 2016-05-31, 45
WITH tmp_mas(seq, product, gubun, start_dt, end_dt, value) AS ( SELECT 10, 10000, 'A', '20160516', '20160531', 40 FROM dual UNION ALL SELECT 9, 10000, 'A', '20160501', '20160510', 30 FROM dual UNION ALL SELECT 8, 10000, 'A', '20160501', '20160511', 20 FROM dual UNION ALL SELECT 6, 10000, 'A', '20160501', '20160515', 30 FROM dual UNION ALL SELECT 4, 10000, 'B', '20160501', '20160531', 45 FROM dual UNION ALL SELECT 3, 10000, 'B', '20160510', '20160531', 70 FROM dual UNION ALL SELECT 2, 10000, 'B', '20160501', '20160531', 71 FROM dual ) , tmp_cal AS ( SELECT TO_CHAR(dt + LEVEL - 1, 'yyyymmdd') dt FROM (SELECT TO_DATE('201605', 'yyyymm') dt FROM dual) CONNECT BY LEVEL <= LAST_DAY(dt) - dt + 1 ) SELECT seq, product, gubun, v , MIN(dt) s , MAX(dt) e FROM (SELECT product, gubun, dt , MAX(seq) seq , MAX(value) KEEP(DENSE_RANK LAST ORDER BY seq) v , ROW_NUMBER() OVER(PARTITION BY product, gubun ORDER BY dt) rn1 , ROW_NUMBER() OVER(PARTITION BY product, gubun, MAX(seq) ORDER BY dt) rn2 FROM tmp_mas a , tmp_cal b WHERE b.dt BETWEEN a.start_dt AND a.end_dt GROUP BY product, gubun, dt ) GROUP BY product, gubun, seq, v, rn1-rn2 ORDER BY product, gubun, seq DESC ;