SQLD 5회 기출복원 풀이
목차 52
1과목 데이터 모델링의 이해 (문 1~10)
문 1. 내부 스키마에 대한 적절한 설명을 옳은 것의 개수로 가장 적절한 것은?
(가) 사용자나 응용 프로그램이 접근하는 데이터의 표현 방식을 정의한다. (나) 데이터가 실제로 저장되는 물리적 구조와 저장 장치의 접근 경로를 정의한다. (다) 파일의 저장 방식, 인덱스 구조, 데이터 압축 기법 등을 포함한다. (라) 조직 전체의 관점에서 데이터 구조를 통합적으로 표현한다.
- ① 1
- ② 2
- ③ 3
- ④ 4
정답 및 해설 보기
정답 ②
내부 스키마는 데이터베이스의 가장 하위 계층으로, 데이터가 실제 저장 장치에 어떻게 저장되고 접근되는지를 정의한다. (나) 물리적 저장 구조·접근 경로, (다) 파일 저장 방식·인덱스 구조·데이터 압축은 모두 물리적 저장에 관한 설명이므로 내부 스키마에 해당한다. (가)는 외부 스키마(사용자/응용 관점), (라)는 개념 스키마(조직 전체 통합 관점)에 대한 설명이다. 따라서 옳은 것은 (나)·(다) 2개다.
🔑 암기 3단계 스키마 — 외부(사용자·응용) · 개념(조직 통합) · 내부(물리적 저장).
문 2. 엔터티를 명명하는 방법으로 가장 적절하지 않은 것은?
- ① 현업의 업무 용어를 사용하며, 만약 용어가 길고 복잡하다면 약어 사용이 권장된다.
- ② 복수명사 대신 단수명사를 사용한다.
- ③ 모든 엔터티에서 유일하게 이름이 부여되어야 한다.
- ④ 엔터티 생성 의미대로 이름을 부여한다.
정답 및 해설 보기
정답 ①
엔터티 명명에서는 약어 사용을 지양하는 것이 원칙이다. 약어는 가독성을 떨어뜨리고 의미 전달이 모호할 수 있어, 시간이 지나거나 다른 사람이 볼 때 의미 파악이 어려워진다. ② 단수명사 사용, ③ 유일한 이름 부여, ④ 생성 의미대로 명명은 모두 올바른 명명 원칙이다.
⚠️ 함정 '약어 사용이 권장된다'처럼 권장/지양 방향이 뒤집힌 선지를 조심한다 — 약어는 지양이 원칙이다.
문 3. 아래 테이블을 기준으로 엔터티, 인스턴스, 속성, 속성값 간의 관계를 올바르게 짝지은 것은?

- ① ㉠ 엔터티, ㉡ 인스턴스, ㉢ 속성
- ② ㉠ 엔터티, ㉡ 인스턴스, ㉢ 속성값
- ③ ㉠ 속성, ㉡ 인스턴스, ㉢ 속성값
- ④ ㉠ 속성, ㉡ 엔터티, ㉢ 인스턴스
정답 및 해설 보기
정답 ③
- 엔터티: 관리해야 할 개체(테이블 전체)를 의미한다.
- 인스턴스: ㉡과 같이 엔터티의 개별적인 행(튜플, 레코드)을 의미한다.
- 속성: ㉠과 같이 테이블의 열(칼럼, 필드)을 의미한다.
- 속성값: ㉢과 같이 속성에 저장된 개별적인 값(셀의 값)을 의미한다.
따라서 ㉠ 속성, ㉡ 인스턴스, ㉢ 속성값이다.
🔑 암기 엔터티=표 전체 · 속성=열 · 인스턴스=행 · 속성값=칸의 값.
문 4. 아래과 같이 테이블을 [A]에서 [B]로 변환하였을 때, 이에 대한 설명으로 가장 적절하지 않은 것은? (단, [A] 테이블에서 기본 키는 [학생ID, 신청과목]이며, 신청과목이 담당교수를 결정하는 함수 종속성(신청과목 → 담당교수)이 존재한다고 가정함) 🎯 고난도
[A]
| 학생ID | 신청과목 | 담당교수 |
|---|---|---|
| M-173 | 빅데이터분석 | 백찬영 |
| M-302 | 선형대수 | 김재직 |
| M-98 | 빅데이터분석 | 백찬영 |
| H-398 | 통계학입문 | 박종선 |
| M-29 | 선형대수 | 김재직 |
[B]
| 학생ID | 신청과목 |
|---|---|
| M-173 | 빅데이터분석 |
| M-302 | 선형대수 |
| M-98 | 빅데이터분석 |
| H-398 | 통계학입문 |
| M-29 | 선형대수 |
| 신청과목 | 담당교수 |
|---|---|
| 빅데이터분석 | 백찬영 |
| 선형대수 | 김재직 |
| 통계학입문 | 박종선 |
- ① [A]는 제1정규형을 위반하고 있다.
- ② [B]는 제2정규형을 만족한다.
- ③ [B]는 제3정규형까지 만족하는 형태이다.
- ④ [B]에서는 학생ID별 신청과목에 따른 담당교수를 조회하는 작업의 효율성이 낮아질 수 있다.
정답 및 해설 보기
정답 ①
[A] 테이블은 모든 속성값이 개별적인(원자) 값만 포함하므로 제1정규형(1NF)을 만족한다. [A]의 문제는 기본 키의 일부인 '신청과목'에만 종속된 '담당교수'(부분 함수 종속)가 존재해 제2정규형(2NF)을 위반한다는 점이다. 따라서 '1NF를 위반한다'는 ①이 틀린 설명이다. ② [B]는 담당교수를 별도 테이블로 분리해 부분 함수 종속을 제거했으므로 2NF를 만족한다. ③ 이행적 종속도 함께 제거되어 3NF까지 만족한다. ④ 담당교수 조회 시 JOIN이 필요해져 성능이 저하될 수 있다.
⚠️ 함정 부분 함수 종속이 있으면 1NF가 아니라 2NF 위반이다. 1NF 위반은 다중값(원자성 위배)일 때다(문 8과 비교).
문 5. 두 엔터티 간의 관계에서 특정 엔터티가 반드시 관계에 참여해야 하는지 여부를 나타내는 용어로 가장 적절한 것은?
- ① 관계명
- ② 관계차수
- ③ 관계선택사양
- ④ 관계정의
정답 및 해설 보기
정답 ③
관계선택사양(Optionality)은 관계에 참여하는 엔터티가 필수적으로 참여해야 하는지, 또는 선택적으로 참여할 수 있는지를 나타낸다. 필수적 관계는 NULL 값을 가질 수 없고, 선택적 관계는 NULL 값을 가질 수 있다. ② 관계차수(Cardinality)는 1:1, 1:N, N:M처럼 몇 대 몇으로 참여하는지를 나타내는 개념이므로 구분해야 한다.
🔑 암기 필수/선택 참여 여부 = 관계선택사양 / 몇 대 몇 = 관계차수.
문 6. 기본 엔터티의 특징으로 가장 적절하지 않은 것은?
- ① 발생 시점이나 상속 관계에 따라 분류되는 엔터티의 유형에 해당된다.
- ② 독립적으로 존재할 수 있다.
- ③ 다른 엔터티로부터 주식별자를 상속받는다.
- ④ 주요 데이터베이스 테이블로 활용될 가능성이 높다.
정답 및 해설 보기
정답 ③
기본 엔터티는 고유한 주식별자를 가지며, 다른 엔터티로부터 주식별자를 상속받지 않는다. 다른 엔터티에 의존해 주식별자를 상속받는 것은 행위 엔터티나 중심 엔터티 등 종속적인 엔터티의 특징이다. ①②④는 모두 기본 엔터티의 올바른 특징이다.
🔑 암기 기본 엔터티 = 독립적 존재 · 고유 주식별자 소유(상속받지 않음).
문 7. 외래 키에 대한 설명으로 가장 적절하지 않은 것은?
- ① 외래 키는 다른 테이블의 기본 키를 참조할 수 있다.
- ② 외래 키는 참조된 기본 키 값이 변경되거나 삭제될 때, 관계를 유지하기 위한 제약 조건을 설정할 수 있다.
- ③ 외래 키는 단일 속성뿐만 아니라, 두 개 이상의 속성(복합 키)으로도 구성될 수 있다.
- ④ 외래 키를 설정하면 개체 무결성을 유지하여 데이터의 일관성을 보장할 수 있다.
정답 및 해설 보기
정답 ④
개체 무결성(Entity Integrity)은 기본 키에 대한 무결성 규칙으로, 기본 키의 NULL과 중복을 허용하지 않아 각 행을 고유하게 식별한다. 외래 키가 담당하는 것은 개체 무결성이 아니라 참조 무결성(Referential Integrity)으로, 부모 테이블에 존재하는 값만 자식 테이블에서 참조할 수 있도록 보장한다. ①②③은 외래 키의 올바른 특징이다.
🔑 암기 개체 무결성 → 기본 키(PK) / 참조 무결성 → 외래 키(FK).
문 8. 아래 테이블에서 필요한 정규화 단계로 가장 적절한 것은?
[교수]
| 교번(PK) | 교수명 | 학과 | 담당과목 |
|---|---|---|---|
| F-1730 | 백찬영 | 통계학과 | 빅데이터분석, 데이터마이닝 |
| F-1801 | 김재직 | 수학교육과 | 선형대수, 집합론 |
| F-2001 | 박종선 | 통계학과 | 통계학입문, 수치해석, 확률론 |
- ① 1차 정규화
- ② 2차 정규화
- ③ 3차 정규화
- ④ BCNF
정답 및 해설 보기
정답 ①
'담당과목' 칼럼 한 칸에 '빅데이터분석, 데이터마이닝'처럼 여러 개의 값이 들어가 있다. 한 개의 속성 안에 여러 값이 들어가는 것은 제1정규형(1NF, 모든 속성값은 원자값)을 위배한다. 따라서 가장 먼저 필요한 것은 다중값을 분리하는 1차 정규화다.
⚠️ 함정 한 칸에 콤마로 나열된 다중값 → 1NF 위배(원자성). 부분 함수 종속(2NF)·이행 종속(3NF)과 구분한다(문 4와 비교).
문 9. 아래에서 설명하는 트랜잭션의 특성으로 가장 적절한 것은?
동시에 실행되는 여러 트랜잭션이 서로 영향을 주지 않도록 독립적으로 수행되며, 중간 수행 결과가 다른 트랜잭션에 노출되지 않도록 보장하는 특성이다.
- ① 원자성(Atomicity)
- ② 의존성(Dependency)
- ③ 고립성(Isolation)
- ④ 영속성(Durability)
정답 및 해설 보기
정답 ③
고립성(Isolation)은 여러 트랜잭션이 동시에 실행될 때 서로 영향을 주지 않고 독립적으로 수행되며, 트랜잭션이 완료되기 전 중간 결과가 다른 트랜잭션에 노출되지 않도록 보장하는 특성이다. ① 원자성은 All or Nothing(전부 반영 또는 전부 취소), ④ 영속성은 COMMIT한 결과의 영구 보존을 뜻한다. ② 의존성은 ACID 특성이 아니다.
🔑 암기 ACID — 원자성(All or Nothing) · 일관성 · 고립성(독립 수행·중간 결과 비노출) · 영속성(영구 저장).
문 10. 반정규화에 대한 설명으로 가장 적절하지 않은 것은?
- ① 자주 조회되는 데이터를 미리 저장하거나 캐싱하여 성능을 높이는 방식으로 반정규화를 적용할 수 있다.
- ② 대량의 데이터를 활용한 보고서나 통계 분석에서 조회 속도를 높이기 위해 집계 테이블을 생성하는 방식으로 반정규화를 적용할 수 있다.
- ③ 특정 테이블 조회 시 디스크 I/O 부하가 크다면, 데이터를 중복 저장하여 성능을 최적화할 수 있다.
- ④ 데이터의 중복을 최소화하고 무결성을 유지하는 것이 중요한 경우, 반정규화를 수행하는 것이 적절하다.
정답 및 해설 보기
정답 ④
반정규화는 조회 성능을 높이기 위해 의도적으로 중복을 허용하는 기법이다. 데이터의 중복을 최소화하고 무결성을 유지하는 것이 중요한 경우에는 반정규화가 아니라 정규화를 유지하는 것이 적절하므로 ④가 정반대 설명이다. ①②③은 모두 성능 향상을 위한 반정규화의 대표 사례다.
💡 정리 반정규화 = 성능 중심(중복 허용) / 정규화 = 무결성·중복 최소화 중심.
2과목 SQL 기본 및 활용 (문 11~50)
문 11. 데이터 조작어에 해당되지 않는 명령어는?
- ① INSERT
- ② ALTER
- ③ UPDATE
- ④ DELETE
정답 및 해설 보기
정답 ②
데이터 조작어(DML)는 테이블 안의 데이터를 조작하는 명령어로 INSERT·UPDATE·DELETE(·SELECT)가 있다. ALTER는 테이블의 구조를 변경하는 명령어로 데이터 정의어(DDL)에 해당한다.
🔑 암기 DML(데이터 조작) = INSERT·UPDATE·DELETE·SELECT / DDL(구조 정의) = CREATE·ALTER·DROP·TRUNCATE.
문 12. COL1의 값이 NULL이 아닌 경우를 조회하는 SQL로 가장 적절한 것은?
①
SELECT * FROM TABLE1
WHERE COL1 NOT NULL;
②
SELECT * FROM TABLE1
WHERE COL1 != NULL;
③
SELECT * FROM TABLE1
WHERE COL1 ^= NULL;
④
SELECT * FROM TABLE1
WHERE COL1 IS NOT NULL;
정답 및 해설 보기
정답 ④
NULL은 값이 아닌 상태이므로 일반 비교 연산자(=, !=, ^=, <>)로는 비교할 수 없다. NULL 여부는 IS NULL / IS NOT NULL로만 정확히 확인할 수 있다. ②·③처럼 작성하면 항상 거짓(UNKNOWN)이 되어 아무 행도 반환되지 않고, ①은 문법 오류다.
⚠️ 함정 COL1 != NULL·COL1 = NULL은 결과가 0건이다. 반드시 IS (NOT) NULL을 쓴다.
문 13. 아래 SQL의 실행 결과로 가장 적절하지 않은 것은?
①
TRIM('D' FROM 'DAABBCCDD') = 'AABBCC'
②
REPLACE('A0BB0CC0', '0B', 'X') = 'AXB0CC0'
③
INSTR(REPLACE('AB0B0CC0', '0'), 'C', 2) = 5
④
SUBSTR('AABBCCDD', INSTR('AABBCCDD', 'B', 2, 2), 3) = 'BCC'
정답 및 해설 보기
정답 ③
REPLACE(문자열, 찾을 문자)처럼 바꿀 문자를 생략하면 해당 문자를 삭제한다. REPLACE('AB0B0CC0', '0')은 '0'을 모두 제거해 'ABBCC'가 되고, INSTR('ABBCC', 'C', 2)는 2번째 위치부터 'C'를 찾아 처음 위치인 4를 반환한다. 보기 ③은 결과를 5라고 적었으므로 틀렸다(실제 4). ① TRIM('D' FROM ...)은 양끝의 'D'를 제거해 'AABBCC', ② REPLACE(..., '0B', 'X')는 첫 '0B'만 바꿔 'AXB0CC0', ④ INSTR(..., 'B', 2, 2)는 2번째 위치부터 2번째 'B'를 찾아 4 → SUBSTR('AABBCCDD', 4, 3) = 'BCC'로 모두 맞다.
⚠️ 함정 INSTR(문자열, 찾을 문자, 시작 위치, N번째) — 시작 위치와 발생 순서를 혼동하지 않는다.
문 14. 별칭(Alias)에 대한 설명으로 가장 적절하지 않은 것은? (단, DBMS는 오라클로 가정함)
- ① 칼럼에 별칭을 지정할 때, AS 키워드를 사용하거나 생략할 수 있다.
- ② 셀프 조인(SELF JOIN)을 수행하는 경우 별칭을 반드시 지정해야 한다.
- ③ 별칭은 칼럼 앞과 뒤에 둘 다 지정할 수 있다.
- ④ 별칭을 사용할 경우, 대소문자를 구별하려면 큰따옴표(" ")를 이용해야 한다.
정답 및 해설 보기
정답 ③
별칭은 항상 칼럼 뒤에만 작성해야 하며, 칼럼 앞에 작성하면 문법 오류가 발생한다(SALARY 월급은 가능, 월급 SALARY는 불가). ① AS는 생략 가능, ② 셀프 조인은 같은 테이블을 두 번 참조하므로 별칭으로 구분 필수, ④ 대소문자·공백을 구별하려면 큰따옴표 사용은 모두 맞다.
🔑 암기 별칭은 항상 원본 칼럼 뒤에 위치한다.
문 15. 테이블 생성 시 오류가 발생하는 SQL은? (단, DBMS는 오라클로 가정함)
①
CREATE TABLE 2025_EMP (
EMP_ID VARCHAR2(20),
NAME VARCHAR2(100));
②
CREATE TABLE EMP_2025 (
EMP_ID VARCHAR2(20),
NAME VARCHAR2(100));
③
CREATE TABLE EMP#2025 (
EMP_ID VARCHAR2(20),
NAME VARCHAR2(100));
④
CREATE TABLE "emp2025" (
EMP_ID VARCHAR2(20),
NAME VARCHAR2(100));
정답 및 해설 보기
정답 ①
오라클의 객체 명명 규칙에서 이름은 반드시 문자로 시작해야 하며, 숫자로 시작하면 오류가 발생한다. 2025_EMP는 숫자로 시작해 오류다. ② 문자 시작 후 숫자 사용, ③ 특수문자 _·$·# 사용, ④ 큰따옴표로 묶기는 모두 가능하다.
🔑 암기 객체 이름은 반드시 문자로 시작(숫자 시작 불가). 허용 특수문자 _ $ #.
문 16. UNION ALL에 대한 설명으로 가장 적절한 것은?
- ① UNION ALL은 집합 간의 결과에서 중복 행을 제외하고 결과를 반환한다.
- ② UNION ALL은 집합 간의 결과가 중복되지 않은 경우, UNION과 동일한 결과를 반환한다.
- ③ UNION ALL은 UNION – INTERSECT와 동일한 결과를 반환한다.
- ④ UNION ALL은 스키마가 다른 테이블을 병합할 때 사용된다.
정답 및 해설 보기
정답 ②
UNION ALL은 중복 제거 없이 두 결과를 그대로 합친다. 두 집합 간에 중복된 행이 없으면 중복을 제거하는 UNION과 결과가 동일해지므로 ②가 옳다. ① 중복 제외는 UNION에 대한 설명, ③ UNION ALL은 UNION + INTERSECT와 같고, ④ UNION ALL은 열 개수와 데이터 타입이 같아야 하며 스키마가 다른 테이블 병합은 JOIN의 영역이다.
⚠️ 함정 UNION은 중복 제거(정렬 수반) / UNION ALL은 중복 포함(정렬 없음·성능 유리).
문 17. 아래 SQL의 실행 결과는?
[TAB]
| COL1 | COL2 |
|---|---|
| 1 | 2025-06-30 00:00:00 |
| 1 | 2025-05-31 00:00:00 |
| 2 | 2025-04-21 00:00:00 |
| 2 | 2025-02-21 00:00:00 |
| 2 | 2025-03-21 00:00:00 |
SELECT COL1, COL2
FROM TAB
ORDER BY 1, 2 DESC;
①
| COL1 | COL2 |
|---|---|
| 1 | 2025-05-31 00:00:00 |
| 1 | 2025-06-30 00:00:00 |
| 2 | 2025-02-21 00:00:00 |
| 2 | 2025-03-21 00:00:00 |
| 2 | 2025-04-21 00:00:00 |
②
| COL1 | COL2 |
|---|---|
| 1 | 2025-06-30 00:00:00 |
| 1 | 2025-05-31 00:00:00 |
| 2 | 2025-04-21 00:00:00 |
| 2 | 2025-03-21 00:00:00 |
| 2 | 2025-02-21 00:00:00 |
③
| COL1 | COL2 |
|---|---|
| 2 | 2025-04-21 00:00:00 |
| 2 | 2025-03-21 00:00:00 |
| 2 | 2025-02-21 00:00:00 |
| 1 | 2025-06-30 00:00:00 |
| 1 | 2025-05-31 00:00:00 |
④
| COL1 | COL2 |
|---|---|
| 2 | 2025-02-21 00:00:00 |
| 2 | 2025-03-21 00:00:00 |
| 2 | 2025-04-21 00:00:00 |
| 1 | 2025-05-31 00:00:00 |
| 1 | 2025-06-30 00:00:00 |
정답 및 해설 보기
정답 ②
ORDER BY 1, 2 DESC에서 숫자는 SELECT 절의 컬럼 순서를 가리킨다. 1차로 COL1 오름차순, 그 안에서 동일한 COL1끼리 2차로 COL2 내림차순 정렬한다. COL1이 1인 행은 06-30·05-31 순, COL1이 2인 행은 04-21·03-21·02-21 순이 되므로 ②가 맞다. 이 쿼리는 정확히 ②의 순서를 반환한다.
🔑 암기 ORDER BY n의 n은 SELECT 절 n번째 컬럼. 정렬 옵션은 각 컬럼에 개별 적용(2번째에만 DESC).
문 18. 아래 SQL의 실행 결과는?
[TAB_A]
| 사원ID | 사원명 |
|---|---|
| 1 | 김미영 |
| 2 | 김수영 |
| 3 | 김영수 |
| 4 | 남궁영준 |
| 5 | 최선영 |
| 6 | 최영 |
SELECT COUNT(*)
FROM TAB_A
WHERE REGEXP_LIKE(사원명, '^..영$');
- ① 3
- ② 4
- ③ 5
- ④ 6
정답 및 해설 보기
정답 ①
정규식 ^..영$에서 ^는 시작, ..은 임의의 두 글자, 영$은 끝 글자가 '영'임을 뜻하므로 총 3글자이면서 마지막 글자가 '영'인 이름을 찾는다. 김미영·김수영·최선영이 각각 3글자이고 '영'으로 끝나 조건을 만족한다(김영수는 끝 글자가 '수', 남궁영준은 4글자, 최영은 2글자라 제외). 이 쿼리는 정확히 3건을 반환한다.
⚠️ 함정 ..은 글자 수 2를 고정한다 — ^..영$은 정확히 3글자만 매치(%영 같은 가변 길이와 다름).
문 19. 아래 빈칸 ㉠에 들어갈 명령어로 가장 적절한 것은?
㉠ 은/는 SQL에서 테이블의 구조를 유지하면서 테이블의 데이터만 삭제하는 명령어이다. WHERE 절을 사용하면 특정 조건을 만족하는 행만 삭제할 수 있고, WHERE 절을 생략하면 모든 데이터가 삭제된다. 트랜잭션을 적용 시 ROLLBACK(복구)이 가능하며, 삭제 후에도 테이블 구조는 그대로 유지된다.
- ① DELETE
- ② DROP
- ③ REMOVE
- ④ TRUNCATE
정답 및 해설 보기
정답 ①
WHERE 절로 선택적 삭제가 가능하고 ROLLBACK으로 복구할 수 있으며 테이블 구조가 유지되는 명령어는 DELETE(DML)다. ④ TRUNCATE는 WHERE 절을 쓸 수 없고 DDL이라 자동 COMMIT되어 ROLLBACK이 불가능하다. ② DROP은 테이블 구조 자체를 제거하고, ③ REMOVE는 표준 SQL 명령어가 아니다.
⚠️ 함정 DELETE(DML, ROLLBACK 가능) vs TRUNCATE(DDL, 자동 COMMIT·WHERE 불가·ROLLBACK 불가).
문 20. COMMIT, ROLLBACK 명령어에 대한 적절한 설명을 모두 고른 것은? (단, DBMS는 오라클로 가정함)
(가) COMMIT은 모든 트랜잭션 작업을 영구적으로 저장하는 역할을 한다. (나) COMMIT을 실행하면 SAVEPOINT가 자동으로 생성된다. (다) ROLLBACK은 COMMIT 이후에도 작업을 되돌릴 수 있다. (라) DDL 명령어는 기본적으로 자동 COMMIT이 되기 때문에 ROLLBACK이 불가능하다.
- ① (가), (나)
- ② (가), (라)
- ③ (나), (다)
- ④ (나), (라)
정답 및 해설 보기
정답 ②
(가) COMMIT은 트랜잭션 작업을 영구 저장하므로 옳고, (라) DDL은 자동 COMMIT되어 ROLLBACK이 불가능하므로 옳다. (나) SAVEPOINT는 사용자가 명시적으로 지정하는 저장점이지 COMMIT으로 자동 생성되지 않으며, (다) COMMIT 이후에는 ROLLBACK이 불가능하다.
🔑 암기 COMMIT = 영구 저장(이후 ROLLBACK 불가) / DDL = 자동 COMMIT / SAVEPOINT = 수동 지정.
문 21. 아래 SQL의 실행 결과를 순서대로 나열한 것은?
SELECT 100/DECODE(NULL, NULL, 0, 10)
FROM DUAL;
SELECT NVL(0/100, 999)
FROM DUAL;
SELECT 100/NVL(NULL, 10)
FROM DUAL;
- ① 오류 발생, 0, 10
- ② 오류 발생, 999, NULL
- ③ NULL, 0, 10
- ④ NULL, 999, 10
정답 및 해설 보기
정답 ①
100/DECODE(NULL, NULL, 0, 10):DECODE(NULL, NULL, 0, 10)이 0을 반환해100/0이 되고, 0으로 나누는 연산은 오류가 발생한다.NVL(0/100, 999):0/100은 0이며, 0은 NULL이 아니므로 NVL이 적용되지 않아 결과는 0이다.100/NVL(NULL, 10):NVL(NULL, 10)이 10으로 대체되어100/10= 10이다.
따라서 순서대로 오류 발생, 0, 10이다. 실제 실행 시 첫 번째 쿼리는 0으로 나누기 오류(ORA-01476)가 발생함을 확인했다.
⚠️ 함정 0으로 나누면 오류 / NULL과의 산술 연산 결과는 NULL. NVL(0/100, 999)의 0은 NULL이 아니라 그대로 0이다.
문 22. 아래 SQL의 실행 결과는?
[학생1]
| 학생ID | 학생명 | 학과명 |
|---|---|---|
| 100 | 김영수 | 국문학과 |
| 101 | 정영재 | 경영학과 |
| 102 | 박진영 | 경영학과 |
| 103 | 김철수 | 철학과 |
| 104 | 최준식 | 수학과 |
[학생2]
| 학생ID | 학생명 | 학과명 |
|---|---|---|
| 100 | 김영수 | 국문학과 |
| 101 | 정영재 | 경영학과 |
| 103 | 김철수 | 철학과 |
| 105 | 허재웅 | 철학과 |
SELECT 학생명, 학과명
FROM 학생1
WHERE 학생ID IN (101, 103)
UNION ALL
SELECT 학생명, 학과명
FROM 학생2
WHERE 학생ID IN (101, 103)
ORDER BY 1;
①
| 학생명 | 학과명 |
|---|---|
| 정영재 | 경영학과 |
| 김철수 | 철학과 |
②
| 학생명 | 학과명 |
|---|---|
| 정영재 | 경영학과 |
| 박진영 | 경영학과 |
| 김철수 | 철학과 |
③
| 학생명 | 학과명 |
|---|---|
| 정영재 | 경영학과 |
| 정영재 | 경영학과 |
| 김철수 | 철학과 |
| 김철수 | 철학과 |
④
| 학생명 | 학과명 |
|---|---|
| 김철수 | 철학과 |
| 김철수 | 철학과 |
| 정영재 | 경영학과 |
| 정영재 | 경영학과 |
정답 및 해설 보기
정답 ④
각 SELECT는 학생ID 101·103을 조회해 (정영재, 경영학과)·(김철수, 철학과)를 반환한다. UNION ALL은 중복을 제거하지 않으므로 두 결과가 그대로 합쳐져 총 4건이 된다. 마지막 ORDER BY 1(학생명 오름차순)을 적용하면 '김철수, 김철수, 정영재, 정영재' 순이 되어 ④가 맞다. 이 쿼리는 정확히 ④의 4행을 반환한다.
🔑 암기 ORDER BY는 집합 연산 전체 결과에 마지막으로 한 번 적용된다.
문 23. 아래 테이블을 참고할 때 실행 결과가 다른 하나는?
[고객]
| 고객ID | 고객명 |
|---|---|
| 1 | 박미영 |
| 2 | 김영자 |
| 3 | 최철민 |
| NULL | 최창안 |
| NULL | 김철수 |
| 6 | 김영희 |
①
SELECT COUNT(4) FROM 고객;
②
SELECT COUNT(고객ID) FROM 고객;
③
SELECT COUNT(*) FROM 고객
WHERE 고객ID IS NOT NULL;
④
SELECT COUNT(*) FROM 고객
WHERE 고객ID IN (1, 2, 3, 6, NULL);
정답 및 해설 보기
정답 ①
COUNT(4)·COUNT(상수)는 NULL과 무관하게 전체 행 수를 세므로 6을 반환한다. ② COUNT(고객ID)는 NULL인 행을 제외해 4, ③ 고객ID IS NOT NULL 조건도 4, ④ IN (1,2,3,6,NULL)은 NULL을 비교하지 못해 무시되므로 IN (1,2,3,6)과 같아 4를 반환한다. 따라서 결과가 다른 하나는 6인 ①이다. 실행 결과 ①=6, ②③④=4임을 확인했다.
⚠️ 함정 COUNT(*)·COUNT(상수) = 전체 행 / COUNT(칼럼) = NULL 제외 / IN (..., NULL)의 NULL은 무시된다.
문 24. 아래 SQL의 실행 결과는?
[TAB]
| COL1 | COL2 |
|---|---|
| 10 | 100 |
| 20 | 200 |
| 30 | 300 |
| NULL | NULL |
SELECT SUM(COL1)/NULLIF(COUNT(COL2), 0)
+ SUM(NVL(COL2, 0))/COUNT(*)
FROM TAB;
- ① 150
- ② 170
- ③ 200
- ④ 오류 발생
정답 및 해설 보기
정답 ②
두 항의 합을 계산한다. 첫째 항: SUM(COL1)은 NULL을 제외해 10+20+30 = 60, COUNT(COL2)는 NULL을 빼 3, NULLIF(3, 0) = 3이므로 60/3 = 20. 둘째 항: SUM(NVL(COL2, 0))은 NULL을 0으로 바꿔 100+200+300+0 = 600, COUNT(*)는 전체 행 4이므로 600/4 = 150. 두 항을 더하면 20+150 = 170이다. 실행 결과 170임을 확인했다.
⚠️ 함정 집계 함수는 NULL을 자동 제외 / COUNT(*)는 NULL 행 포함. 같은 600이라도 분모가 COUNT(COL2)=3이냐 COUNT(*)=4냐로 값이 갈린다.
문 25. 아래 실행 결과를 참고할 때 SQL의 빈칸 ㉠에 들어갈 내용으로 가장 적절한 것은?
[고객]
| 고객ID | 고객등급 | 등록연도 |
|---|---|---|
| 100 | GOLD | 2023 |
| 101 | VIP | 2022 |
| 102 | VIP | 2021 |
| 103 | GOLD | 2023 |
| 104 | GOLD | 2023 |
| 105 | VIP | 2022 |
| 106 | GOLD | 2021 |
[실행 결과]
| 고객등급 | 등록연도 | 고객수 |
|---|---|---|
| GOLD | 2021 | 1 |
| GOLD | 2023 | 3 |
| GOLD | NULL | 4 |
| VIP | 2021 | 1 |
| VIP | 2022 | 2 |
| VIP | NULL | 3 |
| NULL | NULL | 7 |
SELECT 고객등급, 등록연도, COUNT(*) AS 고객수
FROM 고객
GROUP BY ㉠ (고객등급, 등록연도)
ORDER BY 고객등급, 등록연도;
- ① CUBE
- ② GROUPING
- ③ GROUPING SETS
- ④ ROLLUP
정답 및 해설 보기
정답 ④
실행 결과는 (고객등급, 등록연도)별 집계 → (고객등급)별 소계(등록연도=NULL) → 전체 총계(고객등급=NULL, 등록연도=NULL)로 계층적 집계가 나타난다. 이는 GROUP BY 컬럼 순서대로 소계를 만드는 ROLLUP의 특징이다. ① CUBE라면 (등록연도)별 소계까지 모두 나와야 한다.
🔑 암기 계층적 소계·총계 → ROLLUP / 모든 조합의 소계 → CUBE.
문 26. 서브쿼리에 대한 설명으로 가장 적절하지 않은 것은?
- ① 스칼라 서브쿼리는 단일칼럼, 단일행을 반환한다.
- ② 다중행 서브쿼리 비교 연산자는 단일행 서브쿼리 비교 연산자로도 사용할 수 있다.
- ③ 단일행 비교 연산자와 함께 사용할 때는 서브쿼리의 결과가 반드시 1건 이상이어야 한다.
- ④ 인라인 뷰는 FROM 절의 테이블이 입력되는 위치에 들어가는 서브쿼리를 말한다.
정답 및 해설 보기
정답 ③
단일행 비교 연산자(=, <, >, <=, >=)는 서브쿼리 결과가 반드시 1건 이하(0건 또는 1건)여야 한다. 결과가 0건이면 NULL로 처리되어 조회만 안 될 뿐 오류가 아니고, 2건 이상일 때 오류가 발생한다. 따라서 '반드시 1건 이상'이라는 ③이 틀렸다. ①·②·④는 옳은 설명이다.
⚠️ 함정 단일행 연산자 + 서브쿼리 결과 2건 이상 → 오류 / 0건 → NULL(오류 아님).
문 27. 아래 테이블과 실행 결과를 참고할 때 SQL의 빈칸 ㉠에 들어갈 내용으로 가장 적절한 것은?
[점수]
| 학생명 | 국어 | 수학 | 총점 |
|---|---|---|---|
| 김지수 | 90 | 65 | 155 |
| 박수진 | 70 | 85 | 155 |
| 김명진 | 100 | 80 | 180 |
| 노진영 | 90 | 90 | 180 |
| 김철수 | 70 | 40 | 110 |
| 최철민 | 75 | 45 | 120 |
[실행 결과]
| 순위 | 학생명 | 국어 | 수학 | 총점 |
|---|---|---|---|---|
| 1 | 김명진 | 100 | 80 | 180 |
| 1 | 노진영 | 90 | 90 | 180 |
| 2 | 김지수 | 90 | 65 | 155 |
| 2 | 박수진 | 70 | 85 | 155 |
| 3 | 최철민 | 75 | 45 | 120 |
| 4 | 김철수 | 70 | 40 | 110 |
SELECT ㉠ OVER (ORDER BY 총점 DESC)
AS 순위, 학생명, 국어, 수학, 총점
FROM 점수;
- ① DENSE_RANK( )
- ② RANK( )
- ③ ROWNUM( )
- ④ ROW_NUMBER( )
정답 및 해설 보기
정답 ①
180점 동점자 2명이 공동 1위이고, 그 다음 순위가 3위가 아니라 2위로 이어진다. 공동 순위 이후에도 순위를 건너뛰지 않고 연속으로 증가하는 방식은 DENSE_RANK의 특징이다. RANK였다면 공동 1위 다음이 3위였을 것이고, ROW_NUMBER였다면 동점도 1·2·3·4로 다른 번호를 받았을 것이다.
🔑 암기 동점 후 순위 안 건너뜀 → DENSE_RANK / 건너뜀 → RANK / 무조건 일련번호 → ROW_NUMBER(문 33과 비교).
문 28. 아래에서 설명하는 개념으로 가장 적절한 것은?
DBMS에서 여러 개의 권한을 묶어 그룹화하여 사용자에게 부여할 수 있는 개념이다. 이를 활용하면 여러 사용자에게 동일한 권한을 쉽게 부여하고 관리할 수 있어 유지보수성이 향상된다. 또한, 특정 권한을 추가하거나 제거할 때 개별 사용자에게 적용하는 대신 그룹 단위로 조정할 수 있어 보안과 효율성이 높아진다.
- ① GRANT
- ② ROLE
- ③ REVOKE
- ④ PACKAGE
정답 및 해설 보기
정답 ②
ROLE은 여러 개의 권한을 묶어 그룹화해 관리하는 개념으로, 여러 사용자에게 동일한 권한을 쉽게 부여하고 유지보수를 간편하게 한다. ① GRANT는 권한·ROLE을 부여, ③ REVOKE는 회수하는 명령어이며, ④ PACKAGE는 PL/SQL에서 관련 프로시저·함수를 묶는 개념이다.
🔑 암기 권한의 묶음(그룹) = ROLE / 부여 = GRANT / 회수 = REVOKE.
문 29. 아래 산술 연산자를 연산 우선순위대로 올바르게 나열한 것은?
(), *, /, +, -
- ① ( ), *, /, +, -
- ② ( ), +, -, *, /
- ③ *, /, +, -, ( )
- ④ *, /, ( ), +, -
정답 및 해설 보기
정답 ①
산술 연산자의 우선순위는 괄호 ( )가 가장 높고, 다음으로 곱셈 *·나눗셈 /(서로 동순위), 마지막으로 덧셈 +·뺄셈 -(서로 동순위) 순이다. 따라서 ( ), *, /, +, - 순인 ①이 맞다.
🔑 암기 연산 우선순위 — ( ) → * / → + -.
문 30. 아래 SQL의 실행 결과는? 🎯 고난도
SELECT
REGEXP_SUBSTR('Ax1bC34dEF6G8',
'[A-Z]{1,2}[0-9]+', 1, 2)
FROM DUAL;
- ① EF6
- ② C34
- ③ G8
- ④ NULL
정답 및 해설 보기
정답 ①
패턴 [A-Z]{1,2}[0-9]+는 '대문자 1~2개 + 숫자 1개 이상'을 의미한다. 'Ax1bC34dEF6G8'에서 이 패턴에 매칭되는 부분은 순서대로 'C34'(첫째), 'EF6'(둘째), 'G8'(셋째)이다. REGEXP_SUBSTR(..., 1, 2)의 네 번째 인자가 2이므로 두 번째 매칭인 'EF6'를 반환한다. 실행 결과 EF6임을 확인했다.
⚠️ 함정 네 번째 인자(발생 순서)는 'N번째로 일치하는 부분'을 의미한다 — 시작 위치(세 번째 인자)와 혼동하지 않는다.
문 31. 오라클 계층형 질의에 대한 설명으로 가장 적절하지 않은 것은?
- ① 루트 노드의 LEVEL 값은 1이다.
- ② 역방향 전개란 자식 노드에서 부모 노드 방향으로 전개하는 것을 말한다.
- ③ 'PRIOR 부모 = 자식' 형태로 사용하면 순방향 전개로 수행된다.
- ④ ORDER SIBLINGS BY 절은 같은 레벨의 형제 노드끼리 정렬을 지정한다.
정답 및 해설 보기
정답 ③
CONNECT BY PRIOR 부모 = 자식 형태는 자식 노드에서 부모 노드로 거슬러 올라가는 역방향 전개를 의미한다. 따라서 '순방향 전개로 수행된다'는 ③이 틀렸다. ① 루트 LEVEL=1, ② 역방향 전개 정의, ④ ORDER SIBLINGS BY 정의는 모두 옳다.
🔑 암기 PRIOR 자식 = 부모 → 순방향(부모→자식) / PRIOR 부모 = 자식 → 역방향(자식→부모).
문 32. 아래 SQL의 실행 시 출력되는 행의 개수로 가장 적절한 것은?
[TAB1]
| 학생명 | 학과 |
|---|---|
| 이세영 | 국문학과 |
| 박유림 | 영문학과 |
| 김영수 | 철학과 |
| 김수철 | 수학과 |
[TAB2]
| 학생명 | 학년 | 학과 |
|---|---|---|
| 박유림 | 2 | 영문학과 |
| 김수철 | 3 | 수학과 |
| 이수연 | 1 | 철학과 |
SELECT *
FROM TAB1 LEFT OUTER JOIN TAB2
ON TAB1.학생명 = TAB2.학생명
AND TAB2.학과 = TAB1.학과
UNION ALL
SELECT *
FROM TAB1 RIGHT OUTER JOIN TAB2
ON TAB1.학생명 = TAB2.학생명
WHERE TAB1.학과 = TAB2.학과
UNION ALL
SELECT *
FROM TAB1 FULL OUTER JOIN TAB2
ON TAB1.학생명 = TAB2.학생명
WHERE NVL(TAB1.학과, TAB2.학과) = '철학과';
- ① 5
- ② 6
- ③ 7
- ④ 8
정답 및 해설 보기
정답 ④
세 SELECT를 UNION ALL로 합치므로 행 수는 단순 합산된다.
- LEFT OUTER JOIN: 기준 TAB1의 모든 행이 유지되어 4행.
- RIGHT OUTER JOIN: 기준 TAB2 중
WHERE TAB1.학과 = TAB2.학과로 TAB1이 NULL인 행은 제외되어 박유림·김수철 2행. - FULL OUTER JOIN: 학생명 기준으로 합친 뒤
WHERE NVL(TAB1.학과, TAB2.학과) = '철학과'로 김영수(철학과)·이수연(철학과) 2행.
합하면 4+2+2 = 8행이다. 실행 결과 정확히 8행임을 확인했다.
⚠️ 함정 OUTER JOIN 뒤 WHERE 절은 NULL 행을 떨어뜨려(INNER처럼 작용) 건수를 줄인다 — ON 조건과 WHERE 조건의 위치를 구분한다.
문 33. 아래 테이블과 같은 방식으로 순위를 매기는 데 사용되는 적절한 함수 또는 키워드는? (단, [TAB] 테이블의 '순위' 칼럼은 사원들의 급여에 대한 순위로 가정함)
[TAB]
| 사원ID | 사원명 | 급여 | 순위 |
|---|---|---|---|
| 1000 | 박진희 | 8000 | 1 |
| 1001 | 김미화 | 7000 | 2 |
| 1002 | 박영수 | 7000 | 2 |
| 1003 | 김철수 | 5000 | 4 |
| 1004 | 최영식 | 5000 | 4 |
| 1005 | 김명진 | 4000 | 6 |
- ① RANK( )
- ② DENSE_RANK( )
- ③ ROWNUM( )
- ④ ROW_NUMBER( )
정답 및 해설 보기
정답 ①
동일한 급여를 가진 사원에게 같은 순위를 부여하고, 그다음 순위는 동점자 수만큼 건너뛴다(7000 공동 2위 → 다음은 4위, 5000 공동 4위 → 다음은 6위). 이렇게 순위를 건너뛰는 방식은 RANK의 특징이다. DENSE_RANK였다면 다음 순위가 3위·5위였을 것이다.
🔑 암기 공동 순위 후 건너뜀 → RANK / 안 건너뜀 → DENSE_RANK(문 27과 비교).
문 34. 아래 SQL의 실행 결과는? 🎯 고난도
SELECT REGEXP_INSTR('ABCDEFG',
'(AB)((CD)E)(FG)', 1, 1, 0, 'i', 3)
FROM DUAL;
- ① 1
- ② 3
- ③ 5
- ④ 6
정답 및 해설 보기
정답 ②
REGEXP_INSTR(문자열, 패턴, 시작 위치, 발생 순서, 반환값, 매칭 옵션, 서브 표현식) 구조다. 패턴 (AB)((CD)E)(FG)의 그룹 번호는 바깥쪽부터 (AB)=1, ((CD)E)=2, (CD)=3, (FG)=4다. 마지막 인자(서브 표현식)가 3이므로 3번 그룹 (CD)가 'ABCDEFG'에서 시작하는 위치인 3을 반환한다(여섯 번째 인자 'i'는 대소문자 무시). 실행 결과 3임을 확인했다.
⚠️ 함정 REGEXP_INSTR의 일곱 번째 인자(서브 표현식)는 패턴 안 특정 괄호 그룹의 위치를 지정한다 — 그룹 번호는 여는 괄호 순서로 매긴다.
문 35. 아래 SQL의 실행 결과는?
CREATE TABLE TBL(
COL1 VARCHAR2(10),
COL2 NUMBER);
INSERT INTO TBL VALUES('A', 5000);
INSERT INTO TBL VALUES('B', 4000);
INSERT INTO TBL VALUES('C', 3000);
INSERT INTO TBL VALUES('C', 2000);
COMMIT;
SELECT COUNT(*)
FROM TBL
GROUP BY ROLLUP(COL1), COL1;
- ① 3
- ② 5
- ③ 6
- ④ 7
정답 및 해설 보기
정답 ③
GROUP BY ROLLUP(COL1), COL1은 GROUP BY (COL1, COL1) → GROUP BY (전체, COL1)을 합친 것과 같다. 두 경우 모두 사실상 COL1로 집계한 것이므로 'A, B, C' 3행이 두 번 나와 총 6행이 출력된다. 실행 결과 6행임을 확인했다.
⚠️ 함정 ROLLUP(COL1), COL1처럼 ROLLUP 밖에 같은 컬럼을 또 쓰면 소계가 상쇄되어 단순 집계가 두 벌 나온다.
문 36. 아래 테이블에서 특정한 한 명의 사원에 대한 상위 관리자를 조회하는 SQL로 가장 적절한 것은?
[EMP]
| EMP_ID | EMP_NAME | MANAGER_ID |
|---|---|---|
| 101 | James | 100 |
| 102 | John | 101 |
| 103 | Linda | 102 |
| 104 | Andrew | 103 |
| 105 | Olivia | 104 |
| 106 | Kevin | 105 |
①
SELECT * FROM EMP
START WITH MANAGER_ID = 100
CONNECT BY PRIOR EMP_ID = MANAGER_ID;
②
SELECT * FROM EMP
START WITH MANAGER_ID = 107
CONNECT BY PRIOR EMP_ID = MANAGER_ID;
③
SELECT * FROM EMP
WHERE EMP_ID = 103
START WITH MANAGER_ID = 100
CONNECT BY PRIOR EMP_ID = MANAGER_ID;
④
SELECT * FROM EMP
WHERE EMP_ID = 103
START WITH MANAGER_ID = 100
CONNECT BY PRIOR MANAGER_ID = EMP_ID;
정답 및 해설 보기
정답 ③
계층형 질의는 START WITH·CONNECT BY가 먼저 수행된 후 마지막에 WHERE 절이 적용된다. ③은 MANAGER_ID=100인 노드부터 순방향 전개로 전체 트리를 조회한 뒤 WHERE EMP_ID=103으로 한 행만 필터링하므로 특정 사원을 조회할 수 있다. ① WHERE가 없어 모든 행 출력, ② MANAGER_ID=107인 노드가 없어 결과 없음, ④ CONNECT BY 방향이 반대(역방향)라 EMP_ID=103이 탐색 범위에 없어 결과가 없다.
🔑 암기 계층형 실행 순서 — START WITH → CONNECT BY → (마지막) WHERE.
문 37. 아래 SQL의 실행 결과는? 🎯 고난도
[등록회원]
| 회원ID | 회원명 | 등록일시 |
|---|---|---|
| 1 | 김진숙 | 2025-01-01 00:00:00 |
| 2 | 박진영 | 2025-01-01 10:00:00 |
| 3 | 박연자 | 2025-01-01 23:59:59 |
| 4 | 김미영 | 2025-01-02 00:00:00 |
| 5 | 이미화 | 2025-01-02 10:10:00 |
| 6 | 김철수 | 2025-01-02 23:59:59 |
SELECT COUNT(*)
FROM 등록회원
WHERE 등록일시 BETWEEN
TO_DATE('2025-01-02',
'YYYY-MM-DD') - 1/24
AND TO_DATE('2025-01-02',
'YYYY-MM-DD') + 4/24/(60/30);
- ① 1
- ② 2
- ③ 3
- ④ 4
정답 및 해설 보기
정답 ②
TO_DATE('2025-01-02', 'YYYY-MM-DD')는 '2025-01-02 00:00:00'이다. 1/24는 1시간이므로 하한은 1시간 전인 '2025-01-01 23:00:00'이고, 4/24/(60/30)은 4시간을 2(=60/30)로 나눈 2시간이므로 상한은 2시간 후인 '2025-01-02 02:00:00'이다. 따라서 범위는 '2025-01-01 23:00:00 ~ 2025-01-02 02:00:00'이고, 여기에 드는 행은 박연자(01-01 23:59:59)·김미영(01-02 00:00:00) 2건이다. 실행 결과 2건임을 확인했다.
⚠️ 함정 TO_DATE(날짜)는 시간 생략 시 자정(00:00:00). 1/24 = 1시간, 4/24/(60/30) = 2시간으로 분해해 경계를 계산한다.
문 38. 아래 SQL의 실행 결과는? 🎯 고난도
SELECT
REGEXP_SUBSTR('data science', '[abc]')
AS COL1,
REGEXP_SUBSTR('data science', '[^abc]')
AS COL2,
REGEXP_SUBSTR('data science', '^abc')
AS COL3
FROM DUAL;
①
| COL1 | COL2 | COL3 |
|---|---|---|
| a | d | NULL |
②
| COL1 | COL2 | COL3 |
|---|---|---|
| a | NULL | d |
③
| COL1 | COL2 | COL3 |
|---|---|---|
| NULL | d | NULL |
④
| COL1 | COL2 | COL3 |
|---|---|---|
| a | NULL | NULL |
정답 및 해설 보기
정답 ①
[abc]: a·b·c 중 하나와 일치하는 첫 문자를 찾는다. 'data science'에서 가장 먼저 등장하는 'a'를 반환 → COL1 = 'a'.[^abc]: 대괄호 안의^는 부정(NOT)이므로 a·b·c가 아닌 첫 문자를 찾는다. 첫 글자 'd'가 해당 → COL2 = 'd'.^abc: 대괄호 밖의^는 '문자열의 시작'을 의미한다. 'data science'는 'abc'로 시작하지 않으므로 → COL3 = NULL.
실행 결과 a · d · NULL임을 확인했다.
⚠️ 함정 [^...](대괄호 안 캐럿) = 부정 / ^...(대괄호 밖 캐럿) = 문자열 시작. 위치에 따라 의미가 완전히 다르다.
문 39. NATURAL JOIN에 대한 설명으로 가장 적절한 것은?
- ① ON 절에 조인 조건을 추가할 수 없다.
- ② 칼럼명이 같은 경우 데이터 타입이 달라도 조인할 수 있다.
- ③ NATURAL JOIN 시 조인에 이용되는 칼럼을 명시해야 한다.
- ④ 각 테이블의 행이 상대 테이블의 모든 행과 조합되어 새로운 행이 생성된다.
정답 및 해설 보기
정답 ①
NATURAL JOIN은 두 테이블에서 이름과 데이터 타입이 모두 동일한 칼럼을 자동으로 찾아 조인하므로, ON 절이나 USING 절로 조인 조건을 명시할 필요가 없고 명시할 수도 없다. ② 데이터 타입이 다르면 조인되지 않고, ③ 조인 칼럼을 명시하지 않으며, ④는 CROSS JOIN(카티션 곱)에 대한 설명이다.
🔑 암기 NATURAL JOIN = 동일 이름·동일 타입 칼럼 자동 조인(ON·USING 사용 불가).
문 40. 아래 SQL의 실행 결과는? 🎯 고난도
[TBL]
| 학생ID | 학생명 | 점수 |
|---|---|---|
| 100 | 김진수 | 40 |
| 101 | 박진영 | 50 |
| 102 | 김미영 | 60 |
| 103 | 김철수 | 70 |
| 104 | 최창안 | 80 |
| 105 | 임수연 | 90 |
SELECT MIN(점수)
FROM (SELECT 점수,
NTILE(4) OVER (
ORDER BY 점수 DESC) AS GN
FROM TBL
WHERE 점수 >= 60)
WHERE GN IN (1, 2);
- ① 50
- ② 60
- ③ 70
- ④ 80
정답 및 해설 보기
정답 ④
먼저 WHERE 점수 >= 60으로 90·80·70·60만 남고, 점수 내림차순으로 NTILE(4)가 4개 구간으로 나눈다(90→1, 80→2, 70→3, 60→4). 바깥쪽 WHERE GN IN (1, 2)로 90·80만 선택되고, 그중 최솟값(MIN)은 80이다. 실행 결과 80임을 확인했다.
🔑 암기 NTILE(n)은 정렬된 행을 n개 구간으로 균등 분할해 1~n 번호를 부여한다.
문 41. 아래 테이블을 참고할 때 오류가 발생하는 INSERT 문은?
CREATE TABLE 강좌신청 (
신청번호 NUMBER PRIMARY KEY,
학생번호 NUMBER NOT NULL,
신청일자 DATE,
신청현황 VARCHAR2(3) DEFAULT '000');
①
INSERT INTO 강좌신청(신청번호, 학생번호, 신청일자, 신청현황)
VALUES(1, 100, 20250301, '001');
②
INSERT INTO 강좌신청(신청번호, 학생번호, 신청일자, 신청현황)
VALUES(2, 200, '20250301', '001');
③
INSERT INTO 강좌신청(신청번호, 학생번호, 신청일자, 신청현황)
VALUES(3, 300, SYSDATE, '003');
④
INSERT INTO 강좌신청(신청번호, 학생번호, 신청일자, 신청현황)
VALUES(4, 400, SYSDATE+1, '001');
정답 및 해설 보기
정답 ①
①의 20250301은 따옴표가 없는 숫자(NUMBER) 리터럴이다. 숫자는 DATE 타입으로 자동(암시적) 변환되지 않으므로 DATE 칼럼인 '신청일자'에 입력하면 오류가 발생한다. ② 날짜 형식의 문자열은 DATE로 변환 가능, ③ SYSDATE는 현재 날짜·시간, ④ SYSDATE+1은 날짜 + 정수 연산으로 모두 유효하다. 실제 실행 시 ①은 ORA-00932(NUMBER가 DATE와 호환되지 않음) 오류가 발생함을 확인했다.
⚠️ 함정 숫자(NUMBER)는 DATE로 자동 변환 불가. 날짜는 문자열('20250301')이나 TO_DATE 또는 SYSDATE로 넣는다.
문 42. 아래 SQL의 실행 결과는? 🎯 고난도
[PLAYER]
| PLAYER_ID | TEAM_ID | SALARY |
|---|---|---|
| 1001 | A | 3500 |
| 1002 | A | 4000 |
| 1003 | B | 5500 |
| 1004 | B | 4000 |
| 1005 | B | 3500 |
| 1006 | C | 5500 |
| 1007 | C | 4000 |
SELECT PLAYER_ID, C2
FROM (SELECT PLAYER_ID,
ROW_NUMBER() OVER(
PARTITION BY TEAM_ID ORDER BY SALARY DESC
) AS C1,
SUM(SALARY) OVER(
PARTITION BY TEAM_ID ORDER BY PLAYER_ID ROWS BETWEEN UNBOUNDED
PRECEDING AND CURRENT ROW
) AS C2
FROM PLAYER)
WHERE C1 = 2
ORDER BY PLAYER_ID;
①
| PLAYER_ID | C2 |
|---|---|
| 1001 | 7500 |
| 1004 | 7500 |
| 1007 | 4000 |
②
| PLAYER_ID | C2 |
|---|---|
| 1001 | 3500 |
| 1004 | 7500 |
| 1007 | 9500 |
③
| PLAYER_ID | C2 |
|---|---|
| 1001 | 3500 |
| 1004 | 9500 |
| 1007 | 9500 |
④
| PLAYER_ID | C2 |
|---|---|
| 1001 | 7500 |
| 1004 | 7500 |
| 1007 | 9500 |
정답 및 해설 보기
정답 ③
C1은 팀별로 SALARY 내림차순 순번이므로 C1 = 2는 각 팀에서 연봉 2위(1001·1004·1007)다. C2는 팀별로 PLAYER_ID 오름차순 누적 합계(ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)다.
- A팀 1001: 3500 (누적 = 3500)
- B팀 1004: 5500(1003) + 4000(1004) = 9500
- C팀 1007: 5500(1006) + 4000(1007) = 9500
따라서 1001/3500, 1004/9500, 1007/9500인 ③이 맞다. 실행 결과로 확인했다.
⚠️ 함정 C1(순위) 정렬 기준은 SALARY, C2(누적) 정렬 기준은 PLAYER_ID로 서로 다르다. 누적은 ID 순으로 더해진다.
문 43. 아래 테이블과 실행 결과를 참고할 때 SQL의 빈칸 ㉠에 들어갈 내용으로 가장 적절한 것은?
[주문]
| 고객ID | 주문제품 | 주문수량 |
|---|---|---|
| 100 | 모니터 | 1 |
| 100 | 키보드 | 2 |
| 101 | 모니터 | 1 |
| 101 | 키보드 | 2 |
| 102 | 모니터 | 1 |
| 102 | 키보드 | 1 |
| 102 | 마우스 | 2 |
SELECT 고객ID, 주문제품, SUM(주문수량)
AS 주문수량
FROM 주문
GROUP BY GROUPING SETS( ㉠ );
[실행 결과]
| 고객ID | 주문제품 | 주문수량 |
|---|---|---|
| 100 | 모니터 | 1 |
| 100 | 키보드 | 2 |
| 100 | NULL | 3 |
| 101 | 모니터 | 1 |
| 101 | 키보드 | 2 |
| 101 | NULL | 3 |
| 102 | 모니터 | 1 |
| 102 | 키보드 | 1 |
| 102 | 마우스 | 2 |
| 102 | NULL | 4 |
- ① (고객ID, 주문제품)
- ② (고객ID, 주문제품), 고객ID
- ③ (고객ID, 주문제품), 주문제품
- ④ (고객ID, 주문제품), ( )
정답 및 해설 보기
정답 ②
실행 결과에는 두 가지 그룹화가 보인다 — (고객ID, 주문제품)별 집계와, 주문제품이 NULL로 표기된 (고객ID)별 소계다. GROUPING SETS는 보고 싶은 집계 레벨을 괄호 안에 콤마로 나열하므로, (고객ID, 주문제품), 고객ID인 ②가 맞다. 전체 총계 행이 없으므로 ④의 ( )(전체)는 해당하지 않는다.
🔑 암기 GROUPING SETS = 원하는 집계 레벨만 직접 나열 / ( ) = 전체 총계.
문 44. 아래 SQL의 실행 결과는?
[EMP]
| EMP_ID | EMP_NAME | DEPT_ID | SALARY |
|---|---|---|---|
| 1001 | Kim | 10 | 3000 |
| 1002 | Lee | 20 | 4000 |
| 1003 | Park | 20 | 4500 |
| 1004 | Choi | 30 | 5000 |
| 1005 | Jung | 10 | 3500 |
| 1006 | Han | 20 | 3800 |
SELECT EMP_NAME
FROM EMP E
WHERE SALARY >
(SELECT AVG(SALARY) FROM EMP
WHERE DEPT_ID = E.DEPT_ID);
①
| EMP_NAME |
|---|
| Kim |
| Lee |
②
| EMP_NAME |
|---|
| Park |
| Choi |
③
| EMP_NAME |
|---|
| Lee |
| Jung |
④
| EMP_NAME |
|---|
| Park |
| Jung |
정답 및 해설 보기
정답 ④
상관 서브쿼리가 메인 쿼리의 각 행에 대해 자신이 속한 부서의 평균 급여를 구해 비교한다.
- 10번 부서 평균 = (3000+3500)/2 = 3250 → 초과자 Jung(3500)
- 20번 부서 평균 = (4000+4500+3800)/3 = 4100 → 초과자 Park(4500)
- 30번 부서 평균 = 5000 → Choi 한 명이라 초과자 없음
따라서 결과는 Park·Jung인 ④다. 실행 결과로 확인했다.
🔑 암기 상관 서브쿼리는 메인 쿼리 각 행마다 서브쿼리가 반복 실행된다(E.DEPT_ID처럼 외부 컬럼 참조).
문 45. 아래 SQL의 실행 결과는?
[사원]
| 사원ID | 사원명 | 관리자ID |
|---|---|---|
| 1 | 김명수 | NULL |
| 2 | 박영진 | 1 |
| 3 | 한수진 | 1 |
| 4 | 김영자 | 2 |
| 5 | 김미영 | 3 |
| 6 | 박영희 | 3 |
SELECT * FROM 사원
START WITH 관리자ID = 1
CONNECT BY 사원ID = PRIOR 관리자ID
ORDER SIBLINGS BY 사원ID;
①
| 사원ID | 사원명 | 관리자ID |
|---|---|---|
| 2 | 박영진 | 1 |
| 1 | 김명수 | NULL |
②
| 사원ID | 사원명 | 관리자ID |
|---|---|---|
| 2 | 박영진 | 1 |
| 1 | 김명수 | NULL |
| 3 | 한수진 | 1 |
| 1 | 김명수 | NULL |
③
| 사원ID | 사원명 | 관리자ID |
|---|---|---|
| 3 | 한수진 | 1 |
| 1 | 김명수 | NULL |
| 2 | 박영진 | 1 |
| 1 | 김명수 | NULL |
④
| 사원ID | 사원명 | 관리자ID |
|---|---|---|
| 2 | 박영진 | 1 |
| 3 | 한수진 | 1 |
| 1 | 김명수 | NULL |
정답 및 해설 보기
정답 ②
START WITH 관리자ID = 1은 관리자ID가 1인 박영진(사원ID 2)·한수진(사원ID 3)에서 시작한다. CONNECT BY 사원ID = PRIOR 관리자ID는 사원ID = PRIOR 관리자ID(부모 컬럼에 PRIOR) 형태로 자식에서 부모로 올라가는 역방향 전개다. 따라서 박영진→김명수, 한수진→김명수로 따라가고, ORDER SIBLINGS BY 사원ID로 형제(박영진·한수진)를 사원ID 순 정렬하면 '박영진 → 김명수 → 한수진 → 김명수' 순서가 된다. 실행 결과로 확인했다.
🔑 암기 CONNECT BY 사원ID = PRIOR 관리자ID = 역방향 전개(자식→부모). 부모 컬럼 앞에 PRIOR.
문 46. 아래 SQL의 빈칸 ㉠, ㉡에 들어갈 내용으로 가장 적절한 것은?
[테이블]
학생(학생번호, 학생명, 소속학과), 수강정보(수강번호, 학생번호, 과목명)
- 수강정보 테이블의 학생번호는 학생 테이블의 학생번호를 참조하는 외래 키이다.
[조건]
수강 이력이 있는 학생 중 수강 횟수가 5회 이상인 학생의 이름과 소속학과를 출력
SELECT A.학생명, A.소속학과
FROM 학생 A
㉠
GROUP BY A.학생명, A.소속학과
㉡ ;
①
㉠ NATURAL JOIN 수강정보 B
㉡ HAVING SUM(B.수강번호) >= 5
②
㉠ LEFT OUTER JOIN 수강정보 B
ON A.학생번호 = B.학생번호
㉡ WHERE B.수강번호 >= 5
③
㉠ INNER JOIN 수강정보 B
ON A.학생번호 = B.학생번호
㉡ HAVING COUNT(B.수강번호) >= 5
④
㉠ LEFT OUTER JOIN 수강정보 B
ON A.학생번호 = B.학생번호
㉡ HAVING SUM(B.수강번호) >= 5
정답 및 해설 보기
정답 ③
'수강 이력이 있는 학생'만 대상이므로 학생과 수강정보를 학생번호로 INNER JOIN해야 한다(수강 이력 없는 학생 제외). '수강 횟수 5회 이상'은 그룹별 행 수를 세는 조건이므로 COUNT(B.수강번호) >= 5를 그룹 함수 조건절인 HAVING에 둔다. ① NATURAL JOIN은 학생번호 외에 의도치 않은 동일 컬럼까지 조인될 위험이 있고 SUM은 횟수가 아니며, ② WHERE는 그룹 함수 조건에 쓸 수 없고, ④ LEFT OUTER JOIN은 수강 이력 없는 학생을 포함하고 SUM도 횟수가 아니다.
🔑 암기 그룹 함수(COUNT·SUM 등)에 대한 조건은 WHERE가 아니라 HAVING / 수강 '횟수'는 COUNT.
문 47. 아래 테이블에서 PHONE_NUMBER 칼럼을 추가하고자 할 때 가장 적절한 SQL 문은?
CREATE TABLE EMP (
EMP_ID NUMBER(5) PRIMARY KEY,
EMP_NAME VARCHAR2(20),
DEPT_ID NUMBER(3)
);
①
ALTER TABLE EMP
MODIFY PHONE_NUMBER VARCHAR2(20);
②
ALTER TABLE EMP
ADD PHONE_NUMBER VARCHAR2(20);
③
ALTER TABLE EMP
ALTER PHONE_NUMBER VARCHAR2(20);
④
ALTER TABLE EMP ADD CONSTRAINT
PHONE_NUMBER VARCHAR2(20);
정답 및 해설 보기
정답 ②
기존 테이블에 새 칼럼을 추가할 때는 ALTER TABLE ... ADD 칼럼명 타입 구문을 사용한다. ① MODIFY는 기존 칼럼의 타입·크기 변경, ③ ALTER 칼럼 구문은 이 형태로 컬럼을 추가하지 않으며, ④ ADD CONSTRAINT는 제약조건을 추가하는 구문이다.
🔑 암기 컬럼 추가 → ALTER TABLE ... ADD / 컬럼 수정 → ALTER TABLE ... MODIFY.
문 48. 아래 SQL의 실행 결과는?
CREATE TABLE CUSTOMER (
CUST_ID NUMBER(3),
CUST_NAME VARCHAR2(20)
);
INSERT INTO CUSTOMER VALUES(1, 'Kim');
SAVEPOINT S;
INSERT INTO CUSTOMER VALUES(2, 'Lee');
ROLLBACK TO S;
INSERT INTO CUSTOMER VALUES(2, 'Park');
INSERT INTO CUSTOMER VALUES(3, 'Choi');
SAVEPOINT S;
UPDATE CUSTOMER
SET CUST_NAME = 'Kang'
WHERE CUST_ID = 2;
ROLLBACK TO S;
COMMIT;
INSERT INTO CUSTOMER VALUES(5, 'Han');
ROLLBACK;
SELECT MAX(CUST_ID) FROM CUSTOMER;
- ① 2
- ② 3
- ③ 4
- ④ 5
정답 및 해설 보기
정답 ②
흐름을 따라가면 — (1,'Kim') 입력 후 SAVEPOINT S 설정 → (2,'Lee')는 ROLLBACK TO S로 취소 → (2,'Park')·(3,'Choi') 입력 → 다시 SAVEPOINT S(이전 지점이 새 지점으로 덮어써짐) → CUST_ID=2를 'Kang'으로 UPDATE했으나 ROLLBACK TO S로 취소 → COMMIT으로 (1,'Kim')·(2,'Park')·(3,'Choi') 확정 → (5,'Han')은 ROLLBACK으로 취소된다. 최종 데이터는 (1,'Kim')·(2,'Park')·(3,'Choi')이므로 MAX(CUST_ID) = 3이다. 실행 결과 3임을 확인했다.
⚠️ 함정 같은 이름으로 SAVEPOINT를 다시 설정하면 이전 지점이 새 위치로 덮어써진다. COMMIT 이후의 ROLLBACK은 직전 COMMIT까지만 되돌린다.
문 49. ROW LIMITING 절에 대한 설명으로 가장 적절하지 않은 것은?
- ① ONLY: FETCH 절과 함께 사용되어 지정된 행의 개수나 백분율만큼의 행을 반환한다.
- ② FETCH: 반환할 행의 개수나 백분율을 지정한다.
- ③ WITH TIES: FETCH 절과 함께 사용되어 첫 번째 행과 동일한 값의 행들을 포함하여 반환한다.
- ④ OFFSET offset: 건너뛸 행의 개수를 지정한다.
정답 및 해설 보기
정답 ③
WITH TIES는 FETCH로 가져온 마지막 행과 동일한 정렬 기준값을 가진 행(동점)들을 함께 반환하는 옵션이다. 보기는 '첫 번째 행'과 동일한 값이라고 했으므로 틀렸다(기준은 마지막 행). ①·②·④는 옳은 설명이다. ROW LIMITING 절은 ORDER BY 다음에 기술하며 오라클 12c부터 사용 가능하다.
🔑 암기 WITH TIES = 마지막으로 가져온 행과 순위가 같은 행까지 포함.
문 50. TX1, TX2 트랜잭션에서 아래 SQL을 순서대로 실행할 때, 오류가 발생하는 구문은? 🎯 고난도
[테이블]
CREATE TABLE 도서 (
도서ID NUMBER,
도서명 VARCHAR2(20),
CONSTRAINT 도서_PK PRIMARY KEY (도서ID)
);
CREATE TABLE 대여정보 (
대여번호 NUMBER,
회원ID NUMBER,
도서ID NUMBER,
CONSTRAINT 대여정보_PK PRIMARY KEY (대여번호),
CONSTRAINT 대여정보_F1 FOREIGN KEY (도서ID) REFERENCES 도서 (도서ID)
);
INSERT INTO 도서 VALUES(101, '데미안');
INSERT INTO 도서 VALUES(102, '오만과 편견');
INSERT INTO 도서 VALUES(103, '어린왕자');
COMMIT;
[SQL]
| 시간 | TX1 | TX2 |
|---|---|---|
| t1 | (가) DELETE FROM 도서 WHERE 도서ID = 101; | |
| t2 | COMMIT; | |
| t3 | (나) INSERT INTO 대여정보 VALUES(9987, 1, 101); | |
| t4 | COMMIT; | |
| t5 | (다) UPDATE 도서 SET 도서ID = 101 WHERE 도서ID = 103; | |
| t6 | (라) INSERT INTO 대여정보 VALUES(9988, 2, 102); | |
| t7 | ROLLBACK; | |
| t8 | COMMIT; |
- ① (가)
- ② (나)
- ③ (다)
- ④ (라)
정답 및 해설 보기
정답 ②
t1에서 TX1이 도서ID=101을 삭제하고 t2의 COMMIT으로 실제 삭제가 확정된다. t3에서 TX2가 도서ID=101을 참조하는 대여정보를 입력하려 하지만, 부모 테이블에 101이 더 이상 존재하지 않으므로 참조 무결성(외래 키) 제약을 위반해 오류가 발생한다. 따라서 오류 구문은 (나)다. 실제로 101 삭제·COMMIT 후 101을 참조하는 INSERT는 ORA-02291(부모 키 없음) 오류가 발생함을 확인했다.
⚠️ 함정 외래 키는 부모 테이블에 존재하는 값만 참조 가능 — 부모가 삭제·COMMIT된 뒤 그 값을 참조하면 ORA-02291.
합격까지
SQLD, 약점 유형이 보이나요?
초개인화 학습앱 Klue로 틀린 유형을 집중 공략하고, 에듀윌 온라인강의로 개념까지 정리하세요.
