SQLP 실기 기출문제 풀이(44회)
※ 시험후기들을 참고해 복기한것으로 틀린부분이 있을 수 있음.
※ 실제 시험에는 ERD와 ROW SOURCE GENERATION 정보가 같이 나왔음.
(실기1) 아래와 같이 테이블구조가 있는데 기존SQL를 개선하고, 필요시 인덱스를 설계변경 할 것
불필요하게 인덱스 변경시 감점
[테이블 정보]
* BOARD : #BRD_ID, BRD_NM, ...
* ARTICLE : #ART_SN, ART_TIL, ART_CONT, MBR_NO, BRD_ID, REG_DT, ...
* MEMBER : #MBR_NO, MBR_NM, ...
[인덱스]
BOARD_PK : BRD_ID
ARTICLE_PK : ART_SN
MEMBER_PK : MBR_NO
ARTICLE_X01 : BRD_ID
ARTICLE_X02 : MBR_NO
[정보]
BOARD 테이블 : 10건
최근 3일 또는 5일전 게시글 : 850여 건
MEMBER 테이블 : 5000건(?)
[기존SQL]
SELECT
*
FROM
(SELECT
B.BRD_ID
, B.BRD_NM
, A.ART_SN
, A.ART_TIL
, GET_TEXT(A.ART_CONT,100) AS ART_CONT
, A.MBR_NO
, M.MBR_NM
, A.REG_DT
FROM BOARD B, ARTICLE A, MEMBER M
WHERE B.BRD_ID = :BRD_ID
AND A.BRD_ID = B.BRD_ID
AND A.MBR_NO = M.MBR_NO
AND A.REG_DT >= TRUNC(SYSDATE,-3)
ORDER BY A.REG_DT DESC
)
WHERE ROWNUM <= 5;
· 인덱스 재구성 : ARTICLE_X01 : BRD_ID, REG_DTM
SELECT *
FROM (SELECT /*+ LEADING(B A M) USE_NL(A M) INDEX_DESC(A ARTICLE_X01) */B.BRD_ID, B.BRD_NM, A.ART_SN, A.ART_TIL, GET_TEXT(A.ART_CONT,100) AS ART_CONT, A.MBR_NO, M.MBR_NM,A.REG_DT
FROM BOARD B, ARTICLE A, MEMBER M
WHERE B.BRD_ID = :BRD_ID
AND A.BRD_ID = B.BRD_ID
AND A.MBR_NO = M.MBR_NO
AND A.REG_DT >=TRUNC(SYSDATE,-3)
ORDER BY A.REG_DT DESC
)
WHERE ROWNUM<=5;
(실기2) 아래와 같이 테이블 구조가 있는데, 기존SQL를 개선하고, 필요시 인덱스를 생성할 것
(생성시 인덱스 및 파티셔닝이 필요하면 local partition 또는 global partition 등 작성, PK는 변경할 수 없음)
[테이블 정보]
* COMPANY : #CMP_ID, CMP_NM, ...
* ORDER : #ORD_NO, ORD_DT, CMP_ID, STAT_CD
* ORD_MATERIAL : #ORD_NO, #MTR_ID, ORD_DT, NUM_QTY, AMT_QTY, DC_QTY
* MATERIAL : #MTR_ID, MTR_NM, ...
[인덱스]
COMPANY_PK : CMP_ID
ORDER_PK : ORD_NO
ORDER_X01 : STAT_CD, ORD_DT
ORD_MATERIAL_PK : ORD_NO, MTR_ID
MATERIAL_PK : MTR_ID
[정보]
ORDER : 약 150여 만건 → STAT_CD 01(5%), 02(5%), 03(40%), 04(50%) 이외의 값은 존재하지 않음
ORD_MATERIAL : 연간 1500여 만건 / ORD_DT가 ORDER 테이블을 반정규화 하여 사용하고 있음 / 연단위로 ORD_DT로 파티셔닝 되어 있음
COMPANY : 5천건
MATERIAL : 1만건
주로 최근 1년을 자주 조회함
[기존SQL]
SELECT
A.ORD_NO
, C.CMP_ID
, C.CMP_NM
, SUM(B.NUM_QTY) AS NUM_QTY
, SUM(B.AMT_QTY) AS AMT_QTY
, SUM(B.DC_QTY) AS DC_QTY
FROM ORDER A, ORD_MATERIAL B, COMPANY C, MATERIAL D
WHERE
A.STAT_CD NOT IN ('03','04')
AND A.ORD_DT BETWEEN TO_DATE(:START_DT,'YYYY-MM-DD') AND TO_DATE(:END_DT,'YYYY-MM-DD')
AND A.ORD_NO = B.ORD_NO
AND A.CMP_ID = C.CMP_ID
AND A.MTR_ID = D.MTR_ID
GROUP BY A.ORD_NO, C.CMP_ID, C.CMP_NM;
· 신규 인덱스 생성 : ORD_MATERIAL_X01 : ORD_NO, ORD_DT (LOCAL NON-PREFIXED)
SELECT /*+ LEADING(A B C) USE_NL(B C) INDEX(B ORD_MATERIAL_X01) INDEX(A ORDER_X01) */ A.ORD_NO, C.CMP_ID, C.CMP_NM, SUM(B.NUM_QTY) AS NUM_QTY, SUM(B.AMT_QTY) AS AMT_QTY, SUM(B.DC_QTY) AS DC_QTY
FROM ORDER A, ORD_MATERIAL B, COMPANY C
WHERE A.STAT_CD IN ('01','02')
AND A.ORD_DT BETWEEN TO_DATE(:START_DT,'YYYY-MM-DD') AND TO_DATE(:END_DT,'YYYY-MM-DD')
AND A.ORD_NO = B.ORD_NO
AND A.CMP_ID = C.CMP_ID
AND A.ORD_DT = B.ORD_DT
GROUP BY A.ORD_NO, C.CMP_ID, C.CMP_NM;
문제점
1) STAT_CD 에 '01', '02' 는 전체 중 10% 뿐이나 NOT IN 으로 인해 풀스캔중임.
-> 너무 IN으로 바꾸고 싶음.
2) NL JOIN(ORD_NO + ORD_DT) VS HASH JOIN(ORD_DT)
STAT_CD 10% 라는 부분과 최근 1년 조회가 많다는 걸 보고 전자를 택함
IF) ORD_DT를 선두로한다면 다른 모든 주문을 불필요하게 읽어야함.
튜닝포인트
1) STAT_CD NOT_IN을 IN으로 변경해 INDEX SCAN 유도
ORD_NO이 150만건 테이블이고 최대기간 검색 시 10%인 15만건 정도면 INDEX 스캔해도 괜찮아보임.
2) ORD_MATERIAL 테이블 인덱스 신규생성 및 A.ORD_DT = B.ORD_DT 조건추가
ORD_NO + ORD_DT LOCAL로 구성하면 ORD_NO으로 접근 후 파티션 PRNUING가능
자주 들어오는 연단위조건이 들어올 시 한개의 파티션만 보면됨.
3) 불필요 테이블 제거하기 : MATERIAL 조인 제거
SELECT-LIST에 참조하지도 않고, ERD상 ORD_MATERIAL은 반드시 하나의 MATERIAL을 포함하기 때문에 제거가능(EXISTS도 불필요.)
추가 개선여지
1) A,B 조인 후 GROUP BY 하고나서 COMPANY C와 조인