SQLP 모의고사 2회
70문항 · 제한 시간 180분. 다 풀면 과목별 점수와 합격 여부, 문제마다 해설을 보여 줘요.
1. 전자상거래 주문·정산 시스템을 구축하면서 수행한 다음 활동 중, 데이터 모델링 단계와 바르게 짝지은 것을 모두 고른 것은?
- ㄱ. 고객·주문·상품·정산 등 핵심 엔터티와 그 관계를 도출해 전사 관점의 개략적인 ERD를 작성했다. → 개념적 모델링
- ㄴ.
주문상품엔터티의 주식별자를 확정하고, 부분 함수 종속인상품명속성을상품엔터티로 옮겼다. → 논리적 모델링 - ㄷ.
정산테이블을 정산월 기준 Range 파티션으로 나누고 파티션별 테이블스페이스를 지정했다. → 물리적 모델링 - ㄹ. 월별 정산 리포트의 응답 속도를 높이려고
주문테이블에 정산금액 합계 컬럼을 추가하는 반정규화를 결정했다. → 개념적 모델링
- ㄱ, ㄴ
- ㄱ, ㄴ, ㄷ
- ㄴ, ㄷ, ㄹ
- ㄱ, ㄷ, ㄹ
2. 전자상거래 주문·정산 시스템의 엔터티를 분류한 것으로 가장 적절하지 않은 것은?
고객: 물리적 형태가 있고 업무 시작 전부터 독립적으로 존재하므로 유형 엔터티이자 기본(키) 엔터티이다.상품: 다른 엔터티로부터 주식별자를 상속받지 않고 자신의 고유한 주식별자를 가지는 기본 엔터티이다.주문: 고객과 상품이라는 기본 엔터티로부터 발생하며 업무의 중심 역할을 하고 많은 행위 엔터티를 파생시키므로 중심 엔터티이다.주문상품: 물리적 형태가 없는 개념이므로 개념 엔터티이며, 다른 엔터티에 의존하지 않고 생성되므로 기본 엔터티이다.
3. 다음은
주문엔터티의 속성 정의서 일부이다. 속성에 대한 설명으로 가장 적절한 것은?속성명 설명 주문번호 주문 1건을 식별하기 위해 시스템이 순차적으로 부여하는 번호 주문일자 고객이 주문한 날짜 주문상태코드 '10'(접수), '20'(결제완료), '30'(배송중), '40'(완료), '90'(취소) 주문총액 해당 주문에 속한 주문상품의 (수량 × 판매가) 합계 주문총액은 다른 속성으로부터 계산되는 파생 속성이며, 원천 데이터가 바뀔 때마다 함께 갱신해야 하므로 꼭 필요한 경우에만 정의하는 것이 바람직하다.주문번호는 업무 담당자가 실제 사용하는 번호이므로 업무로부터 추출한 기본 속성이다.주문상태코드는 업무 현장에서 그대로 쓰는 상태 값이므로 설계 속성이 아니라 기본 속성이다.- 한 주문에 여러 결제수단을 쓸 수 있다면
결제수단속성에 'CARD,POINT'처럼 여러 값을 저장해도 속성 정의 원칙에 어긋나지 않는다.
4. 부모 엔터티와 자식 엔터티의 관계 설정에 대한 설명으로 가장 적절한 것은?
주문상품의 주식별자를 (주문번호, 상품번호)로 하려면주문–주문상품,상품–주문상품관계를 모두 비식별 관계로 설정해야 한다.- 비식별 관계로 연결된 자식 엔터티는 부모의 주식별자를 일반 속성으로 상속받으므로, 관계는 반드시 필수(Mandatory) 관계여야 한다.
- 식별 관계만으로 계층을 계속 연결하면 하위 엔터티로 갈수록 주식별자 속성 수가 늘어나 조인 조건이 길어지고 SQL 작성 오류 가능성이 커진다.
정산이 주문과 무관한 조정 정산처럼 주문 없이도 생성될 수 있다면주문–정산관계는 식별 관계로 설정하는 것이 적절하다.
5.
고객엔터티의 주식별자 후보와, 그 후보가 주식별자로 부적절한 이유(위배되는 특징)를 짝지은 것으로 가장 적절하지 않은 것은?- 모든 고객은 가입 시 고유한 고객번호를 부여받는다.
- 이메일주소는 고객이 언제든 변경할 수 있다.
- 휴대폰번호가 없는 고객(법인 담당자 등)도 존재한다.
- 이메일주소 — 값이 변경될 수 있으므로 불변성 위배
- (고객명, 생년월일) — 동명이인이 같은 날 태어났을 수 있으므로 유일성 위배
- (고객번호, 이메일주소) — 고객번호만으로 유일하게 식별되므로 존재성 위배
- 휴대폰번호 — 값이 없는 고객이 있으므로 존재성 위배
6. 다음 두 테이블과 함수 종속에 대한 설명으로 가장 적절한 것은?
[주문상품] PK: (주문번호, 상품번호)
주문번호 상품번호 수량 판매가 상품명 주문일자 [정산] PK: 정산번호
정산번호 주문번호 판매자번호 판매자명 수수료율 함수 종속:
- 상품번호 → 상품명
- 주문번호 → 주문일자
- (주문번호, 상품번호) → 수량, 판매가 (판매가는 주문 시점의 실제 판매 가격)
- 판매자번호 → 판매자명, 수수료율
정산은 기본키가 단일 컬럼이므로 2정규형과 3정규형을 모두 만족한다.주문상품은 부분 함수 종속이 있어 2정규형을 위반하며, 2정규화하면 주문상품·상품·주문의 세 테이블로 분해된다.판매가는 상품의 가격 정보이므로 상품번호에 종속되며, 정규화 시상품테이블로 옮겨야 한다.정산을 3정규화하면판매자명과수수료율은주문테이블로 이동한다.
7.
주문엔터티가개인고객또는법인고객중 어느 하나와만 관계를 맺는 상호배타적(Exclusive OR, 아크) 관계로 설계되었다. 이에 대한 설명으로 가장 적절하지 않은 것은?- 주문 1건의 주문자 정보를 조회하려면 고객구분 값에 따라 개인고객 또는 법인고객과 조인해야 하므로, 두 테이블과 각각 아우터 조인하거나 UNION ALL로 분기하는 SQL이 필요하다.
- 주문 테이블에 개인고객번호와 법인고객번호를 각각 FK 컬럼으로 두면, 한 행에서는 둘 중 하나만 값을 갖고 나머지는 NULL이 된다.
- 주문 테이블에 고객구분코드와 하나의 고객번호 컬럼을 두는 방식은 컬럼 수는 줄지만, 고객번호 컬럼 하나에 두 부모 테이블을 참조하는 FK 제약을 선언할 수 없다.
- 상호배타적 관계에서는 주문 한 건이 개인고객과 법인고객 모두와 동시에 관계를 맺을 수 있으며, 이 경우 두 부모 정보를 모두 조인해야 한다.
8. 업무 규칙이 다음과 같을 때, 이를 데이터 모델에 표현하는 방법으로 가장 적절한 것은?
주문은 반드시 1개 이상의 주문상품과 하나의 트랜잭션으로 함께 생성되어야 한다. 주문상품만 먼저 생기거나, 주문상품이 하나도 없는 주문이 저장되어서는 안 된다.
주문–주문상품관계를 양쪽 모두 필수(Mandatory) 관계로 표현해, 함께 생성·관리되어야 하는 하나의 트랜잭션 단위임을 모델에 드러낸다.주문상품쪽을 선택(Optional) 관계로 두고, 최소 1건 존재 여부는 모델에 표현하지 않고 애플리케이션 로직으로만 관리한다.- 트랜잭션을 단순하게 만들기 위해
주문과주문상품을 하나의 엔터티로 통합하고 상품 정보를 반복 컬럼으로 둔다. 주문과주문상품이 한 트랜잭션에서 생성되므로 두 엔터티 사이의 관계는 비식별 관계로만 설정할 수 있다.
9. 다음 [주문] 테이블에 대해 SQL을 수행한 결과로 옳은 것은?
[주문]
주문번호 주문금액 할인금액 O1 10000 1000 O2 20000 NULL O3 30000 3000 O4 40000 NULL SELECT SUM(주문금액 - 할인금액) AS A , SUM(주문금액) - SUM(할인금액) AS B , AVG(할인금액) AS C , SUM(할인금액) / COUNT(*) AS D FROM 주문;- A = 96000, B = 96000, C = 2000, D = 1000
- A = 36000, B = 96000, C = 2000, D = 1000
- A = 36000, B = 96000, C = 1000, D = 2000
- A = 96000, B = 36000, C = 1000, D = 1000
10.
주문상품엔터티의 주식별자로 본질식별자 (주문번호, 상품번호)를 쓸지, 인조식별자주문상품일련번호를 쓸지 검토하고 있다. 같은 주문에 같은 상품은 한 번만 담을 수 있다는 업무 규칙이 있을 때, 설명으로 가장 적절하지 않은 것은?- 인조식별자만 PK로 두면, 같은 주문번호·상품번호 조합이 일련번호만 다른 채 중복 저장될 수 있다.
- 본질식별자를 PK로 쓰면 '주문번호 = ?' 조건의 조회가 PK 인덱스의 선두 컬럼을 활용할 수 있다.
- 인조식별자를 PK로 쓰면 주문번호로 조회하기 위한 별도 인덱스가 추가로 필요해져 인덱스 수와 DML 부하가 늘 수 있다.
- 인조식별자를 PK로 쓰면 PK의 유일성 제약에 의해 (주문번호, 상품번호) 중복도 자동으로 방지되므로 별도의 Unique 제약이 필요 없다.
11. 다음 중 Oracle에서 실행 시 오류가 발생하는 SQL은? (주문 테이블에는 주문번호, 고객번호, 주문일자, 주문금액 컬럼이 있다.)
- SELECT 주문번호, 주문금액 * 0.9 AS 할인가 FROM 주문 WHERE 할인가 > 10000;
- SELECT 고객번호, 주문금액 AS 금액 FROM 주문 ORDER BY 금액 DESC;
- SELECT DISTINCT 고객번호 FROM 주문 ORDER BY 고객번호;
- SELECT 주문.*, 1 AS 구분 FROM 주문;
12. 다음 SQL의 실행 결과(A, B, C, D 순서)로 옳은 것은? (Oracle 기준)
SELECT ROUND(15678.567, -2) AS A , TRUNC(15678.567, 1) AS B , CEIL(-3.2) AS C , MOD(-7, 3) AS D FROM DUAL;- 15700 / 15678.5 / -4 / 2
- 15600 / 15678.6 / -3 / -1
- 15700 / 15678.5 / -3 / -1
- 15700 / 15678.6 / -3 / 2
13. 다음 [주문할인] 테이블에 대해 SQL을 수행한 결과로 옳은 것은?
[주문할인]
주문번호 할인금액 쿠폰금액 O1 1000 1000 O2 NULL 500 O3 2000 NULL O4 NULL NULL O5 3000 1500 SELECT SUM(COALESCE(할인금액, 쿠폰금액, 0)) AS A , COUNT(NULLIF(할인금액, 쿠폰금액)) AS B , SUM(NVL2(할인금액, 1, 0)) AS C FROM 주문할인;- A = 6500, B = 3, C = 3
- A = 6500, B = 2, C = 3
- A = 6000, B = 2, C = 2
- A = 6000, B = 3, C = 2
14. 다음 [주문] 테이블에 대해 (가)~(라) SQL을 수행했을 때 결과 건수를 순서대로 나열한 것은?
[주문]
주문번호 주문상태 주문금액 O1 완료 50000 O2 취소 30000 O3 완료 20000 O4 배송중 70000 O5 배송중 15000 O6 완료 NULL -- (가) SELECT COUNT(*) FROM 주문 WHERE 주문상태 = '완료' OR 주문상태 = '배송중' AND 주문금액 >= 30000; -- (나) SELECT COUNT(*) FROM 주문 WHERE (주문상태 = '완료' OR 주문상태 = '배송중') AND 주문금액 >= 30000; -- (다) SELECT COUNT(*) FROM 주문 WHERE 주문금액 BETWEEN 50000 AND 20000; -- (라) SELECT COUNT(*) FROM 주문 WHERE NOT (주문금액 < 30000);- 2, 2, 0, 3
- 4, 2, 2, 4
- 4, 4, 0, 3
- 4, 2, 0, 3
15. 다음 [주문] 테이블에 대해 수행한 SQL 중 결과 건수가 나머지 셋과 다른 것은?
[주문]
주문번호 고객번호 주문상태 주문금액 O1 C01 완료 50000 O2 C01 취소 30000 O3 C02 완료 20000 O4 C03 배송중 70000 O5 C03 배송중 15000 O6 C03 완료 NULL O7 C04 취소 10000 - SELECT 고객번호 FROM 주문 WHERE 주문상태 <> '취소' GROUP BY 고객번호 HAVING COUNT(*) >= 2;
- SELECT 고객번호 FROM 주문 GROUP BY 고객번호 HAVING COUNT(*) >= 2;
- SELECT 고객번호 FROM 주문 GROUP BY 고객번호 HAVING COUNT(주문금액) >= 2;
- SELECT 고객번호 FROM 주문 GROUP BY 고객번호 HAVING SUM(CASE WHEN 주문상태 = '취소' THEN 1 ELSE 0 END) = 0;
16. 다음 [고객] 테이블에 대해 Oracle에서 SQL을 수행했을 때 출력되는 고객명의 순서로 옳은 것은?
[고객]
고객명 등급점수 김 80 이 NULL 박 95 최 80 정 NULL SELECT 고객명 FROM 고객 ORDER BY 등급점수 DESC, 고객명;- 박, 김, 최, 이, 정
- 박, 최, 김, 이, 정
- 이, 정, 박, 최, 김
- 이, 정, 박, 김, 최
17. [고객] 테이블에는 고객번호 C01~C05의 5건이 있고, [주문] 테이블은 아래와 같다. (가), (나)의 결과 건수로 옳은 것은?
[주문]
주문번호 고객번호 주문상태 O1 C01 완료 O2 C01 취소 O3 C02 완료 O4 C03 배송중 O5 C03 배송중 O6 C03 완료 O7 C04 취소 -- (가) SELECT C.고객번호, O.주문번호 FROM 고객 C, 주문 O WHERE C.고객번호 = O.고객번호(+) AND O.주문상태(+) = '완료'; -- (나) SELECT C.고객번호, O.주문번호 FROM 고객 C, 주문 O WHERE C.고객번호 = O.고객번호(+) AND O.주문상태 = '완료';- (가) 5, (나) 5
- (가) 5, (나) 3
- (가) 3, (나) 3
- (가) 8, (나) 3
18. ANSI 표준 조인에 대한 설명으로 옳은 것을 모두 고른 것은? (Oracle 기준)
[고객]
고객번호 C01 C02 C03 C04 [주문]
주문번호 고객번호 A C01 B C01 C C03 D C09 - ㄱ.
NATURAL JOIN에서 공통 컬럼에 테이블 별칭을 붙여O.고객번호처럼 참조하면 오류가 발생한다. - ㄴ.
LEFT OUTER JOIN에서 오른쪽 테이블에 대한 필터 조건은 ON 절에 쓰든 WHERE 절에 쓰든 결과가 같다. - ㄷ.
고객 FULL OUTER JOIN 주문 USING (고객번호)의 결과는 6건이다. - ㄹ.
JOIN … USING절에는 두 테이블에서 이름이 서로 다른 조인 컬럼도USING (고객번호, 고객ID)처럼 나열해 조인할 수 있다.
- ㄱ, ㄴ
- ㄴ, ㄹ
- ㄱ, ㄷ
- ㄱ, ㄷ, ㄹ
- ㄱ.
19. 다음 [주문] 테이블에 대해 (가)~(다) SQL을 수행한 결과를 순서대로 나열한 것은? (고객번호 'C09'의 주문은 없다.)
[주문]
주문번호 고객번호 주문금액 O1 C01 50000 O2 C01 30000 O3 C02 20000 O4 C03 70000 O5 C03 15000 O6 C03 40000 -- (가) SELECT COUNT(*) FROM 주문 WHERE 주문금액 > ALL (SELECT 주문금액 FROM 주문 WHERE 고객번호 = 'C09'); -- (나) SELECT COUNT(*) FROM 주문 WHERE 주문금액 > ANY (SELECT 주문금액 FROM 주문 WHERE 고객번호 = 'C09'); -- (다) SELECT COUNT(*) FROM 주문 WHERE 주문금액 > ALL (SELECT 주문금액 FROM 주문 WHERE 고객번호 = 'C01');- 6, 0, 1
- 0, 0, 1
- 0, 6, 1
- 6, 0, 4
20. 서브쿼리에 대한 설명으로 가장 적절한 것은? (Oracle 기준)
- WHERE 절의
IN (서브쿼리)안에는 결과 정렬을 위해 ORDER BY 절을 사용할 수 있다. - SELECT 절의 스칼라 서브쿼리가 어떤 메인 행에 대해 0건을 반환하면 오류가 발생한다.
- SELECT 절의 스칼라 서브쿼리가 어떤 메인 행에 대해 2건 이상을 반환하면, 그 행을 처리하는 시점에 오류가 발생한다.
WHERE 주문금액 = (서브쿼리)처럼 단일 행 비교 연산자와 함께 쓴 서브쿼리가 0건을 반환하면 오류가 발생한다.
- WHERE 절의
21. [주문1월]과 [주문2월]의 고객번호가 다음과 같을 때, (가)~(다) SQL의 결과 건수를 순서대로 나열한 것은? (Oracle 기준)
[주문1월]
고객번호 C01 C01 C02 C03 [주문2월]
고객번호 C02 C03 C03 C04 -- (가) SELECT 고객번호 FROM 주문1월 MINUS SELECT 고객번호 FROM 주문2월 UNION ALL SELECT 고객번호 FROM 주문2월; -- (나) SELECT 고객번호 FROM 주문1월 UNION ALL SELECT 고객번호 FROM 주문2월 MINUS SELECT 고객번호 FROM 주문2월; -- (다) SELECT 고객번호 FROM 주문1월 INTERSECT SELECT 고객번호 FROM 주문2월;- 5, 5, 2
- 1, 1, 2
- 5, 1, 3
- 5, 1, 2
22. [판매] 테이블의 (카테고리, 판매월) 조합이 다음 3가지만 존재할 때, (가)~(라) SQL의 결과 건수를 순서대로 나열한 것은?
[판매] (카테고리, 판매월 조합)
카테고리 판매월 가전 01 가전 02 의류 01 -- (가) SELECT 카테고리, 판매월, SUM(판매금액) FROM 판매 GROUP BY ROLLUP(카테고리, 판매월); -- (나) SELECT 카테고리, 판매월, SUM(판매금액) FROM 판매 GROUP BY CUBE(카테고리, 판매월); -- (다) SELECT 카테고리, 판매월, SUM(판매금액) FROM 판매 GROUP BY GROUPING SETS(카테고리, 판매월); -- (라) SELECT 카테고리, 판매월, SUM(판매금액) FROM 판매 GROUP BY ROLLUP((카테고리, 판매월));- 6, 7, 4, 4
- 6, 8, 4, 4
- 6, 8, 5, 3
- 5, 8, 4, 6
23. 다음 [주문] 테이블에 SQL을 수행했을 때
누적금액컬럼의 값으로 옳은 것은? (주문일자, 주문금액 오름차순으로 출력한다.)[주문]
주문일자 주문금액 2025-01-05 100 2025-01-05 200 2025-01-07 300 2025-01-09 400 SELECT 주문일자, 주문금액 , SUM(주문금액) OVER (ORDER BY 주문일자) AS 누적금액 FROM 주문 ORDER BY 주문일자, 주문금액;- 300, 300, 600, 1000
- 100, 300, 600, 1000
- 300, 300, 600, 600
- 100, 300, 300, 700
24. 다음 [주문] 테이블에 SQL을 수행했을 때 고객번호 C01의 세 행(주문일자 오름차순)에 대한 A, B 값으로 옳은 것은?
[주문]
고객번호 주문일자 주문금액 C01 2025-01-03 100 C01 2025-01-10 300 C01 2025-01-20 200 C02 2025-01-05 500 C02 2025-01-15 400 SELECT 고객번호, 주문일자, 주문금액 , LAST_VALUE(주문금액) OVER (PARTITION BY 고객번호 ORDER BY 주문일자) AS A , LAG(주문금액, 1, 0) OVER (PARTITION BY 고객번호 ORDER BY 주문일자) AS B FROM 주문;- A: 100, 300, 200 / B: 0, 100, 300
- A: 200, 200, 200 / B: 0, 100, 300
- A: 200, 200, 200 / B: NULL, 100, 300
- A: 100, 300, 200 / B: 100, 300, 0
25. 주문 테이블에서 주문금액 내림차순으로 2위~3위 주문을 정확히 조회하는 SQL로 옳은 것은? (주문금액에는 동일 값이 없다.)
- SELECT * FROM (SELECT * FROM 주문 ORDER BY 주문금액 DESC) WHERE ROWNUM BETWEEN 2 AND 3;
- SELECT * FROM (SELECT ROWNUM RN, A.* FROM 주문 A ORDER BY 주문금액 DESC) WHERE RN BETWEEN 2 AND 3;
- SELECT * FROM (SELECT ROWNUM RN, A.* FROM (SELECT * FROM 주문 ORDER BY 주문금액 DESC) A WHERE ROWNUM <= 3) WHERE RN >= 2;
- SELECT * FROM 주문 ORDER BY 주문금액 DESC OFFSET 2 ROWS FETCH NEXT 2 ROWS ONLY;
26. [고객] 테이블은 추천인 관계를 다음과 같이 저장한다. (가), (나)의 결과 건수로 옳은 것은?
[고객]
고객번호 추천인번호 등급 C01 NULL GOLD C02 C01 BRONZE C03 C01 SILVER C04 C02 GOLD C05 C03 BRONZE C06 C04 SILVER -- (가) SELECT 고객번호, LEVEL FROM 고객 START WITH 고객번호 = 'C01' CONNECT BY PRIOR 고객번호 = 추천인번호 AND 등급 <> 'BRONZE'; -- (나) SELECT 고객번호, LEVEL FROM 고객 WHERE 등급 <> 'BRONZE' START WITH 고객번호 = 'C01' CONNECT BY PRIOR 고객번호 = 추천인번호;- (가) 4, (나) 4
- (가) 4, (나) 2
- (가) 2, (나) 2
- (가) 2, (나) 4
27. [주문] 테이블은 주문번호, 고객번호, 주문월, 주문금액 네 컬럼으로 구성되며 데이터는 다음과 같다. (가), (나)의 결과 행 수로 옳은 것은?
[주문]
주문번호 고객번호 주문월 주문금액 O1 C01 01 100 O2 C01 02 200 O3 C02 01 300 O4 C03 01 400 O5 C03 02 500 -- (가) SELECT * FROM (SELECT 고객번호, 주문월, 주문금액 FROM 주문) PIVOT (SUM(주문금액) FOR 주문월 IN ('01' AS M01, '02' AS M02)); -- (나) SELECT * FROM 주문 PIVOT (SUM(주문금액) FOR 주문월 IN ('01' AS M01, '02' AS M02));- (가) 3, (나) 3
- (가) 3, (나) 5
- (가) 5, (나) 5
- (가) 2, (나) 5
28. [고객]과 [주문] 테이블이 다음과 같을 때, UPDATE 수행 후 SELECT의 결과로 옳은 것은?
[고객]
고객번호 누적주문금액 C01 0 C02 0 C03 0 C04 0 [주문]
고객번호 주문금액 C01 50000 C01 30000 C02 20000 C03 70000 UPDATE 고객 C SET 누적주문금액 = (SELECT SUM(O.주문금액) FROM 주문 O WHERE O.고객번호 = C.고객번호); SELECT SUM(누적주문금액), COUNT(누적주문금액), COUNT(*) FROM 고객;- 170000, 4, 4
- 170000, 4, 3
- 170000, 3, 4
- 120000, 3, 4
29. 빈 [주문] 테이블에 대해 Oracle에서 다음 문장을 순서대로 수행했다. 마지막 SELECT의 결과로 옳은 것은? (자동 커밋은 꺼져 있다.)
INSERT INTO 주문(주문번호) VALUES ('O1'); SAVEPOINT A; INSERT INTO 주문(주문번호) VALUES ('O2'); SAVEPOINT B; DELETE FROM 주문 WHERE 주문번호 = 'O1'; ROLLBACK TO A; INSERT INTO 주문(주문번호) VALUES ('O3'); CREATE TABLE 주문_백업 (주문번호 VARCHAR2(10)); INSERT INTO 주문(주문번호) VALUES ('O4'); ROLLBACK; SELECT COUNT(*) FROM 주문;- 1
- 2
- 3
- 4
30. DDL에 대한 설명으로 가장 적절하지 않은 것은? (Oracle 기준)
주문상품.주문번호의 FK를ON DELETE CASCADE로 선언하면, 주문 행을 삭제할 때 해당 주문의 주문상품도 함께 삭제된다.- 데이터가 들어 있는 VARCHAR2 컬럼의 길이를 기존 데이터 중 가장 긴 값보다 작게 줄이는
ALTER TABLE … MODIFY는 실패한다. CREATE TABLE … AS SELECT로 테이블을 복제하면 NOT NULL 제약은 복제되지만 PK·FK·UNIQUE 같은 다른 제약조건과 인덱스는 복제되지 않는다.TRUNCATE TABLE은 DELETE와 같이 행 단위로 Undo를 남기므로, 수행 직후 ROLLBACK하면 삭제된 데이터를 복구할 수 있다.
31. Oracle의 아키텍처와 DML 처리 과정에 대한 설명으로 옳은 것을 모두 고른 것은?
- ㄱ. 사용자가 커밋하면 DBWn이 변경된 버퍼 블록을 데이터 파일에 기록해야 커밋이 완료된다.
- ㄴ. Undo는 트랜잭션 롤백뿐 아니라, 다른 세션이 변경 중인 블록을 쿼리 시작 시점 기준으로 읽게 하는 읽기 일관성에도 사용된다.
- ㄷ. DBWn이 더티 블록을 데이터 파일에 기록하기 전에, 해당 변경에 대한 Redo가 먼저 리두 로그 파일에 기록되어야 한다.
- ㄹ. 하나의 익스텐트는 테이블스페이스를 구성하는 여러 데이터 파일에 걸쳐 할당될 수 있다.
- ㄱ, ㄴ
- ㄱ, ㄹ
- ㄴ, ㄷ
- ㄴ, ㄷ, ㄹ
32. Oracle 메모리 구조와 프로세스에 대한 설명으로 가장 적절한 것은?
- 해시 조인의 Build Input으로 만든 해시 테이블과 정렬 작업 공간은 서버 프로세스 전용 메모리인 PGA에 할당되므로, 접근할 때 래치를 획득할 필요가 없다.
- 라이브러리 캐시는 서버 프로세스별 PGA에 있으므로, 세션마다 같은 SQL의 실행계획을 따로 만들어 보관한다.
- 인스턴스가 비정상 종료된 후 재기동될 때 Redo를 적용해 Roll Forward하는 인스턴스 복구는 PMON이 담당한다.
- RAC는 하나의 인스턴스가 여러 데이터베이스를 관리하는 구조이며, 캐시 퓨전으로 데이터베이스 간 블록을 주고받는다.
33. 다음 6개의 SQL을 한 세션에서 위에서부터 차례로 처리했다. 처음 수행 시 라이브러리 캐시에는 관련 SQL이 전혀 없었고, 수행 중 캐시에서 밀려나는 SQL은 없다고 가정한다. 하드 파싱이 발생한 횟수는?
(1) SELECT * FROM 주문 WHERE 주문번호 = :B1 -- :B1 = 'O1001' (2) SELECT * FROM 주문 WHERE 주문번호 = :B1 -- :B1 = 'O2002' (3) select * from 주문 where 주문번호 = :B1 -- :B1 = 'O1001' (4) SELECT * FROM 주문 WHERE 주문번호 = :B2 -- :B2 = 'O1001' (5) SELECT * FROM 주문 WHERE 주문번호 = 'O1001' (6) SELECT * FROM 주문 WHERE 주문번호 = 'O1001'- 1회
- 2회
- 3회
- 4회
34. 주문 테이블(1,000만 건)의
주문상태컬럼은 '완료'가 95%, '접수'가 1% 등 분포가 크게 치우쳐 있고 히스토그램이 수집되어 있다. 주문상태 단일 인덱스가 있을 때, 다음 SQL에 대한 설명으로 가장 적절하지 않은 것은?SELECT 주문번호, 고객번호, 주문금액 FROM 주문 WHERE 주문상태 = :상태;- 바인드 변수를 쓰면 OLTP 환경에서 반복 수행될 때 하드 파싱이 줄어 공유 풀 래치 경합과 CPU 사용량이 감소한다.
- 바인드 변수를 사용해도 옵티마이저는 매 실행 시점마다 입력된 값의 히스토그램을 확인해 '접수'일 때는 인덱스, '완료'일 때는 Full Scan 계획을 새로 만든다.
- 바인드 변수 Peeking이 동작하면 하드 파싱 시점의 첫 입력값이 '접수'일 경우 인덱스 스캔 계획이 캐싱되어, 이후 '완료' 조회도 인덱스로 수행될 수 있다.
- 하루 한 번 수행되는 배치 SQL이라면 '완료'처럼 값을 리터럴로 지정해 값에 맞는 최적 계획을 얻는 편이 나을 수 있다.
35. 다음은 어떤 SQL의 TKPROF 결과이다. 이 SQL의 **버퍼캐시 히트율(BCHR)**은?
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 201 1.12 9.84 12500 48000 2000 20000 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 203 1.12 9.84 12500 48000 2000 20000- 약 74%
- 75%
- 약 76%
- 25%
36. 정산 테이블 세그먼트는 블록 수가 각각 20, 20, 16개인 익스텐트 3개로 구성되어 있다.
db_file_multiblock_read_count = 8이고 버퍼 캐시에 이 테이블의 블록이 하나도 없을 때, 정산 테이블을 Table Full Scan하는 데 필요한 Multiblock I/O Call 횟수는? (세그먼트 헤더 읽기 등 다른 I/O는 무시한다.)- 8회
- 7회
- 9회
- 3회
37. 다음 실행계획에서 오퍼레이션이 실행되는 순서(Id 기준)로 옳은 것은?
------------------------------------------------------------------ | Id | Operation | Name | Rows | ------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 2500 | |* 1 | HASH JOIN | | 2500 | |* 2 | TABLE ACCESS FULL | 고객 | 5000 | | 3 | NESTED LOOPS | | 2500 | | 4 | TABLE ACCESS BY INDEX ROWID | 주문 | 1000 | |* 5 | INDEX RANGE SCAN | 주문_X01 | 1000 | | 6 | TABLE ACCESS BY INDEX ROWID | 주문상품 | 3 | |* 7 | INDEX RANGE SCAN | 주문상품_PK | 3 | ------------------------------------------------------------------- 0 → 1 → 2 → 3 → 4 → 5 → 6 → 7
- 2 → 4 → 5 → 3 → 6 → 7 → 1 → 0
- 2 → 5 → 4 → 7 → 6 → 3 → 1 → 0
- 5 → 4 → 7 → 6 → 3 → 2 → 1 → 0
38. 주문_X01 인덱스는 (고객번호, 주문일자, 주문상태) 순으로 구성되어 있다. 다음 SQL과 실행계획에 대한 설명으로 가장 적절하지 않은 것은?
SELECT 주문번호, 주문금액 FROM 주문 WHERE 고객번호 = :B1 AND 주문상태 = '완료' AND 주문일자 BETWEEN :D1 AND :D2 AND 결제수단 = 'CARD'; --------------------------------------------------------------------- | Id | Operation | Name | Rows | --------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | |* 1 | TABLE ACCESS BY INDEX ROWID BATCHED| 주문 | 5 | |* 2 | INDEX RANGE SCAN | 주문_X01 | 12 | --------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("결제수단"='CARD') 2 - access("고객번호"=:B1 AND "주문일자">=:D1 AND "주문일자"<=:D2) filter("주문상태"='완료')- 주문일자 조건이 인덱스 액세스 조건으로 사용되어 고객번호 = :B1 범위 안에서 주문일자 구간만 스캔한다.
- 주문상태 조건은 인덱스 필터 조건이므로, 인덱스를 읽으면서 테이블로 갈지 여부를 판단해 테이블 랜덤 액세스 횟수를 줄여 준다.
- 결제수단 조건은 테이블을 액세스한 뒤에 적용되는 테이블 필터 조건이다.
- 주문상태 조건도 인덱스 컬럼이므로 수평적 탐색의 시작점과 끝점을 정해 인덱스 스캔 범위를 줄여 준다.
39. 다음 TKPROF 결과에 대한 설명으로 가장 적절한 것은?
call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.01 0.01 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 1001 0.85 2.10 1200 15030 0 100000 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 1003 0.86 2.11 1200 15030 0 100000 Misses in library cache during parse: 1 Optimizer mode: ALL_ROWS- Fetch Call 횟수와 처리 건수로 볼 때, 클라이언트의 Array Size는 100으로 설정되어 있었다고 볼 수 있다.
- disk 컬럼 값 1,200은 버퍼 캐시에서 읽은 블록 수로, 논리적 I/O에 해당한다.
Misses in library cache during parse: 1은 라이브러리 캐시에서 SQL을 찾아 소프트 파싱했다는 의미이다.- Fetch 단계의 elapsed가 cpu보다 큰 것은 SQL이 블록을 읽지 않고 대기만 했기 때문이므로 I/O 튜닝은 필요 없다.
40.
gather_plan_statistics힌트를 주고 수행한 뒤 DBMS_XPLAN.DISPLAY_CURSOR로 확인한 결과이다. 설명으로 가장 적절한 것은?----------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | Buffers | ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 50 | 2530 | | 1 | NESTED LOOPS | | 1 | 40 | 50 | 2530 | | 2 | TABLE ACCESS BY INDEX ROWID BATCHED| 고객 | 1 | 20 | 500 | 520 | |* 3 | INDEX RANGE SCAN | 고객_X01 | 1 | 20 | 500 | 5 | |* 4 | TABLE ACCESS BY INDEX ROWID | 주문 | 500 | 2 | 50 | 2010 | |* 5 | INDEX RANGE SCAN | 주문_X01 | 500 | 2 | 1000 | 1010 | ------------------------------------------------------------------------------------------ 이 SQL에서 가장 많은 블록을 읽은 오퍼레이션은 Buffers 값이 가장 큰 Id 1(NESTED LOOPS)이다.
- Id 4의 E-Rows 2는 전체 실행에 대한 예상 건수이므로, 실제 A-Rows 50보다 크게 과소 추정되었다.
- Id 4에서 테이블 액세스로 읽은 블록은 약 1,000개이며 1,000건 중 50건만 남았으므로, 주문_X01 인덱스에 테이블 필터 조건 컬럼을 추가하는 것을 검토할 수 있다.
- Id 3의 인덱스는 NL 조인의 Inner이므로 500번 반복 수행되었다.
41. Oracle B*Tree 인덱스에 대한 설명으로 가장 적절한 것은?
- 루트에서 리프 블록까지의 높이는 리프 블록마다 다를 수 있으며, 데이터가 많이 몰린 키 값일수록 탐색 경로가 길어진다.
- 단일 컬럼 B*Tree 인덱스는 키 값이 NULL인 레코드를 저장하지 않으므로,
WHERE 쿠폰번호 IS NULL조건은 쿠폰번호 단일 인덱스로 Range Scan할 수 없다. - 인덱스 리프 블록에 저장된 ROWID는 버퍼 캐시의 메모리 주소이므로, ROWID로 테이블 블록을 찾아갈 때는 별도의 탐색 비용이 들지 않는다.
- 주문상태처럼 값의 종류가 적은 컬럼은 동시 DML이 많은 OLTP 주문 테이블이라도 비트맵 인덱스를 만드는 것이 가장 적합하다.
42.
정산내역테이블은 매일 야간 배치로 적재된 뒤에는 거의 변경되지 않으며, 온라인 조회의 대부분이WHERE 판매자번호 = :s AND 정산일자 BETWEEN :d1 AND :d2형태로 수십~수백 건을 읽는다. 현재는 (판매자번호, 정산일자) 일반 인덱스를 통해 조회하는데 클러스터링 팩터가 나빠 테이블 랜덤 액세스가 많다. 저장 구조 개선 방안으로 가장 적절한 것은?- 해시 클러스터 테이블로 바꾸고 판매자번호와 정산일자를 클러스터 키로 지정한다.
- IOT는 PK가 필요 없으므로 PK 없이
ORGANIZATION INDEX로 생성하고, 판매자번호 단일 인덱스를 추가한다. - SQL Server로 이관할 경우를 대비해 (판매자번호, 정산일자)와 (정산일자) 두 개의 클러스터형 인덱스를 함께 만든다.
- PK를 (판매자번호, 정산일자, 정산번호)로 하는 IOT로 생성해, 같은 판매자의 기간 데이터가 PK 순으로 모여 저장되도록 한다.
43. 주문_X01은 (주문일자) 단일 컬럼 인덱스이다. 다음 SQL에 대해 옵티마이저가 아래 실행계획을 선택한 이유로 가장 적절한 것은?
SELECT /*+ FIRST_ROWS(10) */ 주문번호, 주문일자, 주문금액 FROM 주문 WHERE 주문금액 >= 1000000 ORDER BY 주문일자; ---------------------------------------------------------- | Id | Operation | Name | Rows | ---------------------------------------------------------- | 0 | SELECT STATEMENT | | 10 | |* 1 | TABLE ACCESS BY INDEX ROWID| 주문 | 10 | | 2 | INDEX FULL SCAN | 주문_X01 | 4000 | ---------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("주문금액">=1000000)- 주문금액 조건이 인덱스 액세스 조건으로 사용되어 1,000,000 이상인 주문만 인덱스에서 바로 찾을 수 있기 때문이다.
- Index Full Scan은 Multiblock I/O로 인덱스 전체를 빠르게 읽으므로, 대량 데이터를 읽을 때 항상 Table Full Scan보다 유리하기 때문이다.
- 인덱스가 주문일자 순으로 정렬되어 있어 SORT ORDER BY를 생략하고 첫 10건을 빨리 반환(부분범위 처리)할 수 있기 때문이며, 주문금액 조건을 만족하는 행이 드물면 오히려 많은 테이블 블록을 랜덤 액세스하게 될 수 있다.
- 주문일자가 NOT NULL 컬럼이므로 Index Full Scan을 하면 결과 정렬이 보장되지 않아도 되기 때문이다.
44. 주문 테이블에 다음 두 인덱스가 있다. 조건절 ㄱ~ㄹ 중 해당 인덱스로 정상적인 Index Range Scan이 가능한 것을 모두 고른 것은? (Oracle 기준)
- 주문_X01: (고객번호) — 고객번호는 NUMBER 타입
- 주문_X02: (주문일자) — 주문일자는 VARCHAR2(8) 타입, 'YYYYMMDD' 형식
ㄱ. WHERE 고객번호 = '1001' ㄴ. WHERE 주문일자 = 20250105 ㄷ. WHERE 주문일자 LIKE '202501%' ㄹ. WHERE SUBSTR(주문일자, 1, 6) = '202501'- ㄱ, ㄷ
- ㄷ
- ㄴ, ㄷ
- ㄱ, ㄴ, ㄷ
45. 주문 테이블과 두 인덱스의 통계정보가 다음과 같다. 설명으로 가장 적절하지 않은 것은?
-- USER_TABLES TABLE_NAME NUM_ROWS BLOCKS ---------- ---------- ---------- 주문 10,000,000 200,000 -- USER_INDEXES INDEX_NAME LEAF_BLOCKS CLUSTERING_FACTOR ---------- ------------ ------------------ 주문_X01 26,000 210,000 주문_X02 31,000 9,500,000- 주문_X01은 클러스터링 팩터가 테이블 블록 수에 가까우므로, 인덱스 순서와 테이블 데이터의 저장 순서가 거의 일치한다.
- 같은 건수를 인덱스로 읽어 테이블을 액세스할 때, 주문_X02를 사용하면 주문_X01보다 테이블 블록 I/O가 훨씬 많이 발생한다.
- 주문_X02로 전체의 1%(10만 건)를 읽어야 한다면 테이블 랜덤 액세스가 10만 블록 가까이 발생할 수 있어 Table Full Scan이 더 유리할 수 있다.
- 클러스터링 팩터가 좋으면 인덱스 리프 블록을 스캔하는 블록 수 자체가 줄어들므로, 인덱스 스캔 단계의 I/O가 감소한다.
46. 주문_X01 인덱스는 (고객번호, 주문일자)로 구성되어 있다. 다음 SQL과 Row Source Operation을 보고 선택한 개선 방안 중 가장 효과적인 것은? (주문상태 값은 5종류이며 '반품'은 매우 드물다.)
SELECT 주문번호, 주문금액 FROM 주문 WHERE 고객번호 = :B1 AND 주문일자 >= :B2 AND 주문상태 = '반품'; Rows Row Source Operation ------- --------------------------------------------------- 12 TABLE ACCESS BY INDEX ROWID 주문 (cr=8350 pr=7920 time=4.21s) 8200 INDEX RANGE SCAN 주문_X01 (cr=32 pr=5 time=0.01s)- 인덱스 효율이 나쁘므로 FULL 힌트로 주문 테이블을 Full Scan하도록 변경한다.
- 주문_X01을 (고객번호, 주문상태, 주문일자)로 재구성해, 인덱스 스캔 범위와 테이블 액세스를 모두 결과 건수(12건) 수준으로 줄인다.
- 주문_X01의 컬럼 순서는 유지하고 NLJ_BATCHING 힌트를 추가해 테이블 랜덤 액세스를 배치 I/O로 처리한다.
- 주문 테이블을 주문일자 순으로 재생성해 클러스터링 팩터를 개선한다.
47. 다음 SQL은 하루 수십만 번 수행된다. 정산 테이블에 새 인덱스를 하나 만들 때 인덱스 스캔 효율이 가장 좋은 컬럼 구성은? (판매자번호 NDV 5만, 정산상태 NDV 3, 정산일자는 3년치 일자)
SELECT 정산번호, 주문번호, 정산금액 FROM 정산 WHERE 판매자번호 = :s AND 정산상태 = '확정' AND 정산일자 BETWEEN :d1 AND :d2 AND 정산금액 > 0;- (판매자번호, 정산상태, 정산일자)
- (정산일자, 판매자번호, 정산상태)
- (판매자번호, 정산일자, 정산상태)
- (정산상태, 정산일자, 판매자번호)
48. 주문_X03 인덱스는 (주문월, 결제수단)으로 구성되어 있고, 한 달 주문은 약 200만 건이며 결제수단 'POINT'는 그중 0.1% 정도이다. 다음 SQL에 대한 설명으로 가장 적절한 것은? (인덱스 구성은 변경할 수 없다.)
SELECT 주문번호, 고객번호, 주문금액 FROM 주문 WHERE 주문월 BETWEEN '202501' AND '202503' AND 결제수단 = 'POINT'; Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("주문월">='202501' AND "결제수단"='POINT' AND "주문월"<='202503') filter("결제수단"='POINT')- 현재 SQL에서도 결제수단 조건은 인덱스 액세스 조건으로 사용되므로 스캔 범위는 결과 건수와 비슷하다.
- BETWEEN을 IN-List로 바꾸면 주문월이 몇 개의 값이든 항상 유리하므로, 1년 이상의 기간 조회에도 그대로 적용해야 한다.
- 주문월 조건을 IN ('202501', '202502', '202503')으로 바꾸면 INLIST ITERATOR로 월마다 (주문월, 결제수단) 조합의 범위만 스캔하므로, 결제수단도 액세스 조건이 된다.
- 결제수단이 선택도가 좋으므로 INDEX_SS 힌트로 Skip Scan을 유도하는 것이 최선이다.
49. 다음 SQL의 Row Source Operation에 대한 설명으로 가장 적절한 것은?
SELECT /*+ LEADING(C) USE_NL(O) INDEX(C 고객_X01) INDEX(O 주문_X01) */ C.고객명, O.주문번호, O.주문금액 FROM 고객 C, 주문 O WHERE C.등급 = 'VIP' AND O.고객번호 = C.고객번호 AND O.주문일자 >= :B1; -- 주문_X01 : (고객번호, 주문일자) Rows Row Source Operation ------- --------------------------------------------------- 3200 NESTED LOOPS 1000 TABLE ACCESS BY INDEX ROWID 고객 1000 INDEX RANGE SCAN 고객_X01 3200 TABLE ACCESS BY INDEX ROWID 주문 3200 INDEX RANGE SCAN 주문_X01- USE_NL(O) 힌트가 지정되었으므로 주문 테이블이 드라이빙 테이블(Outer)이다.
- NL 조인의 Inner 쪽 테이블은 인덱스와 관계없이 Outer 한 건마다 Full Scan된다.
- 주문_X01 인덱스 탐색은 고객 중 실제로 주문이 있는 고객 수(1,000명 이하)만큼만 수행된다.
- 주문_X01 인덱스에 대한 수직적 탐색은 1,000번 일어났다.
50. 다음 SQL의 Row Source Operation을 분석했다. 조인 순서와 방식(고객 → 주문 NL 조인)을 유지할 때, 블록 I/O를 가장 크게 줄일 수 있는 인덱스 개선안은?
SELECT C.고객명, O.주문번호, O.주문금액 FROM 고객 C, 주문 O WHERE C.등급 = 'VIP' AND C.가입일자 >= :B1 AND O.고객번호 = C.고객번호 AND O.주문일자 >= :B2 AND O.주문상태 = '취소'; -- 고객_X01 : (가입일자) 주문_X01 : (고객번호, 주문일자) Rows Row Source Operation ------- --------------------------------------------------- 45 NESTED LOOPS (cr=36540) 900 TABLE ACCESS BY INDEX ROWID 고객 (cr=10920) 9800 INDEX RANGE SCAN 고객_X01 (cr=30) 45 TABLE ACCESS BY INDEX ROWID 주문 (cr=25620) 22000 INDEX RANGE SCAN 주문_X01 (cr=2720)- 고객_X01을 (등급, 가입일자)로, 주문_X01을 (고객번호, 주문상태, 주문일자)로 변경한다.
- 고객_X01만 (가입일자, 등급)으로 변경한다.
- 주문_X01에 주문금액 컬럼을 추가해 (고객번호, 주문일자, 주문금액)으로 변경한다.
- 고객_X01을 (가입일자, 고객번호)로 변경해 고객 테이블 액세스를 없앤다.
51. 주문(1억 건)과 프로모션(5천 건)을 다음과 같이 조인한다. 두 테이블 모두 조인에 활용할 인덱스가 없고 대부분의 주문이 조인 대상일 때, 조인 방식에 대한 설명으로 가장 적절한 것은?
SELECT P.프로모션명, COUNT(*), SUM(O.주문금액) FROM 주문 O, 프로모션 P WHERE O.주문일자 BETWEEN P.시작일자 AND P.종료일자 GROUP BY P.프로모션명;- 조인 컬럼에 인덱스가 없어도 해시 조인은 항상 가능하므로, 작은 프로모션 테이블을 Build Input으로 하는 해시 조인이 최선이다.
- NL 조인은 인덱스가 없어도 Inner 쪽을 PGA에서 탐색하므로, 프로모션을 Inner로 하는 NL 조인이 가장 효율적이다.
- 조인 조건이 등치(=)가 아니므로 해시 조인은 사용할 수 없고, 양쪽을 정렬한 뒤 범위로 조인하는 소트 머지 조인이 적합하다.
- 소트 머지 조인은 반드시 양쪽 집합을 모두 정렬해야 하므로, 이 경우에는 사용할 수 없다.
52. 주문은 1,000만 건, 고객은 10만 건이다. 다음 SQL의 실행계획을 고객이 Build Input이 되도록 바꾸는 방법으로 가장 적절한 것은? (조인 순서를 정하는 LEADING 힌트는 그대로 둔다.)
SELECT /*+ LEADING(O) USE_HASH(C) FULL(O) FULL(C) */ C.등급, SUM(O.주문금액) FROM 주문 O, 고객 C WHERE C.고객번호 = O.고객번호 GROUP BY C.등급; ----------------------------------------------- | Id | Operation | Name | Rows | ----------------------------------------------- | 0 | SELECT STATEMENT | | 5 | | 1 | HASH GROUP BY | | 5 | |* 2 | HASH JOIN | | 10M | | 3 | TABLE ACCESS FULL | 주문 | 10M | | 4 | TABLE ACCESS FULL | 고객 | 100K | ------------------------------------------------ 해시 조인에서 Build Input은 실행계획상 항상 아래쪽에 위치하므로 현재 계획에서 이미 고객이 Build Input이다.
SWAP_JOIN_INPUTS(C)힌트를 추가해LEADING(O) USE_HASH(C) SWAP_JOIN_INPUTS(C)로 지정한다.NO_SWAP_JOIN_INPUTS(C)힌트를 추가해 고객과 주문의 Build/Probe 역할을 서로 바꾼다.- USE_HASH(C)는 C를 Build Input으로 지정하는 힌트이므로 별도 조치 없이 고객이 Build Input이 된다.
53. 고객(C), 주문(O), 정산(S) 세 테이블을 조인하는 SQL에서 아래 실행계획을 얻기 위한 힌트로 옳은 것은? (조인 조건은
O.고객번호 = C.고객번호,S.주문번호 = O.주문번호이다.)--------------------------------------------- | 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(C S O) USE_HASH(S) USE_HASH(O) */
- /*+ LEADING(S O C) USE_HASH(O) USE_HASH(C) */
- /*+ LEADING(O S C) USE_HASH(S) USE_HASH(C) SWAP_JOIN_INPUTS(C) */
- /*+ LEADING(S O C) USE_HASH(O) USE_HASH(C) SWAP_JOIN_INPUTS(C) */
54. 다음 SQL에서 주문_X02 범위의 주문은 10,000건이고, 그 주문들의 고유한 고객번호는 2,000개이다. 설명으로 가장 적절한 것은?
SELECT O.주문번호, O.주문금액 FROM 주문 O WHERE O.주문일자 = :B1 AND EXISTS (SELECT /*+ NO_UNNEST */ 1 FROM 고객 C WHERE C.고객번호 = O.고객번호 AND C.등급 = 'VIP'); ----------------------------------------------------------- | Id | Operation | Name | ----------------------------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | FILTER | | | 2 | TABLE ACCESS BY INDEX ROWID| 주문 | |* 3 | INDEX RANGE SCAN | 주문_X02 | |* 4 | TABLE ACCESS BY INDEX ROWID| 고객 | |* 5 | INDEX UNIQUE SCAN | 고객_PK | ------------------------------------------------------------ 필터 방식에서는 옵티마이저가 비용에 따라 서브쿼리(고객)를 먼저 읽고 주문을 나중에 읽는 순서도 선택할 수 있다.
- 필터 오퍼레이션은 서브쿼리 입력값(고객번호)별 결과를 캐싱하므로, 캐시가 충분하다면 고객_PK 탐색은 약 2,000회 수행된다.
- EXISTS 서브쿼리는 조건을 만족하는 행을 찾은 뒤에도 서브쿼리 범위를 끝까지 스캔해야 결과를 확정할 수 있다.
- NO_UNNEST 힌트로 인해 서브쿼리가 조인으로 변환되어 해시 세미 조인으로 처리된다.
55. 다음 SQL의 스칼라 서브쿼리를 조인으로 바꾸려 한다. 결과가 원래 SQL과 항상 같은 것은? (정산 테이블은 주문번호당 여러 건이 있을 수 있고, 정산이 없는 주문도 있다.)
SELECT O.주문번호, O.주문금액, (SELECT SUM(S.정산금액) FROM 정산 S WHERE S.주문번호 = O.주문번호 AND S.정산상태 = '확정') AS 확정금액 FROM 주문 O WHERE O.주문일자 = :B1;- SELECT O.주문번호, O.주문금액, S.확정금액 FROM 주문 O, (SELECT 주문번호, SUM(정산금액) 확정금액 FROM 정산 WHERE 정산상태 = '확정' GROUP BY 주문번호) S WHERE O.주문일자 = :B1 AND S.주문번호(+) = O.주문번호;
- SELECT O.주문번호, O.주문금액, S.확정금액 FROM 주문 O, (SELECT 주문번호, SUM(정산금액) 확정금액 FROM 정산 WHERE 정산상태 = '확정' GROUP BY 주문번호) S WHERE O.주문일자 = :B1 AND S.주문번호 = O.주문번호;
- SELECT O.주문번호, O.주문금액, SUM(S.정산금액) 확정금액 FROM 주문 O, 정산 S WHERE O.주문일자 = :B1 AND S.주문번호 = O.주문번호 AND S.정산상태 = '확정' GROUP BY O.주문번호, O.주문금액;
- SELECT O.주문번호, O.주문금액, SUM(S.정산금액) 확정금액 FROM 주문 O, 정산 S WHERE O.주문일자 = :B1 AND S.주문번호(+) = O.주문번호 AND S.정산상태 = '확정' GROUP BY O.주문번호, O.주문금액;
56. 주문 테이블은 1,000,000건이다. 주문상태의 NDV는 5, 결제수단의 NDV는 4이며 두 컬럼 모두 히스토그램이 없고 NULL이 없다. 옵티마이저가 다음 SQL의 결과 건수(Cardinality)를 몇 건으로 추정하겠는가? (Oracle 기본 동작을 따른다.)
SELECT * FROM 주문 WHERE 주문상태 = :s AND 결제수단 = :p AND 주문일자 > :d;- 50,000건
- 12,500건
- 2,500건
- 250건
57. 옵티마이저에 대한 설명으로 가장 적절한 것은?
- FIRST_ROWS_n 모드는 처음 n건을 가장 빨리 반환하는 계획을 선택하므로, ORDER BY를 인덱스로 대체해 소트를 생략하는 계획이 선택되기 쉽다.
- Oracle의 기본 옵티마이저 모드인 ALL_ROWS는 OLTP 화면의 첫 페이지 응답 속도를 최소화하는 데 가장 적합하다.
- 비용기반 옵티마이저는
시도 = '서울' AND 구 = '강남구'처럼 서로 연관된 조건의 상관관계를 기본적으로 자동 반영해 정확한 카디널리티를 계산한다. - 규칙기반 옵티마이저는 수집된 통계정보로 테이블 크기를 반영해, 작은 테이블은 Full Scan을 우선 선택한다.
58. 다음 SQL과 실행계획에 대한 설명으로 가장 적절한 것은?
SELECT C.고객명, O.주문번호, O.주문금액 FROM 고객 C, 주문 O WHERE C.고객번호 = 'C01' AND C.등급 = 'VIP' AND O.고객번호 = C.고객번호; --------------------------------------------------------------- | Id | Operation | Name | --------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | NESTED LOOPS | | |* 2 | TABLE ACCESS BY INDEX ROWID | 고객 | |* 3 | INDEX UNIQUE SCAN | 고객_PK | | 4 | TABLE ACCESS BY INDEX ROWID BATCHED| 주문 | |* 5 | INDEX RANGE SCAN | 주문_X01 | --------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("C"."등급"='VIP') 3 - access("C"."고객번호"='C01') 5 - access("O"."고객번호"='C01')- Id 5의 조건
O.고객번호 = 'C01'은 사용자가 작성하지 않았으므로 실행계획 표시 오류이며, 실제로는 조인 조건으로만 주문_X01을 탐색한다. - 고객번호 조건이 고객 쪽에만 있으므로 주문 테이블은 Full Scan할 수밖에 없다.
- 옵티마이저가 고객 테이블을 제거하는 '불필요한 조인 제거'가 일어나 고객 테이블은 액세스하지 않았다.
- 옵티마이저가
C.고객번호 = 'C01'과 조인 조건O.고객번호 = C.고객번호로부터O.고객번호 = 'C01'을 도출하는 조건절 이행(Transitive Predicate Generation)이 일어났다.
- Id 5의 조건
59. 다음 SQL은 VIP 고객(소수)별로 최근 주문 합계를 구한다. 실행계획에 대한 설명으로 가장 적절한 것은? (주문_X01: (고객번호, 주문일자))
SELECT C.고객번호, C.고객명, O.주문합계 FROM 고객 C, (SELECT 고객번호, SUM(주문금액) 주문합계 FROM 주문 WHERE 주문일자 >= :B1 GROUP BY 고객번호) O WHERE C.등급 = 'VIP' AND O.고객번호 = C.고객번호; ------------------------------------------------------------------ | Id | Operation | Name | ------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | NESTED LOOPS | | | 2 | TABLE ACCESS BY INDEX ROWID BATCHED | 고객 | |* 3 | INDEX RANGE SCAN | 고객_X02 | | 4 | VIEW PUSHED PREDICATE | | | 5 | SORT GROUP BY | | | 6 | TABLE ACCESS BY INDEX ROWID BATCHED | 주문 | |* 7 | INDEX RANGE SCAN | 주문_X01 | ------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 3 - access("C"."등급"='VIP') 7 - access("고객번호"="C"."고객번호" AND "주문일자">=:B1)- 인라인 뷰가 메인쿼리와 Merging되어 고객과 주문을 조인한 뒤 GROUP BY를 수행했다.
- 인라인 뷰의 주문 데이터를 먼저 전체 집계한 뒤, 그 결과를 고객과 해시 조인했다.
- 고객에서 읽은 고객번호가 인라인 뷰 안으로 전달(조인 조건 Pushdown)되어, 고객 한 명마다 주문_X01로 해당 고객의 주문만 읽어 집계했다.
- VIEW PUSHED PREDICATE는 뷰 안의 조건을 메인쿼리로 끌어올리는 조건절 Pullup을 의미한다.
60. 다음 SQL의 힌트에 대한 설명으로 가장 적절한 것은? (고객_PK 인덱스는 존재한다.)
SELECT /*+ LEADING(주문) USE_NL(C) INDEX(C 고객_PK) */ O.주문번호, C.고객명 FROM 주문 O, 고객 C WHERE O.주문일자 = :B1 AND C.고객번호 = O.고객번호;- 힌트에 테이블명을 쓴 LEADING(주문)은 문법 오류이므로 SQL 실행 시 ORA- 에러가 발생한다.
- LEADING(주문)은 FROM 절에 별칭 O를 정의했으므로 무시되며, 오류 없이 나머지 유효한 힌트와 옵티마이저 판단에 따라 실행된다.
- 힌트 하나가 무효이면 같은 주석 안의 INDEX(C 고객_PK)를 포함한 모든 힌트가 함께 무시된다.
- 주문이 FROM 절의 첫 번째 테이블이므로 LEADING(주문)은 별칭과 무관하게 유효하게 적용된다.
61. SQL 구문과 실행계획에 나타나는 소트 관련 오퍼레이션을 짝지은 것으로 가장 적절하지 않은 것은?
SELECT DISTINCT 고객번호 FROM 주문→ HASH UNIQUE 또는 SORT UNIQUERANK() OVER (PARTITION BY 고객번호 ORDER BY 주문금액 DESC)→ WINDOW SORT/*+ USE_MERGE(O) */로 유도한 소트 머지 조인 → SORT JOINSELECT MAX(주문금액) FROM 주문(인덱스 미사용) → SORT AGGREGATE이며, 전체 데이터를 정렬하므로 Sort Area를 크게 사용한다.
62. 주문_X01 인덱스는 (고객번호, 주문일자)로 구성되어 있다. 다음 SQL 중 주문_X01을 이용해 SORT ORDER BY 연산을 생략할 수 있는 것을 모두 고른 것은? (주문번호는 인덱스에 포함되어 있지 않다.)
ㄱ. SELECT * FROM 주문 WHERE 고객번호 = :c ORDER BY 주문일자 DESC; ㄴ. SELECT * FROM 주문 WHERE 고객번호 IN (:c1, :c2) ORDER BY 주문일자; ㄷ. SELECT * FROM 주문 WHERE 고객번호 BETWEEN :c1 AND :c2 ORDER BY 고객번호, 주문일자; ㄹ. SELECT * FROM 주문 WHERE 고객번호 = :c ORDER BY 주문일자, 주문번호;- ㄱ, ㄷ
- ㄱ, ㄴ, ㄷ
- ㄱ, ㄷ, ㄹ
- ㄱ
63. DML 튜닝에 대한 설명으로 가장 적절하지 않은 것은? (Oracle 기준)
- UPDATE는 변경되는 컬럼을 포함한 인덱스에만 영향을 주며, 해당 인덱스에서는 기존 엔트리 삭제와 새 엔트리 삽입이 일어난다.
INSERT /*+ APPEND */ … SELECT가 수행되는 동안에도 다른 세션은 같은 테이블에 자유롭게 INSERT·UPDATE를 수행할 수 있다.- 수정 가능 조인 뷰로 주문 테이블을 갱신하려면, 조인 상대인 고객 테이블의 조인 컬럼(고객번호)에 PK나 Unique 제약이 있어야 한다.
- 테이블을 NOLOGGING으로 설정해도 일반(Conventional) INSERT의 Redo는 줄지 않으며, Direct Path Insert일 때 Redo 최소화 효과가 있다.
64. 정산 테이블은 정산일자 기준 월별 Range 파티션 테이블이다. 다음 SQL과 실행계획에 대한 설명으로 가장 적절한 것은?
SELECT 판매자번호, SUM(정산금액) FROM 정산 WHERE 정산일자 BETWEEN :D1 AND :D2 GROUP BY 판매자번호; -------------------------------------------------------------------- | Id | Operation | Name | Rows | Pstart| Pstop | -------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5000 | | | | 1 | HASH GROUP BY | | 5000 | | | |* 2 | FILTER | | | | | | 3 | PARTITION RANGE ITERATOR| | 2M | KEY | KEY | |* 4 | TABLE ACCESS FULL | 정산 | 2M | KEY | KEY | -------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(:D1<=:D2) 4 - filter("정산일자">=:D1 AND "정산일자"<=:D2)- Pstart/Pstop이 KEY로 표시된 것은 Pruning에 실패해 모든 파티션을 읽는다는 의미이다.
- PARTITION RANGE ITERATOR 아래에서 TABLE ACCESS FULL을 했으므로 정산일자 로컬 인덱스를 사용했다.
- 바인드 변수를 사용해 실행 시점에 읽을 파티션이 결정되는 동적 Pruning이며, Pruning 자체는 정상적으로 수행된다.
- 정산 테이블이 정산일자 기준 Hash 파티션이었더라도 BETWEEN 조건으로 같은 Pruning 효과를 얻을 수 있다.
65. 다음은
PARALLEL(C 4) PARALLEL(O 4) PQ_DISTRIBUTE(O HASH HASH)힌트로 수행한 병렬 실행계획이다. 설명으로 가장 적절한 것은?---------------------------------------------------------------------------- | Id | Operation | Name | TQ |IN-OUT| PQ Distrib | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | | | 1 | PX COORDINATOR | | | | | | 2 | PX SEND QC (RANDOM) | :TQ10002 | Q1,02 | P->S | QC (RAND) | | 3 | HASH JOIN BUFFERED | | Q1,02 | PCWP | | | 4 | PX RECEIVE | | Q1,02 | 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 | | | 8 | PX RECEIVE | | Q1,02 | PCWP | | | 9 | PX SEND HASH | :TQ10001 | Q1,01 | P->P | HASH | | 10 | PX BLOCK ITERATOR | | Q1,01 | PCWC | | | 11 | TABLE ACCESS FULL | 주문 | Q1,01 | PCWP | | ----------------------------------------------------------------------------- IN-OUT 컬럼의 S->P는 병렬 처리에서 가장 효율적인 분배 형태이므로, 이 계획에도 S->P가 나타나도록 유도하는 것이 바람직하다.
- 병렬 DML이 활성화된 INSERT … SELECT였다면 LOAD AS SELECT 오퍼레이션이 PX COORDINATOR보다 위에 나타난다.
- 이 계획에서 고객과 주문은 비파티션 인덱스를 Range Scan하여 병렬로 읽는다.
- IN-OUT 컬럼의 P->P는 두 병렬 서버 집합 사이에 데이터 재분배가 일어난다는 뜻이며, DOP가 4이므로 이 SQL에는 병렬 서버 8개가 사용된다.
66. 다음 SQL은 주문 테이블을 두 번 읽는다. 결과가 항상 같으면서 주문 테이블을 한 번만 읽도록 바꾼 SQL로 옳은 것은? (주문금액은 NOT NULL이며, 주문상태에는 '완료', '취소' 외에 '배송중' 등도 있다.)
SELECT 고객번호, SUM(완료금액) 완료금액, SUM(취소금액) 취소금액 FROM (SELECT 고객번호, 주문금액 완료금액, 0 취소금액 FROM 주문 WHERE 주문일자 LIKE '202501%' AND 주문상태 = '완료' UNION ALL SELECT 고객번호, 0, 주문금액 FROM 주문 WHERE 주문일자 LIKE '202501%' AND 주문상태 = '취소') GROUP BY 고객번호;- SELECT 고객번호 , SUM(CASE WHEN 주문상태 = '완료' THEN 주문금액 ELSE 0 END) 완료금액 , SUM(CASE WHEN 주문상태 = '취소' THEN 주문금액 ELSE 0 END) 취소금액 FROM 주문 WHERE 주문일자 LIKE '202501%' AND 주문상태 IN ('완료', '취소') GROUP BY 고객번호;
- SELECT 고객번호 , SUM(CASE WHEN 주문상태 = '완료' THEN 주문금액 ELSE 0 END) 완료금액 , SUM(CASE WHEN 주문상태 = '취소' THEN 주문금액 ELSE 0 END) 취소금액 FROM 주문 WHERE 주문일자 LIKE '202501%' GROUP BY 고객번호;
- SELECT 고객번호 , SUM(CASE WHEN 주문상태 = '완료' THEN 주문금액 END) 완료금액 , SUM(CASE WHEN 주문상태 = '취소' THEN 주문금액 END) 취소금액 FROM 주문 WHERE 주문일자 LIKE '202501%' AND 주문상태 IN ('완료', '취소') GROUP BY 고객번호;
- SELECT 고객번호 , SUM(CASE WHEN 주문상태 = '완료' THEN 주문금액 ELSE 0 END) 완료금액 , SUM(CASE WHEN 주문상태 = '취소' THEN 주문금액 ELSE 0 END) 취소금액 FROM 주문 WHERE 주문일자 LIKE '202501%' AND 주문상태 IN ('완료', '취소') GROUP BY 고객번호, 주문상태;
67. Oracle에서 두 세션이 다음 순서로 SQL을 수행한다. 세션 2에서 처음으로 대기(블로킹)가 발생하는 시점은? (주문 테이블의 PK는 주문번호이며, 자동 커밋은 꺼져 있다.)
시점 세션 1 세션 2 ---- ----------------------------------------------- ------------------------------------------------ t1 UPDATE 주문 SET 주문상태 = '취소' WHERE 주문번호 = 'O1'; t2 (가) UPDATE 주문 SET 주문상태 = '완료' WHERE 주문번호 = 'O2'; t3 (나) SELECT 주문상태 FROM 주문 WHERE 주문번호 = 'O1'; t4 (다) SELECT 주문상태 FROM 주문 WHERE 주문번호 = 'O1' FOR UPDATE; t5 (라) INSERT INTO 주문(주문번호, 주문상태) VALUES ('O9', '접수');- (가)
- (나)
- (다)
- (라)
68. Lock에 대한 설명으로 가장 적절한 것은?
- Oracle에서도 한 트랜잭션이 갱신한 행이 많아지면 메모리를 절약하기 위해 로우 Lock이 테이블 Lock으로 에스컬레이션된다.
- 교착상태(Deadlock)가 발생하면 Oracle은 한쪽 세션의 문장을 롤백하고 에러를 반환하며, 그 세션의 트랜잭션 전체가 자동으로 롤백되지는 않는다.
- Oracle에서 일반 SELECT 문은 읽는 행에 공유 Lock을 걸기 때문에, 다른 세션의 UPDATE와 서로 블로킹된다.
- 어떤 트랜잭션이 테이블에 RX 모드 TM Lock을 걸면, 커밋할 때까지 다른 트랜잭션은 그 테이블의 어떤 행도 갱신할 수 없다.
69. 격리성 수준이 READ COMMITTED인 환경에서 두 트랜잭션이 다음과 같이 수행되었다. TX1에서 나타난 현상과, ANSI 기준으로 이 현상을 방지할 수 있는 가장 낮은 격리성 수준을 바르게 짝지은 것은?
시점 TX1 TX2 ---- -------------------------------------------- ----------------------------------------- t1 SELECT SUM(주문금액) FROM 주문 WHERE 고객번호 = 'C01'; -- 결과 80000 t2 INSERT INTO 주문(주문번호, 고객번호, 주문금액) VALUES ('O9', 'C01', 15000); t3 COMMIT; t4 SELECT SUM(주문금액) FROM 주문 WHERE 고객번호 = 'C01'; -- 결과 95000- Dirty Read — READ COMMITTED
- Non-Repeatable Read — REPEATABLE READ
- Phantom Read — REPEATABLE READ
- Phantom Read — SERIALIZABLE
70. 상품 재고를 차감하는 다음 로직에 대한 설명으로 가장 적절한 것은?
-- 1) 조회 (Lock 없음) SELECT 재고수량, 변경일시 INTO :qty, :mod_ts FROM 상품 WHERE 상품번호 = :p; -- 2) 사용자 확인·처리 (수 초 소요) -- 3) 차감 UPDATE 상품 SET 재고수량 = 재고수량 - :n , 변경일시 = SYSTIMESTAMP WHERE 상품번호 = :p AND 변경일시 = :mod_ts; -- 4) SQL%ROWCOUNT = 0 이면 ?- UPDATE 결과가 0건이면 조회 이후 다른 트랜잭션이 해당 상품을 변경한 것이므로, 재조회 후 다시 시도하도록 처리하는 낙관적 동시성 제어 방식이다.
- WHERE 절에서 변경일시를 비교하므로, UPDATE의 SET 절에서 변경일시를 새 값으로 갱신하지 않아도 갱신 충돌을 정확히 감지할 수 있다.
- 첫 번째 SELECT 시점에 상품 행에 Lock이 걸리므로, 사용자가 처리하는 동안 다른 트랜잭션은 해당 상품을 변경할 수 없다.
- 이 방식은 데이터를 읽는 시점에 Lock을 거는 비관적 동시성 제어보다 항상 데이터 일관성이 낮으므로 재고 관리에는 사용할 수 없다.
AI가 SQLP 출제 범위를 바탕으로 새로 만든 예상 문제이며, 실제 기출문제가 아닙니다.