SQLP 모의고사 1회
70문항 · 제한 시간 180분. 다 풀면 과목별 점수와 합격 여부, 문제마다 해설을 보여 줘요.
1. 통신사 요금 시스템은 ANSI-SPARC 3단계 스키마 구조로 운영된다. 다음 변경 사례와, 그 변경이 다른 계층에 영향을 주지 않도록 흡수하는 데이터 독립성의 연결로 가장 적절한 것은?
통화내역테이블을 월별 Range 파티션으로 재구성하고 인덱스를 추가했지만 테이블·컬럼 정의는 그대로다 — 논리적 독립성고객테이블에 '마케팅동의여부' 컬럼을 추가했지만, 기존 청구 화면이 사용하는 고객 뷰는 수정하지 않아도 되었다 — 논리적 독립성- 데이터 파일을 새 스토리지로 이전했지만 개념 스키마와 응용 프로그램은 변경되지 않았다 — 논리적 독립성
- 영업팀용 뷰에 컬럼을 하나 추가하자 개념 스키마의 테이블 구조도 함께 바뀌어야 했다 — 물리적 독립성
2. 통신사 가입·요금 업무를 분석하여
고객,요금제,가입회선,요금청구,통화내역,본사정보엔터티 후보를 도출하였다. 이에 대한 설명 중 옳은 것을 모두 고른 것은?- ㄱ.
요금제는 다른 엔터티로부터 주식별자를 상속받지 않고 업무에 원래 존재하는 정보이므로 발생 시점 기준으로 기본 엔터티다. - ㄴ.
가입회선은 고객과 요금제로부터 발생하며 요금청구·통화내역 등 많은 엔터티를 낳는 업무의 중심이므로 중심 엔터티로 볼 수 있다. - ㄷ.
통화내역은 업무 수행 과정에서 발생하고 발생량이 많으며 통계에 활용되므로 유무형 기준으로 사건 엔터티다. - ㄹ.
본사정보는 시스템에 항상 1건만 존재하더라도 업무에 꼭 필요하면 독립된 엔터티로 정의해야 한다.
- ㄱ, ㄴ
- ㄴ, ㄹ
- ㄱ, ㄷ, ㄹ
- ㄱ, ㄴ, ㄷ
- ㄱ.
3.
요금청구엔터티에 다음 속성을 정의하였다. 이에 대한 설명으로 가장 적절한 것은?속성 설명 청구년월 청구 대상 월 (예: 202409) 회선번호 가입회선으로부터 상속받은 속성기본료 요금제 월정액 할인금액 약정·결합 할인 합계 청구금액 기본료 + 사용료 − 할인금액 납부상태코드 미납/완납/부분납을 'N'/'Y'/'P'로 코드화 - '청구금액'은 다른 속성으로부터 계산되는 파생 속성이므로 원본 변경 시 정합성 유지 방안이 필요하며, 가급적 적게 정의하는 것이 원칙이다.
- '납부상태코드'는 업무에 원래 존재하던 상태를 그대로 옮긴 것이므로 설계 속성이 아니라 기본 속성이다.
- '할인금액'에는 약정할인·결합할인 금액을 '3000,5000'처럼 콤마로 이어 하나의 속성값으로 저장해도 속성 정의 원칙에 어긋나지 않는다.
- 속성은 엔터티를 설명하는 가장 작은 단위이므로 '청구년월'은 업무 요구와 관계없이 반드시 '청구년도'와 '청구월'로 분해해야 한다.
4. 통신사 업무 규칙이 다음과 같다.
- 고객은 회선을 1개 이상 보유해야 고객으로 등록되며(해지 회선도 이력으로 남음), 회선은 반드시 한 명의 고객에 속한다.
- 회선에는 단말기가 연결되지 않을 수 있고(유심 단독 개통), 하나의 회선에는 동시에 최대 1대의 단말기만 연결된다.
- 단말기는 재고 상태로 회선 없이 존재할 수 있으며, 동시에 최대 1개의 회선에만 연결된다.
이를 IE(Crow's Foot) 표기법으로 바르게 나타낸 것은? (
|는 필수,o는 선택,{는 여러 건, 기호는 해당 엔터티 쪽 끝에 표시한다.)- 고객
||--o{가입회선, 가입회선|o--o|단말기 - 고객
||--|{가입회선, 가입회선||--o|단말기 - 고객
||--|{가입회선, 가입회선|o--o|단말기 - 고객
|o--|{가입회선, 가입회선|o--o{단말기
5.
가입회선엔터티의 주식별자 후보와 업무 특성이 다음과 같다.후보 특성 전화번호 업무에서 가장 많이 사용. 고객 요청 시 번호 변경 가능하며, 해지 6개월 후 다른 고객에게 재부여된다 USIM일련번호 회선마다 다르지만 분실·교체 시 새 값으로 바뀐다 가입계약번호 개통 시 시스템이 부여. 모든 회선에 존재하고 중복·변경이 없다 고객번호 + 개통일자 한 고객이 같은 날 여러 회선을 개통할 수 있다 주식별자로 가장 적절한 것은?
- 전화번호 — 업무에서 가장 자주 쓰여 명칭으로서 이해하기 쉽고 조회 조건으로도 자주 쓰이기 때문이다.
- USIM일련번호 — 회선마다 값이 다르므로 유일성이 보장되기 때문이다.
- 가입계약번호 — 유일성·최소성·불변성·존재성을 모두 만족하기 때문이다.
- 고객번호 + 개통일자 — 부모 식별자를 상속하는 식별 관계를 구성할 수 있어 조인이 단순해지기 때문이다.
6. 다음
가입회선테이블의 PK는 회선번호이며, 업무 규칙상 고객명은 고객번호에 의해, 요금제명과 기본료는 요금제코드에 의해 결정된다.회선번호 고객번호 고객명 요금제코드 요금제명 기본료 부가서비스목록 L01 C1 김하나 P5G1 5G 스탠다드 55000 A01,A05 L02 C1 김하나 PLT2 LTE 슬림 33000 A05 L03 C2 오지훈 P5G1 5G 스탠다드 55000 (없음) 이 테이블에 대한 설명 중 옳은 것을 모두 고른 것은?
- ㄱ. 부가서비스목록 컬럼은 하나의 속성에 여러 값을 담고 있으므로 제1정규형을 위배한다.
- ㄴ. 고객명은 PK가 아닌 고객번호를 거쳐 결정되므로 이행 종속이 존재하며 제3정규형을 위배한다.
- ㄷ. PK가 단일 컬럼이므로 부분 종속은 발생할 수 없고, 1정규형만 만족하면 2정규형도 만족한다.
- ㄹ. 요금제명·기본료를
요금제테이블로 분리하는 작업은 제2정규화에 해당한다.
- ㄱ, ㄴ, ㄷ
- ㄱ, ㄹ
- ㄴ, ㄷ, ㄹ
- ㄱ, ㄴ, ㄷ, ㄹ
7.
가입회선은고객과 명의자 관계(필수)와 납부자 관계(선택, 납부자를 따로 지정한 회선만 값이 있음) 두 개의 관계를 가진다. 데이터가 다음과 같을 때 SQL의 결과로 옳은 것은? (회선번호 순으로 납부자 값을 나열)[CUST]
고객번호 고객명 등급 C1 김민수 VIP C2 이수진 GOLD C3 박지훈 VIP [LINE]
회선번호 명의고객번호 납부고객번호 L1 C1 NULL L2 C2 C3 L3 C2 C1 L4 C3 C2 SELECT L.회선번호, NVL(P.고객명, M.고객명) AS 납부자 FROM LINE L JOIN CUST M ON M.고객번호 = L.명의고객번호 LEFT OUTER JOIN CUST P ON P.고객번호 = L.납부고객번호 AND P.등급 = 'VIP' ORDER BY L.회선번호;- 김민수, 박지훈, 김민수, 이수진
- 김민수, 이수진, 김민수, 박지훈
- 박지훈, 김민수 (2건)
- 김민수, 박지훈, 김민수, 박지훈
8. 통신사 개통 업무의 ERD(IE 표기법)가 다음과 같다. (
|는 필수,o는 선택,{는 여러 건)이 모델을 바탕으로 한 트랜잭션 설계 설명 중 옳은 것을 모두 고른 것은?
- ㄱ. 회선 개통 시
가입회선과 최초요금제변경이력은 하나의 트랜잭션으로 함께 INSERT한 뒤 COMMIT해야 한다. - ㄴ. 고객은 회선 없이도 존재할 수 있으므로, 고객 등록을 회선 개통과 별도 트랜잭션으로 커밋해도 모델과 모순되지 않는다.
- ㄷ.
번호이동신청은 회선당 최대 1건이므로 회선 개통 트랜잭션에서 반드시 함께 생성해야 한다. - ㄹ. 업무상 필수인
요금제변경이력쪽을 선택(o{)으로 잘못 그리면, 회선만 먼저 커밋하는 코드가 작성되어 이력 없는 회선이 남을 위험이 있다.
고객 ||--o{ 가입회선 : 보유한다 가입회선 ||--|{ 요금제변경이력 : 기록된다 가입회선 ||--o| 번호이동신청 : 신청된다- ㄱ, ㄴ
- ㄱ, ㄴ, ㄹ
- ㄴ, ㄷ, ㄹ
- ㄱ, ㄷ, ㄹ
- ㄱ. 회선 개통 시
9. 다음 데이터에 대해 (가)~(다)를 실행한 결과로 옳은 것은? (Oracle 기준)
[LINE]
회선번호 요금제코드 할인율 L1 P1 10 L2 P2 NULL L3 P1 NULL L4 P3 20 [EXCL] (할인 제외 요금제)
요금제코드 P2 NULL -- (가) SELECT COUNT(*) FROM LINE WHERE 요금제코드 NOT IN (SELECT 요금제코드 FROM EXCL); -- (나) SELECT COUNT(*) FROM LINE L WHERE NOT EXISTS (SELECT 1 FROM EXCL E WHERE E.요금제코드 = L.요금제코드); -- (다) SELECT AVG(할인율) FROM LINE;- 0 / 3 / 15
- 3 / 3 / 15
- 0 / 3 / 7.5
- 3 / 2 / 7.5
10.
요금청구엔터티의 본질식별자는 (청구년월, 회선번호)이고, 업무 규칙상 회선별로 매월 1건만 청구된다. 하위 엔터티청구항목,납부내역이 이 식별자를 상속받는다. 설계자가 시퀀스로 생성하는 인조식별자청구ID를 PK로 도입하려 한다. 이에 대한 설명으로 가장 적절하지 않은 것은?- (청구년월, 회선번호)에 별도의 Unique 제약을 두지 않으면 같은 회선·같은 월의 청구가 중복 입력될 수 있다.
- 조회 대부분이
회선번호 = :a AND 청구년월 = :b형태라면 청구ID PK 인덱스 외에 (회선번호, 청구년월) 인덱스가 추가로 필요해져 인덱스 수가 늘어난다. - 본질식별자를 유지하면
청구항목이 (청구년월, 회선번호, 항목코드)로 식별되어, 회선·월 조건으로 청구항목을 조회할 때 부모와 조인하지 않고도 조건을 줄 수 있다. - 인조식별자를 PK로 지정하면 시퀀스가 유일한 값을 보장하므로 '회선당 월 1건 청구' 업무 규칙도 PK 제약만으로 자동 보장된다.
11.
LINE(회선번호, 고객번호, 요금제코드, 개통일자) 테이블에 대해 다음 SQL을 Oracle에서 실행할 때 오류가 발생하는 것은?SELECT 요금제코드, COUNT(*) AS CNT FROM LINE GROUP BY 요금제코드 ORDER BY CNT DESC;SELECT 고객번호 FROM LINE ORDER BY 개통일자;SELECT DISTINCT 고객번호 FROM LINE ORDER BY 개통일자;SELECT 요금제코드 FROM LINE GROUP BY 요금제코드 ORDER BY MAX(개통일자);
12. 요금 청구 기준일 계산을 위해 다음 SQL을 실행하였다. 결과로 옳은 것은? (Oracle, 결과는 C1 / C2 / C3 순)
SELECT TO_CHAR(ADD_MONTHS(TO_DATE('20240131','YYYYMMDD'), 1), 'YYYYMMDD') AS C1, TO_CHAR(LAST_DAY(TO_DATE('20240215','YYYYMMDD')) + 1, 'YYYYMMDD') AS C2, MONTHS_BETWEEN(TO_DATE('20240331','YYYYMMDD'), TO_DATE('20240229','YYYYMMDD')) AS C3 FROM DUAL;- 20240302 / 20240301 / 1
- 20240229 / 20240301 / 1
- 20240229 / 20240229 / 1.0645...
- 20240302 / 20240301 / 1.0645...
13. 통화 과금 단위를 점검하기 위해 다음 SQL을 실행하였다. 결과로 옳은 것은? (Oracle, C1 / C2 / C3 순)
[CALL]
통화초 59 60 61 NULL SELECT SUM(CEIL(통화초 / 60)) AS C1, SUM(DECODE(SIGN(통화초 - 60), 1, 1, 0)) AS C2, COUNT(NULLIF(MOD(통화초, 60), 0)) AS C3 FROM CALL;- 3 / 1 / 2
- 4 / 2 / 3
- 4 / 1 / 3
- 4 / 1 / 2
14. 다음 [LINE] 테이블에 SQL을 실행한 결과 건수는?
회선번호 상태 요금제코드 월기본료 L1 A P_5G 55000 L2 S P_LTE 33000 L3 S PX5G 69000 L4 T P_5G 55000 L5 A PLTE 29000 L6 S P_5G NULL SELECT COUNT(*) FROM LINE WHERE 상태 = 'A' OR 상태 = 'S' AND 요금제코드 LIKE 'P\_%' ESCAPE '\' AND NOT 월기본료 < 40000;- 2건
- 3건
- 4건
- 5건
15. 다음 [CALL] 테이블에 SQL을 실행한 결과로 옳은 것은? (회선번호 / C1 / C2 순)
회선번호 통화유형 요금 L1 국내 100 L1 국제 500 L1 국제 300 L2 국내 300 L2 국내 NULL L3 국제 800 L3 국내 0 SELECT 회선번호, COUNT(요금) AS C1, SUM(요금) / COUNT(*) AS C2 FROM CALL GROUP BY 회선번호 HAVING MAX(요금) >= 300 ORDER BY 회선번호;- L1 / 3 / 300, L2 / 2 / 150, L3 / 2 / 400
- L1 / 3 / 300, L2 / 1 / 150, L3 / 2 / 400
- L1 / 3 / 300, L2 / 1 / 300, L3 / 2 / 400
- L1 / 3 / 300, L3 / 2 / 400
16. 다음 [CUST] 테이블에 SQL을 실행했을 때 조회되는 고객번호의 순서로 옳은 것은? (Oracle 기준)
고객번호 등급 가입일자 C1 GOLD 20230105 C2 VIP 20220310 C3 NULL 20240101 C4 SILVER 20240301 C5 VIP 20230720 C6 GOLD 20210915 SELECT 고객번호 FROM CUST ORDER BY CASE 등급 WHEN 'VIP' THEN 1 WHEN 'GOLD' THEN 2 END, 가입일자 DESC;- C2, C5, C1, C6, C3, C4
- C4, C3, C5, C2, C1, C6
- C5, C2, C1, C6, C4, C3
- C5, C2, C1, C6, C3, C4
17. 통화초 구간별 단가를 적용하기 위해 [CALL]과 [GRADE]를 조인하였다. SQL 결과로 옳은 것은? (구간:건수 형식)
[GRADE]
구간 시작초 종료초 1 0 60 2 60 180 3 181 99999 [CALL]
통화초 30 60 120 180 181 NULL SELECT G.구간, COUNT(*) AS 건수 FROM CALL C, GRADE G WHERE C.통화초 BETWEEN G.시작초 AND G.종료초 GROUP BY G.구간 ORDER BY G.구간;- 1:2, 2:3, 3:1
- 1:2, 2:2, 3:1
- 1:1, 2:3, 3:1
- 1:2, 2:3, 3:2
18. 청구 내역과 납부 내역을 대사(對査)하기 위해 다음 SQL을 실행하였다. 결과로 옳은 것은? (C1 / C2 / C3 순)
[BILL]
청구년월 회선번호 금액 202409 L1 30000 202409 L2 50000 202409 L3 20000 [PAY]
청구년월 회선번호 금액 202409 L1 30000 202409 L3 10000 202409 L4 5000 202408 L2 50000 SELECT COUNT(*) AS C1, COUNT(B.회선번호) AS C2, COUNT(P.회선번호) AS C3 FROM BILL B FULL OUTER JOIN PAY P ON B.회선번호 = P.회선번호 AND B.청구년월 = P.청구년월 WHERE NVL(B.청구년월, P.청구년월) = '202409';- 5 / 3 / 4
- 3 / 3 / 3
- 4 / 3 / 2
- 4 / 3 / 3
19. [PLAN] 테이블에 대해 (가)~(다)를 실행한 결과 건수로 옳은 것은? (Oracle 기준)
요금제코드 망구분 기본료 P1 LTE 33000 P2 LTE NULL P3 5G 55000 P4 5G 69000 P5 5G 59000 -- (가) SELECT * FROM PLAN WHERE 기본료 > ALL (SELECT 기본료 FROM PLAN WHERE 망구분 = 'LTE'); -- (나) SELECT * FROM PLAN WHERE 기본료 > ANY (SELECT 기본료 FROM PLAN WHERE 망구분 = '5G'); -- (다) SELECT * FROM PLAN WHERE 기본료 < ALL (SELECT 기본료 FROM PLAN WHERE 망구분 = '6G');- 2 / 2 / 0
- 0 / 2 / 5
- 0 / 3 / 4
- 3 / 2 / 5
20. '미납(납부여부 = 'N') 청구가 한 건이라도 있는 고객'의 고객번호를 조회하려 한다. LINE.고객번호는 CUST를 참조하는 NOT NULL FK이고, 한 고객은 여러 회선을, 한 회선은 여러 달의 청구를 가질 수 있다. (가)~(라) 중 결과 집합(순서 무시)이 나머지와 다를 수 있는 것은?
-- (가) SELECT C.고객번호 FROM CUST C WHERE EXISTS (SELECT 1 FROM LINE L, BILL B WHERE L.고객번호 = C.고객번호 AND B.회선번호 = L.회선번호 AND B.납부여부 = 'N'); -- (나) SELECT C.고객번호 FROM CUST C WHERE C.고객번호 IN (SELECT L.고객번호 FROM LINE L, BILL B WHERE B.회선번호 = L.회선번호 AND B.납부여부 = 'N'); -- (다) SELECT C.고객번호 FROM CUST C, LINE L, BILL B WHERE L.고객번호 = C.고객번호 AND B.회선번호 = L.회선번호 AND B.납부여부 = 'N'; -- (라) SELECT DISTINCT L.고객번호 FROM LINE L, BILL B WHERE B.회선번호 = L.회선번호 AND B.납부여부 = 'N';- (가)
- (나)
- (다)
- (라)
21. 5G 가입 고객 목록 A와 미납 고객 목록 B가 다음과 같다. (가)·(나)·(다)의 결과 건수로 옳은 것은? (Oracle 기준)
A.고객번호 B.고객번호 C1 C2 C2 C3 C2 C3 C3 C4 NULL NULL -- (가) SELECT 고객번호 FROM A UNION SELECT 고객번호 FROM B; -- (나) SELECT 고객번호 FROM A INTERSECT SELECT 고객번호 FROM B; -- (다) SELECT 고객번호 FROM A MINUS SELECT 고객번호 FROM B;- 5 / 3 / 1
- 4 / 2 / 1
- 5 / 2 / 2
- 10 / 3 / 1
22. [BILL2] 테이블에 다음 SQL을 실행했을 때 결과 행 수는? (Oracle 기준)
망구분 요금제 금액 LTE P1 10 LTE P1 20 LTE P2 30 5G P3 40 5G P4 50 SELECT 망구분, 요금제, SUM(금액) AS 합계 FROM BILL2 GROUP BY GROUPING SETS (망구분, ROLLUP(망구분, 요금제));- 7
- 8
- 11
- 9
23. 회선 L1의 월별 청구금액이 다음과 같다. SQL 결과에서 청구년월이 202409인 행의 C1 / C2 / C3 값으로 옳은 것은?
청구년월 금액 202406 40 202407 30 202408 50 202409 50 202410 20 SELECT 청구년월, 금액, SUM(금액) OVER (ORDER BY 금액) AS C1, AVG(금액) OVER (ORDER BY 청구년월 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS C2, LAG(금액, 2, 0) OVER (ORDER BY 청구년월) AS C3 FROM BILL WHERE 회선번호 = 'L1';- 140 / 40 / 30
- 190 / 40 / 50
- 190 / 40 / 30
- 190 / 50 / 30
24. 고객 C1의 회선이 다음과 같다. SQL 결과에서 회선번호 L2 행의 F / L / N 값으로 옳은 것은?
회선번호 고객번호 개통일자 기본료 L1 C1 20230101 55000 L2 C1 20230501 33000 L3 C1 20240301 69000 SELECT 회선번호, FIRST_VALUE(기본료) OVER (PARTITION BY 고객번호 ORDER BY 개통일자) AS F, LAST_VALUE(기본료) OVER (PARTITION BY 고객번호 ORDER BY 개통일자) AS L, NTILE(2) OVER (PARTITION BY 고객번호 ORDER BY 개통일자) AS N FROM LINE WHERE 고객번호 = 'C1';- 55000 / 69000 / 1
- 55000 / 33000 / 1
- 55000 / 33000 / 2
- 33000 / 69000 / 2
25. [BILL] 테이블의 청구금액이 L1 90, L2 80, L3 80, L4 80, L5 50일 때, (가)·(나)·(다)의 결과 건수로 옳은 것은? (Oracle 12c 이상)
-- (가) SELECT 회선번호, 청구금액 FROM BILL ORDER BY 청구금액 DESC FETCH FIRST 2 ROWS WITH TIES; -- (나) SELECT * FROM (SELECT 회선번호, 청구금액 FROM BILL ORDER BY 청구금액 DESC) WHERE ROWNUM <= 2; -- (다) SELECT * FROM BILL WHERE ROWNUM > 1;- 4 / 2 / 0
- 2 / 2 / 4
- 3 / 2 / 0
- 4 / 2 / 4
26. 대리점 조직 테이블 [ORG]에 다음 SQL을 실행한 결과로 옳은 것은? (C1 / C2 / C3 순)
코드 상위코드 명칭 H1 NULL 본사 A1 H1 수도권본부 A2 H1 영남본부 B1 A1 강남지사 B2 A1 분당지사 B3 A2 부산지사 D1 B1 역삼대리점 D2 B3 서면대리점 SELECT COUNT(*) AS C1, SUM(CONNECT_BY_ISLEAF) AS C2, MAX(LEVEL) AS C3 FROM ORG WHERE 명칭 <> '수도권본부' START WITH 상위코드 IS NULL CONNECT BY PRIOR 코드 = 상위코드 AND 코드 <> 'B3';- 6 / 3 / 4
- 5 / 2 / 4
- 7 / 3 / 4
- 5 / 3 / 4
27. [청구항목] 테이블에 (가)·(나)를 실행하였다. (가)의 결과 행 수 / (나)의 결과 행 수 / (가)에서 L1의 기본료 값으로 옳은 것은?
회선번호 청구년월 항목 금액 L1 202408 기본료 30 L1 202408 통화료 5 L1 202409 기본료 30 L1 202409 부가 3 L2 202409 기본료 50 L2 202409 통화료 10 -- (가) SELECT * FROM (SELECT 회선번호, 항목, 금액 FROM 청구항목) PIVOT (SUM(금액) FOR 항목 IN ('기본료' AS 기본료, '통화료' AS 통화료, '부가' AS 부가)); -- (나) SELECT * FROM 청구항목 PIVOT (SUM(금액) FOR 항목 IN ('기본료' AS 기본료, '통화료' AS 통화료, '부가' AS 부가));- 2 / 2 / 30
- 2 / 3 / 60
- 3 / 3 / 60
- 2 / 3 / 30
28. [회선요금]과 [변경요청]이 다음과 같을 때 MERGE 수행 후 [회선요금]의 행 수와 기본료 합계로 옳은 것은? (Oracle 10g 이상)
[회선요금]
회선번호 요금제 기본료 L1 P1 30000 L2 P2 50000 L3 P1 30000 [변경요청]
회선번호 신규요금제 신규기본료 해지여부 L1 P3 69000 N L3 P1 30000 Y L4 P2 50000 N MERGE INTO 회선요금 T USING 변경요청 S ON (T.회선번호 = S.회선번호) WHEN MATCHED THEN UPDATE SET T.요금제 = S.신규요금제, T.기본료 = S.신규기본료 DELETE WHERE S.해지여부 = 'Y' WHEN NOT MATCHED THEN INSERT (회선번호, 요금제, 기본료) VALUES (S.회선번호, S.신규요금제, S.신규기본료) WHERE S.해지여부 = 'N';- 4행 / 199000
- 3행 / 130000
- 3행 / 169000
- 2행 / 119000
29. 비어 있는 테이블 T(C NUMBER)에 대해 Oracle에서 다음 문장을 순서대로 실행하였다. 마지막 SELECT의 결과는?
INSERT INTO T VALUES (1); SAVEPOINT SP1; INSERT INTO T VALUES (2); SAVEPOINT SP2; UPDATE T SET C = C * 10 WHERE C = 1; ROLLBACK TO SP1; INSERT INTO T VALUES (3); SAVEPOINT SP1; INSERT INTO T VALUES (4); ROLLBACK TO SP1; CREATE TABLE T2 (X NUMBER); ROLLBACK; SELECT SUM(C) FROM T;- 1
- 8
- 13
- 4
30. 데이터가 저장되어 있는
가입회선테이블과, 이를 참조하는 자식 테이블이 있는고객테이블에 대한 Oracle DDL 설명으로 가장 적절한 것은?ALTER TABLE 가입회선 ADD (해지사유 VARCHAR2(10) DEFAULT '미정' NOT NULL);은 성공하며, 기존 행의 해지사유도 '미정'으로 조회된다.ALTER TABLE 가입회선 ADD (해지사유 VARCHAR2(10) NOT NULL);은 기존 행에 한해 NULL을 허용하므로 데이터가 있어도 성공한다.TRUNCATE TABLE 가입회선;은 DML로 분류되므로 실행 후 ROLLBACK으로 되돌릴 수 있다.DROP TABLE 고객;은 고객을 참조하는 FK가 있어도 CASCADE CONSTRAINTS 옵션 없이 자식 테이블의 FK를 자동 삭제하고 성공한다.
31. 통신사 요금 시스템에서 요금제 변경 트랜잭션이
UPDATE 가입회선 ...; COMMIT;을 수행하였다. Oracle의 처리 과정에 대한 설명으로 가장 적절한 것은?- 서버 프로세스는 커밋 시점에 변경된 데이터 블록을 데이터 파일에 직접 기록한 뒤 커밋 완료를 알린다.
- 커밋은 LGWR가 리두 로그 버퍼의 내용을 온라인 리두 로그 파일에 기록하면 완료되고, 더티 블록은 커밋과 무관하게 나중에 DBWn이 기록한다.
- DBWn은 성능을 위해 해당 블록에 대한 Redo가 리두 로그 파일에 기록되기 전이라도 더티 블록을 먼저 데이터 파일에 기록할 수 있다.
- 인스턴스가 비정상 종료된 뒤 재기동되면 PMON이 리두 로그를 적용해 Roll Forward를 수행한다.
32. Oracle의 메모리·저장 구조에 대한 설명 중 옳은 것을 모두 고른 것은?
- ㄱ. 익스텐트는 연속된 블록의 집합이며, 하나의 익스텐트는 하나의 데이터 파일 안에 존재한다.
- ㄴ. 월별 Range 파티션으로 구성된
통화내역테이블은 파티션마다 별도의 세그먼트이므로 파티션별로 다른 테이블스페이스에 저장할 수 있다. - ㄷ. 하나의 세그먼트는 하나의 데이터 파일에만 저장되므로 테이블 크기는 데이터 파일 하나의 크기를 넘을 수 없다.
- ㄹ. PGA는 서버 프로세스 전용 메모리이므로 정렬·해시 작업 공간에 접근할 때 래치를 획득할 필요가 없다.
- ㄱ, ㄴ
- ㄱ, ㄷ, ㄹ
- ㄱ, ㄴ, ㄹ
- ㄴ, ㄷ, ㄹ
33. 라이브러리 캐시가 비어 있는 상태에서 같은 사용자가 다음 SQL을 ①~⑥ 순서로 실행하였다.
CURSOR_SHARING = EXACT일 때 하드 파싱이 발생한 횟수는?① SELECT * FROM 가입회선 WHERE 회선번호 = :B1 -- :B1 = 'L1' ② SELECT * FROM 가입회선 WHERE 회선번호 = :B1 -- :B1 = 'L2' ③ SELECT * FROM 가입회선 WHERE 회선번호 = 'L1' ④ SELECT * FROM 가입회선 WHERE 회선번호 = 'L2' ⑤ select * from 가입회선 where 회선번호 = :B1 ⑥ SELECT * FROM 가입회선 WHERE 회선번호 = 'L1'- 4회
- 3회
- 5회
- 2회
34. 바인드 변수와 리터럴 상수 사용에 대한 설명으로 가장 적절하지 않은 것은?
- 초당 수천 번 수행되는 회선 조회 SQL은 바인드 변수를 사용해 하드 파싱과 공유 풀 래치 경합을 줄이는 것이 원칙이다.
개통일자 BETWEEN :d1 AND :d2처럼 바인드 변수로 범위 조건을 주면, 옵티마이저는 하드 파싱 시 값을 알 수 없을 경우 고정된 선택도를 적용한다.- 월 1회 수행되는 대량 정산 배치에서 값 분포가 심하게 치우친 상태코드 컬럼은 리터럴 상수를 쓰는 것이 히스토그램 활용 측면에서 유리할 수 있다.
- 바인드 변수 Peeking이 동작하면 SQL을 실행할 때마다 새 바인드 값을 엿보고 그 값에 맞는 실행계획을 다시 생성하므로 값 분포에 따른 성능 편차가 사라진다.
35. 다음은 회선별 월간 청구 조회 SQL의 TKPROF 결과다. 이에 대한 해석으로 가장 적절한 것은?
call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 5 0.42 3.80 4500 14000 1000 60 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 7 0.42 3.80 4500 14000 1000 60- 논리적 I/O는 query 값인 14,000블록이며, 버퍼캐시 히트율은 약 68%다.
- 논리적 I/O는 15,000블록, 버퍼캐시 히트율은 70%이며, 수행 시간의 대부분은 CPU가 아닌 대기 시간이다.
- 물리적 I/O는 query − disk = 9,500블록이다.
- elapsed가 cpu보다 훨씬 크므로 CPU 사용량을 줄이는 것이 이 SQL 튜닝의 핵심이다.
36. 고객별 통화내역 조회 SQL을 10046 이벤트(레벨 8)로 추적한 결과 다음 대기 이벤트가 나타났다. 해석과 대응 방향으로 가장 적절한 것은?
Elapsed times include waiting on following events: Event waited on Times Max. Wait Total Waited ---------------------------------------- Waited ---------- ------------ SQL*Net message to client 12 0.00 0.00 db file sequential read 9812 0.02 7.45 db file scattered read 3 0.01 0.02 SQL*Net message from client 12 0.20 0.85- 인덱스 스캔 또는 인덱스 ROWID에 의한 테이블 액세스에서 발생한 Single Block I/O 대기가 응답 시간의 대부분이므로, 테이블 랜덤 액세스를 줄이는 인덱스 구성·조인 방식을 우선 검토한다.
- Table Full Scan의 Multiblock I/O 대기가 주원인이므로
db_file_multiblock_read_count를 늘리는 것이 우선이다. SQL*Net message from client대기가 12회 발생했으므로 네트워크 대역폭이 주된 병목이다.db file sequential read는 블록을 순차적으로 읽는 Full Scan 대기이므로 인덱스를 제거하면 대기가 줄어든다.
37. 다음 실행계획에 대한 설명으로 가장 적절한 것은?
--------------------------------------------------------------- | Id | Operation | Name | Rows | --------------------------------------------------------------- | 0 | SELECT STATEMENT | | 120 | |* 1 | HASH JOIN | | 120 | | 2 | TABLE ACCESS FULL | 요금제 | 300 | | 3 | NESTED LOOPS | | 120 | | 4 | NESTED LOOPS | | 120 | | 5 | TABLE ACCESS BY INDEX ROWID | 고객 | 40 | |* 6 | INDEX RANGE SCAN | 고객_X01 | 40 | |* 7 | INDEX RANGE SCAN | 가입회선_X01 | 3 | | 8 | TABLE ACCESS BY INDEX ROWID | 가입회선 | 3 | ---------------------------------------------------------------- Id 6 → 5 → 7 → 8 → 2 순서로 수행되며, 고객과 가입회선의 NL 조인 결과가 HASH JOIN의 Build Input이 된다.
- HASH JOIN의 Build Input은 Id 3의 NL 조인 결과이고, 요금제는 Probe Input이다.
- 요금제를 Full Scan해 해시 테이블을 먼저 만든 뒤, 고객_X01 → 고객 → 가입회선_X01 → 가입회선 순서의 NL 조인 결과를 Probe Input으로 사용한다.
- NL 조인의 드라이빙(Outer) 테이블은 가입회선이고, 고객은 Inner 테이블이다.
38.
통화내역_X01인덱스는 (회선번호, 통화유형코드, 통화일시)로 구성되어 있다. 다음 SQL의 실행계획과 Predicate Information에 대한 설명으로 가장 적절한 것은?SELECT 통화일시, 착신번호, 통화초, 통화요금 FROM 통화내역 WHERE 회선번호 = :B1 AND 통화일시 >= :D1 AND 통화일시 < :D2 AND 통화요금 > 0; ----------------------------------------------------------------- | Id | Operation | Name | Rows | ----------------------------------------------------------------- | 0 | SELECT STATEMENT | | 35 | |* 1 | TABLE ACCESS BY INDEX ROWID BATCHED| 통화내역 | 35 | |* 2 | INDEX RANGE SCAN | 통화내역_X01 | 120 | ----------------------------------------------------------------- Predicate Information (identified by operation id): 1 - filter("통화요금">0) 2 - access("회선번호"=:B1 AND "통화일시">=:D1 AND "통화일시"<:D2) filter("통화일시">=:D1 AND "통화일시"<:D2)- 통화일시 조건이 access에 나타나므로 인덱스 수평적 탐색 범위를 줄이는 데 기여한다.
통화요금 > 0조건은 인덱스 필터 조건이므로 테이블 액세스 횟수를 줄여 준다.- 인덱스를 (회선번호, 통화일시, 통화유형코드)로 바꾸어도 이 SQL의 인덱스 스캔 범위는 달라지지 않는다.
- 스캔 범위는 사실상
회선번호 = :B1로만 결정되고, 통화일시 조건은 인덱스 필터로 동작해 테이블 액세스 횟수를 줄인다.
39. 요금제별 미납 청구 조회 SQL의 Row Source Operation이 다음과 같다. 인덱스는
가입회선_X02(요금제코드),요금청구_X01(회선번호, 청구년월)이다. 해석으로 가장 적절한 것은?SELECT L.회선번호, B.청구금액 FROM 가입회선 L, 요금청구 B WHERE L.요금제코드 = :plan AND B.회선번호 = L.회선번호 AND B.청구년월 = :ym AND B.납부여부 = 'N'; Rows Row Source Operation ------- --------------------------------------------------------------- 20 NESTED LOOPS (cr=15120 pr=2104) 3000 NESTED LOOPS (cr=12115 pr=1890) 3000 TABLE ACCESS BY INDEX ROWID BATCHED 가입회선 (cr=3115 pr=410) 3000 INDEX RANGE SCAN 가입회선_X02 (cr=115 pr=12) 3000 INDEX RANGE SCAN 요금청구_X01 (cr=9000 pr=1480) 20 TABLE ACCESS BY INDEX ROWID 요금청구 (cr=3005 pr=214)- 요금청구 테이블 액세스(Id 6) 자체에서 15,120블록을 읽었으므로 이 오퍼레이션이 전체 I/O의 대부분을 차지한다.
- 요금청구 테이블을 3,000번 액세스해 20건만 남았으므로, 테이블 필터 조건인 납부여부를
요금청구_X01에 추가하면 테이블 랜덤 액세스를 크게 줄일 수 있다. - 가입회선 테이블 액세스가 3,115블록으로 가장 많은 블록을 읽었으므로 가입회선_X02의 클러스터링 팩터 개선이 최우선이다.
- 요금청구_X01 인덱스 탐색은 1회 수행되어 3,000건을 범위 스캔하였다.
40.
GATHER_PLAN_STATISTICS힌트로 수행한 뒤DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST')로 조회한 결과다. 해석으로 가장 적절한 것은?--------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | Buffers | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 12 | 3529 | | 1 | NESTED LOOPS | | 1 | 5 | 12 | 3529 | | 2 | TABLE ACCESS BY INDEX ROWID BATCHED| 고객 | 1 | 5 | 1150 | 1180 | |* 3 | INDEX RANGE SCAN | 고객_X03 | 1 | 5 | 1150 | 8 | | 4 | TABLE ACCESS BY INDEX ROWID | 가입회선 | 1150 | 1 | 12 | 2349 | |* 5 | INDEX RANGE SCAN | 가입회선_X01 | 1150 | 1 | 12 | 2310 | ---------------------------------------------------------------------------------------- 고객_X03의 E-Rows(5)와 A-Rows(1,150)가 크게 달라 카디널리티 추정이 잘못되었고, 그 영향으로 고객을 드라이빙하는 NL 조인이 선택되었을 가능성이 크다.
- Id 5의 E-Rows 1과 A-Rows 12를 그대로 비교하면 옵티마이저가 건수를 12배 과소 추정하였다.
- Id 4 가입회선 테이블 액세스 자체가 읽은 블록 수는 2,349블록이다.
- Id 1 NESTED LOOPS 오퍼레이션 자체에서 3,529블록을 읽었다.
41. Oracle B*Tree 인덱스에 대한 설명으로 가장 적절하지 않은 것은? (가입회선.해지일자는 사용 중 회선이면 NULL이고, 회선번호는 NOT NULL이다.)
- 단일 컬럼 인덱스
가입회선_X05(해지일자)로는해지일자 IS NULL인 사용 중 회선을 Range Scan으로 찾을 수 없다. - 결합 인덱스 (해지일자, 회선번호)를 만들면
해지일자 IS NULL조건도 인덱스 Range Scan으로 처리할 수 있다. - 리프 블록은 양방향 연결 리스트로 이어져 있고, 인덱스 키 값이 같은 레코드는 ROWID 순으로 정렬된다.
- 삭제가 많이 발생하면 루트에서 리프까지의 높이가 리프마다 달라지는 Unbalanced 인덱스가 되어 수직적 탐색 비용이 불균일해진다.
- 단일 컬럼 인덱스
42. 개통·해지·상태 변경이 초당 수백 건 발생하는
가입회선테이블에서, 개발자가 회선상태코드(값 4종류) 컬럼에 비트맵 인덱스를 만들자고 제안하였다. 이에 대한 설명으로 가장 적절한 것은?- 비트맵 인덱스는 NULL을 저장하지 못하므로 상태코드가 NULL인 회선은 검색할 수 없다.
- 값의 종류가 4개뿐이어서 저장 공간이 작고, 비트 연산으로 빠르게 결합할 수 있으므로 동시 DML이 많은 이 테이블에 가장 적합하다.
- 비트맵 인덱스는 하나의 엔트리가 많은 행을 커버해 DML 시 Lock 범위가 넓으므로, 동시 변경이 잦은 가입회선보다 읽기 위주의 DW성 집계 테이블에 적합하다.
가입회선을 IOT로 만들면 보조 인덱스는 리프에 물리적 ROWID만 저장하므로 비트맵 인덱스 없이도 동일한 효과를 얻는다.
43. 다음 인덱스가 있을 때, 조건절이 인덱스 Range Scan으로 정상 처리될 수 있는 것은? (개통일자는 VARCHAR2(8), 월기본료는 NUMBER 타입이다.)
CREATE INDEX 가입회선_X03 ON 가입회선 (개통일자); CREATE INDEX 가입회선_X04 ON 가입회선 (월기본료);WHERE 개통일자 = 20240901WHERE 월기본료 = '55000'WHERE 월기본료 LIKE '55%'WHERE NVL(개통일자, '99991231') >= '20240901'
44.
통화내역_X02인덱스는 (통화유형코드, 통화일시)이며, 통화유형코드는 'V'(음성), 'S'(문자), 'D'(데이터) 세 가지 값만 가진다. 다음 SQL에서 옵티마이저가 Index Skip Scan을 선택하였다. 이에 대한 설명으로 가장 적절한 것은?SELECT 회선번호, 통화유형코드, 통화일시, 통화요금 FROM 통화내역 WHERE 통화일시 >= TO_DATE('20240920 09', 'YYYYMMDD HH24') AND 통화일시 < TO_DATE('20240920 10', 'YYYYMMDD HH24'); -------------------------------------------------------------- | Id | Operation | Name | -------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| 통화내역 | |* 2 | INDEX SKIP SCAN | 통화내역_X02 | --------------------------------------------------------------- 선두 컬럼의 Distinct 값이 3개뿐이라 유형별로 통화일시 범위만 골라 읽을 수 있어 효율적이며,
통화유형코드 IN ('V','S','D')조건을 추가해 IN-List Iterator로 처리하도록 유도하는 방법도 있다. - Index Skip Scan은 선두 컬럼의 Distinct 값이 많을수록 건너뛸 수 있는 블록이 많아져 효율이 좋아진다.
- Index Skip Scan은 결과 정렬을 보장하지 않으므로
ORDER BY 통화유형코드, 통화일시를 추가하면 항상 SORT ORDER BY가 발생한다. - Index Skip Scan은 인덱스 세그먼트 전체를 Multiblock I/O로 읽은 뒤 조건에 맞지 않는 리프 블록을 버리는 방식이다.
- 선두 컬럼의 Distinct 값이 3개뿐이라 유형별로 통화일시 범위만 골라 읽을 수 있어 효율적이며,
45.
통화내역테이블(10,000,000건, 100,000블록)의 인덱스 통계가 다음과 같다. 이에 대한 설명으로 가장 적절한 것은?INDEX_NAME COLUMNS CLUSTERING_FACTOR NUM_ROWS LEAF_BLOCKS ------------- ---------- ----------------- ---------- ----------- 통화내역_X01 (통화일시) 120,000 10,000,000 28,000 통화내역_X02 (착신번호) 9,600,000 10,000,000 31,000- X02의 클러스터링 팩터가 나쁘므로
ALTER INDEX 통화내역_X02 REBUILD를 수행하면 X01 수준으로 개선된다. - 클러스터링 팩터는 인덱스 리프 블록 수에 가까울수록 좋으므로 X02가 X01보다 테이블 액세스 효율이 좋다.
- 같은 건수를 인덱스로 읽어도 X01을 통한 테이블 액세스 블록 수가 훨씬 적으므로, X01의 인덱스 손익분기점이 X02보다 높다.
- 클러스터링 팩터는 실행 시점에만 영향을 줄 뿐 옵티마이저의 인덱스 비용 계산에는 사용되지 않는다.
- X02의 클러스터링 팩터가 나쁘므로
46. 다음 SQL은 하루 수십만 번 수행된다. 실행 통계가 다음과 같을 때 가장 효과적인 개선 방안은?
SELECT 회선번호, 개통일자 FROM 가입회선 L WHERE 요금제코드 = :B1 AND 회선상태코드 = 'A'; ------------------------------------------------------------------------------ | Id | Operation | Name | A-Rows | Buffers | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 35 | 2810 | |* 1 | TABLE ACCESS BY INDEX ROWID BATCHED| 가입회선 | 35 | 2810 | |* 2 | INDEX RANGE SCAN | 가입회선_X03 | 2800 | 12 | ------------------------------------------------------------------------------ Predicate Information 1 - filter("회선상태코드"='A') 2 - access("요금제코드"=:B1)가입회선_X03의 클러스터링 팩터가 나쁜 것이 원인이므로 인덱스를 Rebuild한다.- 결과가 35건으로 적으므로
FULL(L)힌트로 Table Full Scan을 유도한다. - 회선상태코드는 값 종류가 적으므로 별도의 비트맵 인덱스를 만들어 BITMAP AND로 결합한다.
가입회선_X03을 (요금제코드, 회선상태코드)로 확장하고, 가능하면 (요금제코드, 회선상태코드, 회선번호, 개통일자)로 구성해 테이블 액세스 자체를 없앤다.
47.
요금청구테이블(청구년월별 Range 파티션, 파티션당 약 1,400만 건)에 대해 다음 SQL 패턴이 확인되었다. ①·②를 모두 효율적으로 처리할 수 있는 로컬 인덱스 구성으로 가장 적절한 것은?-- ① 매우 빈번 (고객센터 조회) WHERE 회선번호 = :line AND 청구년월 BETWEEN :ym1 AND :ym2 -- ② 빈번 WHERE 회선번호 = :line AND 청구년월 = :ym -- ③ 월 1회 미납 독촉 배치 (해당 월 청구의 약 8%) WHERE 청구년월 = :ym AND 납부상태코드 = 'N'- (청구년월, 회선번호)
- (회선번호, 청구년월)
- (납부상태코드, 청구년월, 회선번호)
- (청구년월, 납부상태코드, 회선번호)
48.
통화내역_X01인덱스가 다음과 같을 때, 실행계획에서 SORT ORDER BY 오퍼레이션을 생략할 수 있는 SQL은? (인덱스 외의 접근 경로는 고려하지 않는다.)CREATE INDEX 통화내역_X01 ON 통화내역 (회선번호, 통화일시) LOCAL;SELECT * FROM 통화내역 WHERE 회선번호 = :l1 AND 통화일시 >= :d ORDER BY 통화일시 DESCSELECT * FROM 통화내역 WHERE 회선번호 IN (:l1, :l2) ORDER BY 통화일시SELECT * FROM 통화내역 WHERE 회선번호 = :l1 ORDER BY 통화일시, 착신번호SELECT * FROM 통화내역 WHERE 회선번호 = :l1 ORDER BY TO_CHAR(통화일시, 'YYYYMMDDHH24MISS')
49. VIP 신규 고객의 사용 중 회선을 조회하는 SQL의 Row Source Operation이다. 인덱스는
고객_X02(가입일자),가입회선_X01(고객번호)이다. 가장 효과적인 개선 방안은?SELECT /*+ LEADING(C) USE_NL(L) */ C.고객명, L.회선번호 FROM 고객 C, 가입회선 L WHERE C.가입일자 >= :d AND C.고객등급 = 'VIP' AND L.고객번호 = C.고객번호 AND L.회선상태코드 = 'A'; Rows Row Source Operation ------- ------------------------------------------------------- 25 NESTED LOOPS (cr=4271) 30 NESTED LOOPS (cr=4241) 12 TABLE ACCESS BY INDEX ROWID 고객 (cr=4215) 4200 INDEX RANGE SCAN 고객_X02 (cr=15) 30 INDEX RANGE SCAN 가입회선_X01 (cr=26) 25 TABLE ACCESS BY INDEX ROWID 가입회선 (cr=30)- 가입회선을 드라이빙 테이블로 바꾸고 고객을 Inner로 NL 조인한다.
- 고객과 가입회선을 모두 Full Scan하여 해시 조인으로 변경한다.
고객_X02를 (고객등급, 가입일자)로 변경해 고객등급 조건을 인덱스 액세스 조건으로 사용한다.가입회선_X01을 (고객번호, 회선상태코드)로 변경한다.
50. NL 조인에 대한 설명으로 가장 적절하지 않은 것은?
- Inner 테이블의 인덱스 탐색 횟수는 Outer 테이블에서 조건을 통과한 건수와 같다.
- 조인 조건이 등치가 아니어도(
BETWEEN,LIKE등) Inner 쪽을 탐색할 수 있으면 사용할 수 있다. - 11g 이후 NESTED LOOPS가 두 번 나타나는 배치 I/O 실행계획에서는 결과가 Outer 인덱스 순서대로 나온다는 보장이 없으므로 정렬이 필요하면 ORDER BY를 명시해야 한다.
/*+ USE_NL(L) */힌트만 지정하면 L이 드라이빙 테이블이 되어 L부터 읽는 NL 조인이 수행된다.
51. 통화내역 1억 건과 통화초 구간표를 조인하는 배치 SQL의 실행계획이다. 이에 대한 설명으로 가장 적절한 것은?
SELECT /*+ LEADING(C) USE_MERGE(G) FULL(C) FULL(G) */ G.구간, COUNT(*), SUM(C.통화요금) FROM 통화내역 C, 통화구간 G WHERE C.통화일자 = :dt AND C.통화초 BETWEEN G.시작초 AND G.종료초 GROUP BY G.구간; ---------------------------------------------- | Id | Operation | Name | ---------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | HASH GROUP BY | | | 2 | MERGE JOIN | | | 3 | SORT JOIN | | |* 4 | TABLE ACCESS FULL | 통화내역 | |* 5 | FILTER | | |* 6 | SORT JOIN | | | 7 | TABLE ACCESS FULL | 통화구간 | ----------------------------------------------- 해시 조인을 쓸 수 없는 비등치 조건의 대량 조인에 적합한 방식이며, Inner 쪽(구간표)은 조인 컬럼 인덱스가 있더라도 SORT JOIN으로 정렬되어 PGA에 적재된다.
- 소트 머지 조인은 Outer와 Inner를 항상 모두 정렬하므로 어떤 경우에도 Outer 쪽의 소트를 생략할 수 없다.
- 조인 조건을
C.통화초 <> G.기준초처럼 부정형으로 바꾸어도 동일한 소트 머지 조인으로 처리할 수 있다. - 머지 단계에서 Inner 집합은 SGA 버퍼 캐시에 정렬되어 있으므로 탐색할 때마다 캐시버퍼 체인 래치를 획득한다.
52. 다음 SQL에서
요금제는 300건, 조건을 적용한결합상품은 약 3만 건,가입회선은 2,000만 건이다. 아래 실행계획이 나오도록 하는 힌트로 가장 적절한 것은?SELECT L.회선번호, P.요금제명, S.결합할인율 FROM 가입회선 L, 요금제 P, 결합상품 S WHERE L.요금제코드 = P.요금제코드 AND L.결합번호 = S.결합번호 AND S.결합유형 = '가족'; ---------------------------------------------- | Id | Operation | Name | ---------------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | HASH JOIN | | | 2 | TABLE ACCESS FULL | 요금제 | |* 3 | HASH JOIN | | |* 4 | TABLE ACCESS FULL | 결합상품 | | 5 | TABLE ACCESS FULL | 가입회선 | ----------------------------------------------/*+ LEADING(P L S) USE_HASH(L) USE_HASH(S) *//*+ LEADING(S L P) USE_HASH(L) USE_HASH(P) SWAP_JOIN_INPUTS(P) *//*+ LEADING(S L P) USE_HASH(L) USE_HASH(P) *//*+ LEADING(L S P) USE_HASH(S) USE_HASH(P) SWAP_JOIN_INPUTS(P) */
53. 해시 조인에 대한 설명으로 가장 적절하지 않은 것은?
- Build Input이 PGA의 Hash Area를 초과하면 양쪽 집합을 해시 파티션으로 나눠 Temp 테이블스페이스에 기록한 뒤 파티션 짝 단위로 조인하므로 성능이 크게 떨어진다.
- 해시 테이블은 쿼리가 끝나면 소멸하는 일회성 자료구조이므로, 초당 수백 번 수행되는 OLTP 조회에 해시 조인을 남용하면 CPU·메모리 부담이 커진다.
- Build Input의 해시 키 컬럼에 중복 값이 많으면 한 버킷의 체인이 길어져 Probe할 때마다 긴 체인을 스캔하게 된다.
- 해시 조인은 Probe 쪽 조인 컬럼에 인덱스가 있어야 사용할 수 있으며, 등치 조건과 범위 조건 모두에 사용할 수 있다.
54.
고객은 1,000만 건이고 가입일자 조건을 만족하는 고객은 약 2만 건이다. 다음 SQL과 실행계획에 대한 설명으로 가장 적절한 것은?SELECT C.고객번호, C.고객명 FROM 고객 C WHERE C.가입일자 >= :d AND EXISTS (SELECT /*+ NO_UNNEST */ 'X' FROM 가입회선 L WHERE L.고객번호 = C.고객번호 AND L.회선상태코드 = 'S'); ------------------------------------------------------------- | Id | Operation | Name | ------------------------------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | FILTER | | | 2 | TABLE ACCESS BY INDEX ROWID BATCHED| 고객 | |* 3 | INDEX RANGE SCAN | 고객_X02 | |* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| 가입회선 | |* 5 | INDEX RANGE SCAN | 가입회선_X01 | -------------------------------------------------------------- FILTER 오퍼레이션은 서브쿼리 결과를 캐싱하므로 메인 쿼리 건수와 관계없이 서브쿼리는 1회만 수행된다.
- FILTER 방식에서는 서브쿼리 쪽 가입회선을 먼저 읽어 고객을 찾아가는 순서로도 실행될 수 있다.
- 메인 쿼리 2만 건마다 서브쿼리를 수행하되 조건을 만족하는 첫 건을 찾으면 멈춘다. 입력 값(고객번호)이 모두 달라 캐싱 효과는 기대할 수 없으므로, 메인 집합이 커지는 경우
UNNEST HASH_SJ로 세미 조인을 유도하는 방안을 검토할 수 있다. - 서브쿼리에
PUSH_SUBQ힌트를 추가하면 서브쿼리가 Unnesting되어 해시 세미 조인으로 바뀐다.
55. 다음 원래 SQL의 스칼라 서브쿼리를 조인으로 바꾸려 한다.
가입회선.요금제코드는 NULL을 허용하며(선불 회선은 NULL),요금제.요금제코드는 PK다. 원래 SQL과 항상 같은 결과를 반환하는 것은?-- 원래 SQL SELECT L.회선번호, L.개통일자, (SELECT P.요금제명 FROM 요금제 P WHERE P.요금제코드 = L.요금제코드) AS 요금제명 FROM 가입회선 L WHERE L.고객번호 = :cust; -- (가) SELECT L.회선번호, L.개통일자, P.요금제명 FROM 가입회선 L, 요금제 P WHERE L.고객번호 = :cust AND P.요금제코드(+) = L.요금제코드; -- (나) SELECT L.회선번호, L.개통일자, P.요금제명 FROM 가입회선 L, 요금제 P WHERE L.고객번호 = :cust AND P.요금제코드 = L.요금제코드; -- (다) SELECT L.회선번호, L.개통일자, P.요금제명 FROM 가입회선 L LEFT OUTER JOIN 요금제 P ON P.요금제코드 = L.요금제코드 WHERE L.고객번호 = :cust AND P.요금제명 IS NOT NULL; -- (라) SELECT L.회선번호, L.개통일자, P.요금제명 FROM 가입회선 L RIGHT OUTER JOIN 요금제 P ON P.요금제코드 = L.요금제코드 WHERE L.고객번호 = :cust;- (가)
- (나)
- (다)
- (라)
56.
가입회선테이블과 컬럼 통계가 다음과 같고 히스토그램은 없다. 다음 SQL에 대해 옵티마이저가 추정하는 카디널리티(E-Rows)로 가장 적절한 것은? (하드 파싱 시 바인드 변수 Peeking은 동작하지 않는다고 가정한다.)-- NUM_ROWS(가입회선) = 20,000,000 -- NUM_DISTINCT: 요금제코드 = 200, 회선상태코드 = 4 SELECT * FROM 가입회선 WHERE 요금제코드 = :p AND 회선상태코드 = :s AND 개통일자 > :d;- 25,000건
- 1,250건
- 63건
- 5,000건
57. 옵티마이저와 통계정보에 대한 설명으로 가장 적절하지 않은 것은?
FIRST_ROWS_n모드는 처음 n건의 응답 속도를 최적화하므로, 결과를 끝까지 Fetch하는 화면에서는 ALL_ROWS보다 오히려 느려질 수 있다.시도 = '서울' AND 시군구 = '강남구'처럼 상관관계가 있는 조건은 독립 가정 때문에 카디널리티가 과소 추정되기 쉬우며, 확장 통계로 보완할 수 있다.- 히스토그램이 있어도 바인드 변수를 쓴 조건은 하드 파싱 시점의 Peeking 값 외에는 값별 분포를 반영하기 어렵다.
- 비용기반 옵티마이저는 가능한 모든 조인 순서와 액세스 경로 조합을 빠짐없이 비교하므로 항상 실제 최저 비용의 실행계획을 선택한다.
58. 다음 SQL의 실행계획에서 확인할 수 있는 쿼리 변환으로 가장 적절한 것은? (
통화내역_X01= (회선번호, 통화일시))SELECT /*+ LEADING(L) USE_NL(V) */ L.회선번호, V.통화건수, V.통화요금합 FROM 가입회선 L, (SELECT 회선번호, COUNT(*) AS 통화건수, SUM(통화요금) AS 통화요금합 FROM 통화내역 WHERE 통화일시 >= :d1 AND 통화일시 < :d2 GROUP BY 회선번호) V WHERE L.고객번호 = :cust AND V.회선번호 = L.회선번호; ----------------------------------------------------------------- | Id | Operation | Name | ----------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | NESTED LOOPS | | | 2 | TABLE ACCESS BY INDEX ROWID BATCHED | 가입회선 | |* 3 | INDEX RANGE SCAN | 가입회선_X01 | | 4 | VIEW PUSHED PREDICATE | | | 5 | SORT GROUP BY | | | 6 | TABLE ACCESS BY INDEX ROWID BATCHED| 통화내역 | |* 7 | INDEX RANGE SCAN | 통화내역_X01 | ----------------------------------------------------------------- Predicate Information 3 - access("L"."고객번호"=:CUST) 7 - access("회선번호"="L"."회선번호" AND "통화일시">=:D1 AND "통화일시"<:D2)- 뷰 Merging — 인라인 뷰를 풀어 메인 쿼리와 하나의 쿼리 블록으로 합친 뒤 조인 후 집계하였다.
- 서브쿼리 Unnesting — WHERE 절 서브쿼리를 세미 조인으로 변환하였다.
- 조인 조건 Pushdown — 가입회선에서 읽은 회선번호를 뷰 안으로 넘겨, 해당 회선의 통화내역만 인덱스로 읽어 집계하였다.
- OR-Expansion — 조건을 UNION ALL로 분기해 각 분기가 다른 인덱스를 사용하게 하였다.
59. 다음 원래 SQL에서
가입회선.회선번호는 PK이고,고객번호는 NULL을 허용한다(미등록 선불 회선). 옵티마이저가 OR-Expansion으로 만들어 내는 형태처럼 UNION ALL로 분기했을 때, 원래 SQL과 항상 같은 결과를 내는 것은?-- 원래 SQL SELECT * FROM 가입회선 WHERE 회선번호 = :line OR 고객번호 = :cust; -- (가) SELECT * FROM 가입회선 WHERE 고객번호 = :cust UNION ALL SELECT * FROM 가입회선 WHERE 회선번호 = :line AND LNNVL(고객번호 = :cust); -- (나) SELECT * FROM 가입회선 WHERE 고객번호 = :cust UNION ALL SELECT * FROM 가입회선 WHERE 회선번호 = :line AND 고객번호 <> :cust; -- (다) SELECT * FROM 가입회선 WHERE 고객번호 = :cust UNION ALL SELECT * FROM 가입회선 WHERE 회선번호 = :line; -- (라) SELECT * FROM 가입회선 WHERE 고객번호 = :cust UNION ALL SELECT * FROM 가입회선 WHERE 회선번호 = :line AND 고객번호 IS NULL;- (가)
- (나)
- (다)
- (라)
60. 다음 중 Oracle에서 힌트가 의도대로 적용될 수 있는 SQL은? (인덱스는 모두 존재한다.)
SELECT /*+ INDEX(가입회선 가입회선_X01) */ * FROM 가입회선 L WHERE L.고객번호 = :cSELECT /*+ LEADING(C) USE_NL(L) INDEX(L 가입회선_X01) */ C.고객명, L.회선번호 FROM 가입회선 L, 고객 C WHERE C.고객번호 = :c AND L.고객번호 = C.고객번호SELECT /* +FULL(L) */ * FROM 가입회선 L WHERE L.요금제코드 = :pSELECT /*+ USE_HASH(G) */ * FROM 통화내역 C, 통화구간 G WHERE C.통화초 BETWEEN G.시작초 AND G.종료초
61. 다음 SQL은 VIP 고객 중 2024년 이후 개통한 회선이 있는 고객 목록을 조회한다. 한 고객은 평균 3개의 회선을 가진다. 소트 연산과 불필요한 I/O를 줄이는 방법으로 가장 적절한 것은?
SELECT DISTINCT C.고객번호, C.고객명 FROM 고객 C, 가입회선 L WHERE C.고객등급 = 'VIP' AND L.고객번호 = C.고객번호 AND L.개통일자 >= '20240101';- DISTINCT 대신
GROUP BY C.고객번호, C.고객명으로 바꾸면 소트 연산이 발생하지 않는다. - DISTINCT를 제거하고 UNION ALL로 바꾸면 중복 제거 없이 같은 결과를 얻는다.
- 가입회선 조건을 EXISTS 서브쿼리로 옮기면 고객마다 조건을 만족하는 회선 1건만 찾고 멈추므로, 중복 제거 소트(SORT/HASH UNIQUE)와 불필요한 조인이 함께 사라진다.
ORDER BY C.고객번호를 추가하면 정렬 과정에서 중복이 함께 제거되어 SORT UNIQUE가 생략된다.
- DISTINCT 대신
62. 회선별 통화내역을 통화요금 순으로 페이징하는 화면에서 **2페이지(11~20번째 행)**를 조회하려 한다. 통화요금 순으로 정렬된 인덱스는 없다. (가)~(라) 중 결과가 올바르면서 Top N 소트 알고리즘(SORT ORDER BY STOPKEY)이 작동하는 것은?
-- (가) SELECT * FROM (SELECT ROWNUM NO, A.* FROM (SELECT 통화일시, 착신번호, 통화요금 FROM 통화내역 WHERE 회선번호 = :line ORDER BY 통화요금 DESC) A) WHERE NO BETWEEN 11 AND 20; -- (나) SELECT * FROM (SELECT ROWNUM NO, 통화일시, 착신번호, 통화요금 FROM 통화내역 WHERE 회선번호 = :line AND ROWNUM <= 20 ORDER BY 통화요금 DESC) WHERE NO >= 11; -- (다) SELECT * FROM (SELECT ROWNUM NO, A.* FROM (SELECT 통화일시, 착신번호, 통화요금 FROM 통화내역 WHERE 회선번호 = :line ORDER BY 통화요금 DESC) A WHERE ROWNUM <= 20 AND ROWNUM >= 11); -- (라) SELECT * FROM (SELECT ROWNUM NO, A.* FROM (SELECT 통화일시, 착신번호, 통화요금 FROM 통화내역 WHERE 회선번호 = :line ORDER BY 통화요금 DESC) A WHERE ROWNUM <= 20) WHERE NO >= 11;- (가)
- (나)
- (다)
- (라)
63. 월말에 해지 회선의 청구 이력을 보관 테이블로 옮기기 위해 다음 문장을 수행하였다. 이에 대한 설명으로 가장 적절한 것은?
ALTER TABLE 청구이력_보관 NOLOGGING; INSERT /*+ APPEND */ INTO 청구이력_보관 SELECT B.* FROM 요금청구 B, 가입회선 L WHERE L.회선번호 = B.회선번호 AND L.회선상태코드 = 'T'; ---------------------------------------------- | Id | Operation | Name | ---------------------------------------------- | 0 | INSERT STATEMENT | | | 1 | LOAD AS SELECT | 청구이력_보관 | |* 2 | HASH JOIN | | |* 3 | TABLE ACCESS FULL | 가입회선 | | 4 | TABLE ACCESS FULL | 요금청구 | ----------------------------------------------- HWM 위쪽 새 블록에 버퍼 캐시를 거치지 않고 기록하며 테이블 데이터에 대한 Undo를 생성하지 않는다. 커밋 전에 같은 세션이
청구이력_보관을 조회하면 ORA-12838 오류가 발생한다. 청구이력_보관이 NOLOGGING 모드이면 이후 이 테이블에 대한 일반 UPDATE·DELETE도 Redo가 최소화된다.- APPEND 힌트는
INSERT /*+ APPEND */ INTO 청구이력_보관 VALUES (...)형태에서도 동일하게 Direct Path로 동작한다. - Direct Path Insert는 로우 Lock만 사용하므로 수행 중에도 다른 세션이 같은 테이블에 일반 INSERT를 동시에 수행할 수 있다.
- HWM 위쪽 새 블록에 버퍼 캐시를 거치지 않고 기록하며 테이블 데이터에 대한 Undo를 생성하지 않는다. 커밋 전에 같은 세션이
64.
통화내역테이블이 다음과 같이 월별 Range 파티션으로 구성되어 있다. 다음 조건 중 파티션 Pruning이 일어나지 않아 실행계획에PARTITION RANGE ALL이 나타나는 것은?CREATE TABLE 통화내역 ( 회선번호 VARCHAR2(12), 통화일자 VARCHAR2(8), 통화시각 VARCHAR2(6), 통화초 NUMBER, 통화요금 NUMBER ) PARTITION BY RANGE (통화일자) ( PARTITION P202401 VALUES LESS THAN ('20240201'), PARTITION P202402 VALUES LESS THAN ('20240301'), ... PARTITION P202409 VALUES LESS THAN ('20241001'), PARTITION P202410 VALUES LESS THAN ('20241101'), PARTITION PMAX VALUES LESS THAN (MAXVALUE) );WHERE 통화일자 BETWEEN :d1 AND :d2WHERE SUBSTR(통화일자, 1, 6) = '202409'WHERE 통화일자 >= '20240901' AND 통화일자 < '20241001'WHERE 통화일자 IN ('20240905', '20241003')
65. 월간 통화 집계 배치의 병렬 실행계획이다. 이에 대한 설명으로 가장 적절한 것은?
SELECT /*+ FULL(C) PARALLEL(C 8) */ 회선번호, COUNT(*), SUM(통화요금) FROM 통화내역 C WHERE 통화일자 BETWEEN '20240901' AND '20240930' GROUP BY 회선번호; --------------------------------------------------------------------------- | Id | Operation | Name | TQ |IN-OUT| PQ Distrib | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | | | 1 | PX COORDINATOR | | | | | | 2 | PX SEND QC (RANDOM) | :TQ10001 | Q1,01 | P->S | QC (RAND) | | 3 | HASH GROUP BY | | Q1,01 | PCWP | | | 4 | PX RECEIVE | | Q1,01 | PCWP | | | 5 | PX SEND HASH | :TQ10000 | Q1,00 | P->P | HASH | | 6 | PX BLOCK ITERATOR | | Q1,00 | PCWC | | |* 7 | TABLE ACCESS FULL | 통화내역 | Q1,00 | PCWP | | ---------------------------------------------------------------------------- DOP를 8로 지정했으므로 병렬 서버는 정확히 8개만 사용된다.
- Id 2의
P->S는 직렬로 처리한 결과를 병렬 서버에 다시 분배하는 구간이므로 병목의 신호다. - Producer 서버 집합이 통화내역을 블록 범위 단위로 나눠 읽고 회선번호 해시 값으로 재분배(P->P)하면, Consumer 서버 집합이 GROUP BY를 수행한다. 두 서버 집합이 쓰이므로 병렬 서버는 16개가 필요하다.
PX BLOCK ITERATOR는 파티션 단위로 Granule을 나누었다는 뜻이므로 파티션 개수보다 많은 병렬 서버는 일을 받지 못한다.
66. 다음 원래 SQL은 같은 테이블을 세 번 읽는다. 통화유형코드는 'V', 'S', 'D' 외에 'M'(영상) 등도 있으며, 특정 일자에 어떤 유형의 통화가 한 건도 없을 수 있다. (가)~(라) 중 원래 SQL과 결과 집합(행 순서 무시)이 항상 같으면서
통화내역을 한 번만 읽는 것은?-- 원래 SQL SELECT '음성' 구분, COUNT(*) 건수, SUM(통화요금) 요금 FROM 통화내역 WHERE 통화일자 = :d AND 통화유형코드 = 'V' UNION ALL SELECT '문자', COUNT(*), SUM(통화요금) FROM 통화내역 WHERE 통화일자 = :d AND 통화유형코드 = 'S' UNION ALL SELECT '데이터', COUNT(*), SUM(통화요금) FROM 통화내역 WHERE 통화일자 = :d AND 통화유형코드 = 'D'; -- (가) SELECT DECODE(통화유형코드, 'V', '음성', 'S', '문자', 'D', '데이터') 구분, COUNT(*), SUM(통화요금) FROM 통화내역 WHERE 통화일자 = :d GROUP BY 통화유형코드; -- (나) SELECT DECODE(통화유형코드, 'V', '음성', 'S', '문자', 'D', '데이터') 구분, COUNT(*), SUM(통화요금) FROM 통화내역 WHERE 통화일자 = :d AND 통화유형코드 IN ('V', 'S', 'D') GROUP BY 통화유형코드; -- (다) SELECT COUNT(CASE WHEN 통화유형코드 = 'V' THEN 1 END) 음성건수, SUM(CASE WHEN 통화유형코드 = 'V' THEN 통화요금 END) 음성요금, COUNT(CASE WHEN 통화유형코드 = 'S' THEN 1 END) 문자건수, SUM(CASE WHEN 통화유형코드 = 'S' THEN 통화요금 END) 문자요금, COUNT(CASE WHEN 통화유형코드 = 'D' THEN 1 END) 데이터건수, SUM(CASE WHEN 통화유형코드 = 'D' THEN 통화요금 END) 데이터요금 FROM 통화내역 WHERE 통화일자 = :d; -- (라) SELECT DECODE(B.NO, 1, '음성', 2, '문자', '데이터') 구분, DECODE(B.NO, 1, A.V건수, 2, A.S건수, A.D건수) 건수, DECODE(B.NO, 1, A.V요금, 2, A.S요금, A.D요금) 요금 FROM (SELECT COUNT(CASE WHEN 통화유형코드 = 'V' THEN 1 END) V건수, SUM(CASE WHEN 통화유형코드 = 'V' THEN 통화요금 END) V요금, COUNT(CASE WHEN 통화유형코드 = 'S' THEN 1 END) S건수, SUM(CASE WHEN 통화유형코드 = 'S' THEN 통화요금 END) S요금, COUNT(CASE WHEN 통화유형코드 = 'D' THEN 1 END) D건수, SUM(CASE WHEN 통화유형코드 = 'D' THEN 통화요금 END) D요금 FROM 통화내역 WHERE 통화일자 = :d AND 통화유형코드 IN ('V', 'S', 'D')) A, (SELECT LEVEL NO FROM DUAL CONNECT BY LEVEL <= 3) B;- (가)
- (나)
- (다)
- (라)
67. Oracle에서 세션 A가 다음 UPDATE를 수행하고 커밋하지 않은 상태다. 이때 세션 B가 (가)~(라)를 각각 실행할 때, Lock을 기다리며 대기 상태가 되는 것만 고른 것은? (
DDL_LOCK_TIMEOUT은 기본값 0이다.)-- 세션 A (미커밋) UPDATE 가입회선 SET 요금제코드 = 'P5G2' WHERE 회선번호 = 'L1'; -- 세션 B (가) SELECT 요금제코드 FROM 가입회선 WHERE 회선번호 = 'L1'; (나) UPDATE 가입회선 SET 회선상태코드 = 'S' WHERE 회선번호 = 'L2'; (다) DELETE FROM 가입회선 WHERE 회선번호 = 'L1'; (라) ALTER TABLE 가입회선 ADD (메모 VARCHAR2(100));- (가), (다)
- (다)
- (다), (라)
- (나), (다), (라)
68. Oracle의 Lock 동작에 대한 설명으로 가장 적절한 것은?
가입회선PK가 회선번호일 때 두 세션이 같은 회선번호로 INSERT하면 후행 세션은 대기하고, 선행 세션이 롤백하면 후행 세션의 INSERT는 성공한다.- 교착상태가 감지되면 Oracle은 희생 세션을 골라 그 세션의 트랜잭션 전체를 롤백한다.
- 한 트랜잭션이 갱신한 행이 수천 건을 넘으면 Lock 관리 메모리를 아끼기 위해 테이블 Lock으로 에스컬레이션한다.
SELECT ... FOR UPDATE NOWAIT은 다른 세션이 Lock을 건 행을 건너뛰고 나머지 행만 읽어 Lock을 건다.
69. Oracle에서 두 트랜잭션이 다음 순서로 수행되었다. ①의 결과가 1,000일 때 ③의 결과와 ④의 수행 결과로 옳은 것은? (청구금액 합계 단위: 천 원)
-- T1 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ① SELECT SUM(청구금액) FROM 요금청구 WHERE 청구년월 = '202409'; -- 1,000 -- T2 ② UPDATE 요금청구 SET 청구금액 = 청구금액 + 100 WHERE 청구년월 = '202409' AND 회선번호 = 'L1'; COMMIT; -- T1 ③ SELECT SUM(청구금액) FROM 요금청구 WHERE 청구년월 = '202409'; ④ UPDATE 요금청구 SET 납부상태코드 = 'Y' WHERE 청구년월 = '202409' AND 회선번호 = 'L1';- ③ 1,100 / ④ 성공
- ③ 1,100 / ④ ORA-08177 오류
- ③ 1,000 / ④ ORA-08177 오류
- ③ 1,000 / ④ 성공
70. 요금제 변경 화면은 회선 정보를 조회해 보여 준 뒤, 상담원이 새 요금제를 선택해 저장하면 다음과 같이 처리한다. 이 구현에 대한 설명으로 가장 적절하지 않은 것은?
-- 1) 화면 조회 (Lock 없음) SELECT 요금제코드, 변경일시 INTO :plan, :mod_dt FROM 가입회선 WHERE 회선번호 = :line; -- 2) 저장 UPDATE 가입회선 SET 요금제코드 = :new_plan, 변경일시 = SYSTIMESTAMP WHERE 회선번호 = :line AND 변경일시 = :mod_dt;- 낙관적 동시성 제어 방식이며, UPDATE 처리 건수가 0이면 다른 사용자가 먼저 변경한 것으로 보고 재조회를 안내한다.
가입회선을 갱신하는 모든 프로그램이 변경일시를 함께 갱신해야 동시 변경을 빠짐없이 감지할 수 있다.- UPDATE 전에
SELECT ... WHERE 회선번호 = :line AND 변경일시 = :mod_dt FOR UPDATE NOWAIT으로 먼저 확인하면, 다른 트랜잭션이 갱신 중일 때 대기하지 않고 바로 알 수 있다. - 조회 시점부터 상담원이 저장할 때까지 해당 행에 Lock이 유지되므로 갱신 손실이 원천적으로 차단된다.
AI가 SQLP 출제 범위를 바탕으로 새로 만든 예상 문제이며, 실제 기출문제가 아닙니다.