SQLD 1회 기출복원 풀이
목차 52
1과목 데이터 모델링의 이해 (문 1~10)
문 1. 데이터 모델링의 특징으로 가장 적절하지 않은 것은?
- ① 추상화
- ② 최적화
- ③ 명확화
- ④ 단순화
정답 및 해설 보기
정답 ②
데이터 모델링의 3대 특징은 추상화(Abstraction)·단순화(Simplification)·명확화(Clarity)다. 최적화는 모델링 이후 물리 설계나 SQL 튜닝 단계의 개념이지 모델링의 특징이 아니다.
🔑 암기 데이터 모델링 3대 특징 = 추상화·단순화·명확화.
문 2. 아래 데이터 모델에서 속성에 대한 설명으로 가장 적절하지 않은 것은?
[테이블]
직원(직원ID, 직급코드, 기본급, 수당율, 수당금액)
직급구분(직급코드, 직급명)
* 직원 테이블은 각 직원의 급여 관련 기본 정보를 관리한다.
* 직급구분 테이블은 사원, 대리, 과장 등 직급을 구분하기 위한 값을 관리한다.
* 수당금액은 기본급과 수당율을 기반으로 산정되는 값이다.
- ① 직급코드는 직급 체계를 관리하기 위해 도입된 설계 속성이다.
- ② 직급명은 더 이상 분해할 수 없는 단순 속성이다.
- ③ 수당금액은 계산을 통해 얻어지는 파생 속성이다.
- ④ 기본급은 직무 정보에 따라 여러 값을 가질 수 있는 다중값 속성이다.
정답 및 해설 보기
정답 ④
다중값 속성은 한 속성이 동시에 여러 값을 가질 수 있는 속성(전화번호처럼)이다. 한 직원의 기본급은 특정 시점에 하나의 값만 가지므로 단일값 속성이다.
오답 ① 직급코드는 관리 목적으로 인위 부여 → 설계 속성 맞음. ② 직급명은 더 분해 불가 → 단순 속성 맞음. ③ 수당금액은 기본급·수당율로 계산 → 파생 속성 맞음.
⚠️ 함정 "여러 값을 가질 수 있다"는 표현에 휘둘리지 말 것. 같은 시점에 값이 하나면 단일값이다.
문 3. 각 속성이 가질 수 있는 값의 범위로 가장 적절한 것은?
- ① 도메인
- ② 데이터 타입
- ③ 관계
- ④ 제약 조건
정답 및 해설 보기
정답 ①
도메인(Domain)은 속성이 가질 수 있는 값의 유형·허용 범위(데이터 타입, 크기, 제약)를 정의하는 개념이다. 예: '성별'의 도메인은 {남, 여}.
⚠️ 함정 데이터 타입·제약 조건은 도메인을 구성하는 일부일 뿐, "값의 범위" 전체를 가리키는 용어는 도메인이다.
문 4. 관계 표기법의 요소로 가장 적절하지 않은 것은?
- ① 관계명
- ② 관계 정의
- ③ 관계 차수
- ④ 관계 선택사양
정답 및 해설 보기
정답 ②
ERD에서 관계를 표현하는 3요소는 관계명(Relationship Name)·관계 차수(Cardinality)·관계 선택사양(Optionality)이다. '관계 정의'는 표기법의 공식 구성 요소가 아니다.
🔑 암기 관계 3요소 = 관계명 · 관계 차수(1:1, 1:M) · 관계 선택사양(필수/선택).
문 5. 정규화에 대한 설명으로 가장 적절하지 않은 것은?
- ① 데이터 일관성을 유지하고 중복을 최소화하며, 데이터 모델의 유연성을 높이는 데 활용된다.
- ② 제2정규형은 기본 키가 아닌 모든 속성이 기본 키의 일부분에만 종속되지 않아야 한다.
- ③ 조회 성능을 높이기 위해 테이블을 큰 단위로 통합하는 과정이다.
- ④ 주문상세 엔터티에서 상품번호 칼럼이 상품명을 결정하는 구조는 정규화 대상에 해당한다.
정답 및 해설 보기
정답 ③
정규화는 중복을 줄이고 무결성을 유지하기 위해 테이블을 분해하는 과정이다. 조회 성능을 위해 테이블을 통합(합치는)하는 것은 정반대 개념인 반정규화(Denormalization)다.
오답 ② 제2정규형은 부분 함수 종속 제거 → 맞음. ④ 비기본 속성(상품번호)이 다른 비기본 속성(상품명)을 결정하는 구조는 제3정규형 대상 → 맞음.
⚠️ 함정 "분해 ↔ 통합", "정규화 ↔ 반정규화"의 방향을 뒤집은 전형적 오답.
문 6. 아래 데이터 모델에 대한 설명으로 가장 적절하지 않은 것은?

- ① A는 부모 엔터티이고 B는 자식 엔터티이며, 두 엔터티 간의 관계는 1:M 식별자 관계이다.
- ② A의 KEY1은 A에서는 주식별자이고 B, C, D에서는 부모로부터 상속된 외부식별자로 사용된다.
- ③ A와 B를 비식별자 관계로 변경하면 부모 키가 자식의 주식별자에 포함되지 않으므로, A와 C 조인에 필요한 칼럼 수가 줄어 조인이 더 수월해진다.
- ④ A와 B, B와 C는 모두 식별자 관계이므로 C는 상속된 KEY1을 사용해 C.KEY1 = A.KEY1 형태로 A와 직접 조인할 수 있어, B를 거치지 않고도 조인이 가능하다.
정답 및 해설 보기
정답 ③
식별자 관계(실선)에서는 부모의 주식별자(KEY1)가 자식의 주식별자로 상속되어 A→B→C→D로 KEY1이 그대로 내려간다. 그래서 C는 KEY1만으로 A와 직접 조인할 수 있다(④ 맞음).
A와 B를 비식별자 관계로 바꾸면 KEY1이 C의 주식별자에서 빠져 상속이 끊긴다. 그러면 A와 C를 조인할 때 중간 B를 거쳐야 하므로 조인에 필요한 칼럼 수가 늘고 더 복잡해진다. ③은 이를 정반대("줄어 더 수월")로 설명해 틀렸다.
🔑 암기 식별자 관계(실선) = 부모 PK를 자식 PK로 상속 → 조인 경로 단축. 비식별자로 바꾸면 상속이 끊겨 조인이 길어진다.
문 7. 아래 데이터 모델에서 정규화가 필요한 이유로 가장 적절한 것은?
CREATE TABLE ENROLLMENTS (
수강번호 CHAR(5) PRIMARY KEY,
학생ID NUMBER,
학생명 VARCHAR2(50),
전공학과 VARCHAR2(50)
);
| 수강번호 | 학생ID | 학생명 | 전공학과 |
|---|---|---|---|
| E201 | 10 | 김하늘 | 데이터과학 |
| E202 | 20 | 박서윤 | 경영학 |
| E203 | 10 | 김하늘 | 데이터과학 |
| E204 | 30 | 최민호 | 소프트웨어공학 |
| E205 | 20 | 박서윤 | 경영학 |
| E206 | 40 | 이서준 | 전자공학 |
- ① 학생ID와 학생명이 후보 키가 될 수 없다.
- ② 학생명과 전공학과가 기본 키 전체에 완전 함수 종속되어 있다.
- ③ 기본 키인 수강번호가 학생명과 전공학과를 직접 결정하지 못한다.
- ④ 수강번호가 학생ID를 결정하고 학생ID가 학생명과 전공학과를 결정하므로, 이행 함수 종속이 발생한다.
정답 및 해설 보기
정답 ④
데이터를 보면 학생ID가 같으면(10) 학생명·전공학과도 항상 같다. 즉 수강번호 → 학생ID → 학생명·전공학과의 꼬리 물기 구조다. 비기본 속성이 또 다른 비기본 속성을 거쳐 결정되는 이것이 이행 함수 종속(Transitive Functional Dependency)이고, 제3정규형(3NF) 위배이므로 학생 정보를 별도 테이블로 분리해야 한다.
🔑 암기 A→B→C 꼬리 물기 = 이행 함수 종속 = 3NF 분해 대상.
문 8. 아래에서 설명하는 식별자의 특징으로 가장 적절한 것은?
제품 엔터티에서 한 번 부여된 제품코드가 시간이 지나 변경된다면,
기존 제품이 사라지고 새로운 제품이 생성된 것으로 간주되므로
주식별자로 적합하지 않다.
- ① 최소성
- ② 불변성
- ③ 유일성
- ④ 존재성
정답 및 해설 보기
정답 ②
주식별자는 한 번 지정되면 값이 변하지 않아야 한다 — 불변성(Immutability). 값이 변하면 다른 개체로 간주되므로 주식별자로 부적합하다.
🔑 암기 주식별자 4대 특징 = 유일성·최소성·불변성·존재성. "변하면 안 됨" = 불변성.
문 9. 아래 데이터 모델에 대한 식별자 분류로 가장 적절한 것은? 🎯 고난도

- ① 대여번호 + 도서번호: 복합식별자, 외부식별자, 본질식별자
- ② 대여번호: 외부식별자, 단일식별자, 주식별자
- ③ 카테고리코드 + 카테고리명: 복합식별자, 보조식별자, 본질식별자
- ④ 도서번호: 본질식별자, 단일식별자, 주식별자
정답 및 해설 보기
정답 ④
도서목록의 도서번호는 ① 업무적으로 도서를 고유하게 구분하는 본질식별자, ② 속성 하나로 이뤄진 단일식별자, ③ 엔터티를 대표하는 주식별자다. 모두 정확하다.
오답 ① 대여도서목록의 대여번호·도서번호는 대여목록·도서목록을 참조하며 유입된 외부식별자이므로 본질식별자가 아니다. ② 대여번호는 엔터티 내부에서 생성된 내부식별자이므로 외부식별자가 아니다. ③ 카테고리명은 일반 속성이라 카테고리코드와 묶어 식별자로 쓰지 않는다.
⚠️ 함정 외부에서 유입된 키(FK)는 본질식별자가 될 수 없다. 교차 엔터티의 상속 키를 본질식별자로 부르는 함정이 단골이다.
문 10. 아래 ERD에 대한 설명으로 가장 적절하지 않은 것은?

- ① 계약항목 엔터티는 계약서 엔터티가 존재하지 않더라도 단독으로 생성될 수 있다.
- ② 계약항목은 항상 특정 계약서에 속하므로, 계약항목의 계약ID는 NULL을 허용할 수 없다.
- ③ 계약서는 업무규칙상 최소 1개 이상의 계약항목을 가져야 한다.
- ④ 계약항목의 항목번호는 동일한 계약ID 내에서만 유일하면 된다.
정답 및 해설 보기
정답 ①
실선(식별자 관계)은 부모의 주식별자(계약ID)가 자식의 주식별자에 포함되는 강한 종속 관계다. 부모 계약서가 없으면 자식 계약항목은 키 자체가 성립하지 않아 단독으로 생성될 수 없다. ①은 이 근본을 부정해 틀렸다.
오답 ②③④ 모두 식별자 관계·필수 참여·복합키(계약ID+항목번호) 안에서의 유일성을 바르게 설명한다.
🔑 암기 실선 = 식별자 관계 = 부모 없이는 자식 생성 불가(강한 종속).
2과목 SQL 기본 및 활용 (문 11~50)
문 11. TCL에 대한 설명으로 옳은 것의 개수는?
(가) DDL 명령어를 실행하면 해당 시점에 자동 COMMIT이 발생한다.
(나) ROLLBACK은 SAVEPOINT가 있더라도 항상 트랜잭션 전체를 처음 상태로 되돌린다.
(다) COMMIT을 수행하면 이전에 설정한 SAVEPOINT는 그대로 유지된다.
(라) 트랜잭션은 하나의 논리적 작업 단위를 의미하며, 그 안의 작업은 모두 성공하거나 모두 실패해야 한다.
- ① 1
- ② 2
- ③ 3
- ④ 4
정답 및 해설 보기
정답 ② (옳은 것: 가, 라 — 2개)
- (가) ✅ DDL(CREATE/ALTER/DROP/TRUNCATE)은 실행 즉시 자동 COMMIT된다.
- (나) ❌
ROLLBACK TO 세이브포인트로 지정 시점까지만 부분 롤백할 수 있다. "항상 전체"가 틀렸다. - (다) ❌ COMMIT으로 트랜잭션이 종료되면 그 안의 SAVEPOINT는 모두 사라진다.
- (라) ✅ 트랜잭션의 원자성(All or Nothing).
🔑 암기 DDL 자동 COMMIT · SAVEPOINT는 부분 롤백 지점 · COMMIT/전체 ROLLBACK 시 SAVEPOINT 소멸.
문 12. 아래 SQL의 실행 결과는?
| 상품ID | 상품명 | 가격 | 카테고리 |
|---|---|---|---|
| P01 | 가방 | 18000 | A |
| P02 | 신발 | 15000 | B |
| P03 | 지갑 | 15000 | A |
| P04 | 모자 | 18000 | C |
| P05 | 시계 | 20000 | B |
SELECT COUNT(DISTINCT 가격)
FROM 상품
WHERE 가격 >= (SELECT AVG(가격)
FROM 상품
WHERE 카테고리 IN ('A', 'B'));
- ① 2
- ② 3
- ③ 4
- ④ 5
정답 및 해설 보기
정답 ①
서브쿼리 먼저: 카테고리 A·B는 가방(18000)·신발(15000)·지갑(15000)·시계(20000), 평균은 17000이다. 메인쿼리는 가격 ≥ 17000인 가방(18000)·모자(18000)·시계(20000)를 남기고, COUNT(DISTINCT 가격)이 중복을 제거하면 {18000, 20000} → 2다. 실제 실행으로 검산했다.
🔑 암기 서브쿼리부터 해석 · COUNT(DISTINCT 칼럼)은 중복 제거 건수.
문 13. 오류가 발생하는 SQL은? (단, 현재 날짜는 2025년 12월 31일로 가정함)
①
SELECT EXTRACT(DAY FROM TRUNC(SYSDATE, 'YYYY')) FROM DUAL;
②
SELECT TO_CHAR(ADD_MONTHS(SYSDATE, -1), 'MM') FROM DUAL;
③
SELECT EXTRACT(MONTH FROM TO_CHAR(SYSDATE, 'YYYY-MM-DD')) FROM DUAL;
④
SELECT TO_CHAR(SYSDATE, 'YYYYMM'),
EXTRACT(YEAR FROM ADD_MONTHS(SYSDATE, 1)) FROM DUAL;
정답 및 해설 보기
정답 ③
EXTRACT는 입력값이 반드시 날짜형(DATE/TIMESTAMP)이어야 한다. ③은 TO_CHAR(...)로 날짜를 문자열로 바꾼 뒤 EXTRACT에 넘기므로 타입 불일치 오류가 난다. 실제 실행하면 ORA-30076: invalid extract field for extract source가 발생함을 확인했다.
오답 ① TRUNC(SYSDATE,'YYYY')는 2025-01-01(날짜형) → DAY 추출 1. ② ADD_MONTHS 결과(날짜형)에 TO_CHAR → 정상. ④ ADD_MONTHS 결과에 EXTRACT(YEAR) → 정상.
⚠️ 함정 EXTRACT의 입력은 날짜형. 문자열을 넣으면 오류.
문 14. SQL의 실행 결과가 아래와 같을 때, 빈칸 ㉠에 들어갈 내용으로 가장 적절한 것은? 🎯 고난도
[ORDER_HISTORY]
| ORDER_ID | CUST_ID | ORDER_DTM |
|---|---|---|
| 101 | C01 | 2025-12-05 08:00:00 |
| 102 | C02 | 2025-12-05 18:30:00 |
| 103 | C01 | 2025-12-06 00:00:00 |
| 104 | C03 | 2025-12-06 10:20:00 |
| 105 | C02 | 2025-12-06 23:59:59 |
| 106 | C04 | 2025-12-07 01:00:00 |
| 107 | C01 | 2025-12-07 12:45:00 |
[실행 결과] (TO_CHAR(ORDER_DTM,'YYYYMMDD') / TO_CHAR(ORDER_DTM,'HH24:MI'))
| CUST_ID | ORDER_DATE | ORDER_TIME |
|---|---|---|
| C01 | 20251206 | 00:00 |
| C03 | 20251206 | 10:20 |
| C02 | 20251206 | 23:59 |
①
WHERE ORDER_DTM BETWEEN TO_DATE('20251205','YYYYMMDD')
AND TO_DATE('20251206','YYYYMMDD') + 0.99999
②
WHERE ORDER_DTM >= TO_DATE('20251206','YYYYMMDD')
AND ORDER_DTM < TO_DATE('20251207','YYYYMMDD')
③
WHERE TRUNC(ORDER_DTM) BETWEEN TO_DATE('20251206','YYYYMMDD')
AND TO_DATE('20251207','YYYYMMDD')
④
WHERE ORDER_DTM BETWEEN TO_DATE('20251206','YYYYMMDD') - 0.00001
AND TO_DATE('20251206','YYYYMMDD') + 0.5
정답 및 해설 보기
정답 ②
>= 당일 00:00:00 AND < 다음날 00:00:00 패턴이 12-06 하루(00:00:00 ~ 23:59:59)를 한 건도 빠짐없이, 다른 날은 한 건도 섞이지 않게 가져온다. 보기별 실제 실행 건수로 검산했다.
- ① 시작이 12-05라 12-05의 2건까지 포함 → 5건(결과 불일치).
+0.99999(약 23:59:59)는 끝을 12-06 막차까지 맞춘 것이라 정상이지만, 시작점이 문제다. - ② 정확히 3건 → 정답.
- ③
TRUNC(ORDER_DTM)이 날짜만 비교하는데 BETWEEN이 12-07까지 포함하므로 12-07 행도 들어와 5건(결과 불일치). 게다가 칼럼에 함수를 씌워 인덱스도 못 탄다. - ④ 시작
-0.00001(12-05 약 23:59:59) ~ 끝+0.5(12-06 12:00:00) → 12-06 23:59:59 행이 빠져 2건(결과 불일치).
⚠️ 함정 ①·③·④가 틀린 이유는 "윤초·시간대 오차" 같은 모호한 사유가 아니라 구간의 시작·끝이 12-05나 12-07로 새거나 막차를 놓쳐 건수가 달라지기 때문이다.
🔑 암기 특정 일자 전체 조회 = >= 당일 AND < 다음날. 칼럼에 함수 씌우면 인덱스 무력화.
문 15. 아래 SQL의 실행 결과로 가장 적절하지 않은 것은?
(가) SELECT ROUND(-173.4, -2) FROM DUAL;
(나) SELECT TRUNC(-98.99, -1) FROM DUAL;
(다) SELECT CEIL(-12.01) FROM DUAL;
(라) SELECT FLOOR(7.999) FROM DUAL;
- ① (가) -200
- ② (나) -100
- ③ (다) -12
- ④ (라) 7
정답 및 해설 보기
정답 ②
TRUNC(-98.99, -1)은 일의 자리 아래를 버려 10단위까지 남기므로 -90이다(보기 ②는 -100이라 틀림). 실제 실행으로 -90을 확인했다.
오답 ① ROUND(-173.4,-2)는 100단위로 반올림(십의 자리 7을 올림) → -200. ③ CEIL(-12.01)은 크거나 같은 가장 작은 정수 → -12. ④ FLOOR(7.999)는 작거나 같은 가장 큰 정수 → 7.
🔑 암기 TRUNC는 버림(반올림 아님). 둘째 인자가 음수면 정수부를 절삭 — -1은 일의 자리를 버려 10단위로, -2는 십의 자리까지 버려 100단위로.
⚠️ 함정 TRUNC(-98.99, -1)을 -100으로 착각하기 쉽다. -1은 "일의 자리 아래를 버려 십의 자리까지 남김" → -90.
문 16. NULL에 대한 설명으로 가장 적절하지 않은 것은?
- ① NULL은 산술 연산에서 더하거나 뺄 수 없다.
- ② NULL은 정의되지 않은 값으로, 0이나 공백(Space)으로 대체할 수 없다.
- ③ NOT NULL 제약 조건은 해당 칼럼에 NULL 값을 허용하지 않는 조건이다.
- ④ WHERE COL = NULL과 같은 조건식을 통해 NULL 값을 비교할 수 있다.
정답 및 해설 보기
정답 ④
NULL은 정의되지 않은 값이라 =, !=, <, > 같은 일반 비교 연산자로 비교할 수 없다(= NULL의 결과는 항상 UNKNOWN → 한 건도 안 나옴). 반드시 IS NULL / IS NOT NULL을 써야 한다.
🔑 암기 NULL 비교는 = NULL(❌)이 아니라 IS NULL(✅).
문 17. 아래 SQL의 실행 결과는?
| STU_ID | SCORE | EXTRA_POINT |
|---|---|---|
| S01 | 82 | 18 |
| S02 | 47 | 9 |
| S03 | 91 | 4 |
| S04 | 60 | 25 |
SELECT CASE
WHEN SCORE + EXTRA_POINT >= 100 THEN 'A'
WHEN SCORE < EXTRA_POINT * 3 THEN 'B'
WHEN SCORE >= 90 THEN 'C'
ELSE 'D'
END AS GRADE
FROM TBL
ORDER BY STU_ID;
- ① A, B, C, B
- ② A, D, C, D
- ③ A, D, C, B
- ④ D, D, C, B
정답 및 해설 보기
정답 ③
CASE는 위에서부터 평가해 처음 참이 되는 분기를 반환한다. 실제 실행으로 A, D, C, B를 확인했다.
- S01: 82+18=100 ≥ 100 → A
- S02: 56<100, 47<27(거짓), 47≥90(거짓) → ELSE D
- S03: 95<100, 91<12(거짓), 91≥90(참) → C
- S04: 85<100, 60<75(참) → B
🔑 암기 CASE는 위→아래 순차 평가, 최초 참에서 종료.
문 18. 아래 SQL의 실행 결과는? 🎯 고난도
[회원]
| 회원ID | 회원명 |
|---|---|
| 101 | 김민수 |
| 102 | 박지은 |
| 103 | 최현우 |
[회원전화]
| 회원ID | 전화번호 |
|---|---|
| 101 | 010-234-5678 |
| 101 | 010-834-5678 |
| 102 | 011-335-0000 |
| 102 | 011-135-9999 |
| 103 | 016-2333-8888 |
| 103 | 016-133-0000 |
SELECT M.회원명, T.전화번호
FROM 회원 M, 회원전화 T
WHERE M.회원ID = T.회원ID
AND REGEXP_LIKE(T.전화번호, '^01(0|1|6)-(2|3)\d{2}');
①
| 회원명 | 전화번호 |
|---|---|
| 김민수 | 010-234-5678 |
| 박지은 | 011-335-0000 |
②
| 회원명 | 전화번호 |
|---|---|
| 김민수 | 010-834-5678 |
| 박지은 | 011-335-0000 |
| 최현우 | 016-133-0000 |
③
| 회원명 | 전화번호 |
|---|---|
| 김민수 | 010-234-5678 |
| 박지은 | 011-135-9999 |
| 최현우 | 016-2333-8888 |
④
| 회원명 | 전화번호 |
|---|---|
| 김민수 | 010-234-5678 |
| 박지은 | 011-335-0000 |
| 최현우 | 016-2333-8888 |
정답 및 해설 보기
정답 ④
패턴 ^01(0|1|6)-(2|3)\d{2}를 분해하면 — 시작이 01, 다음 한 자리는 0·1·6 중 하나, 하이픈, 그 뒤 첫 숫자가 2 또는 3, 이어 숫자 2자리. 실제 실행으로 매칭 행을 확인했다.
매칭: 010-234(국번 2 시작 ✓), 011-335(3 시작 ✓), 016-2333(2 시작 ✓) → 김민수·박지은·최현우 각 1건. 탈락: 010-834(8), 011-135(1), 016-133(1).
⚠️ 함정 \d{2} 뒤는 더 보지 않으므로 016-2333처럼 국번이 4자리여도 앞 2+33이 매칭되면 통과한다.
🔑 암기 ^(시작) · (a|b)(택1) · [ ]/( )(문자집합·그룹) · \d{2}(숫자 2자리).
문 19. 아래 테이블을 참고할 때 실행 결과가 다른 하나는?
| ID | MANAGER | TEAM |
|---|---|---|
| 10 | 김도현 | 데이터연구팀 |
| 11 | 이수진 | AI연구센터 |
| 12 | 박지훈 | 연구기획팀 |
| 13 | 최민아 | 영업1팀 |
| 14 | 정유진 | 임상연구부 |
| 15 | 한지민 | 중앙연구소 |
- ①
SELECT * FROM DEPT WHERE TEAM LIKE '%연구%'; - ②
SELECT * FROM DEPT WHERE INSTR(TEAM, '연구') > 1; - ③
SELECT * FROM DEPT WHERE REGEXP_LIKE(TEAM, '연구'); - ④
SELECT * FROM DEPT WHERE INSTR(TEAM, '연구') > 0;
정답 및 해설 보기
정답 ②
INSTR(TEAM,'연구') > 1은 '연구'가 두 번째 글자 이후에 나와야 참이므로, 맨 앞에서 시작하는 연구기획팀(위치 1)이 탈락한다. 나머지 ①③④는 '연구'가 어디든 있으면 참이라 5건으로 동일하다. 실제 실행으로 ①③④=5건, ②=4건을 확인했다.
🔑 암기 INSTR(문자열, 찾을값) = 찾은 시작 위치(맨 앞이면 1, 없으면 0). > 0은 포함, > 1은 맨 앞 제외.
문 20. 아래 SQL의 실행 결과는? 🎯 고난도
| ID | CODE | AMT |
|---|---|---|
| A1 | X1 | 120 |
| A1 | X2 | 380 |
| A2 | C1 | 100 |
| A2 | Y1 | 260 |
| A3 | C2 | 260 |
| A4 | X3 | 500 |
| A5 | C3 | NULL |
SELECT ID, CODE, AMT
FROM TBL
WHERE (ID = 'A1' AND AMT >= 300)
OR ID = 'A2'
AND (CODE LIKE 'C%' OR AMT > 250);
①
| ID | CODE | AMT |
|---|---|---|
| A1 | X2 | 380 |
| A2 | Y1 | 260 |
②
| ID | CODE | AMT |
|---|---|---|
| A1 | X2 | 380 |
| A3 | C2 | 260 |
③
| ID | CODE | AMT |
|---|---|---|
| A1 | X2 | 380 |
| A2 | C1 | 100 |
| A2 | Y1 | 260 |
④
| ID | CODE | AMT |
|---|---|---|
| A2 | Y1 | 260 |
| A3 | C2 | 260 |
정답 및 해설 보기
정답 ③
AND가 OR보다 우선순위가 높아 실제 해석은 (ID='A1' AND AMT>=300) OR (ID='A2' AND (CODE LIKE 'C%' OR AMT>250))다.
- 앞 덩어리: A1이면서 300 이상 → A1/X2/380 (1건)
- 뒤 덩어리: A2이면서 (C로 시작 or 250 초과) → A2/C1/100(C 시작 ✓), A2/Y1/260(260>250 ✓) (2건)
합쳐서 3건. 실제 실행으로 검산했다.
🔑 암기 연산자 우선순위 ( ) → NOT → AND → OR.
문 21. 아래 SQL에 대한 설명으로 가장 적절하지 않은 것은?
[데이터 모델]

SELECT COUNT(고객ID) AS A,
COUNT(주소) AS C,
COUNT(고객ID) - COUNT(주소) AS M,
COUNT(점포코드) AS B
FROM 고객;
- ① C는 주소가 있는 고객 수, M은 주소가 없는 고객 수를 의미한다.
- ② B는 점포코드가 입력된 고객 수이며, 입력된 점포코드는 모두 [서점] 테이블에 존재하는 값이어야 한다.
- ③ [고객] 테이블의 점포코드는 NULL을 허용하므로 특정 서점에 소속되지 않은 고객도 존재할 수 있다.
- ④ A는 고객ID의 중복 여부에 따라 달라지므로 실제 고객 수와 다를 수 있다.
정답 및 해설 보기
정답 ④
고객ID는 기본 키라 NULL도 중복도 없다. 따라서 COUNT(고객ID)(A)는 항상 전체 행 수 = 실제 고객 수와 일치한다. "중복 여부에 따라 달라진다"는 ④는 기본키 성질을 부정해 틀렸다.
오답 ① COUNT(주소)는 NULL 제외 → 주소 있는 고객 수, A−C는 주소 없는 고객 수. ② 점포코드는 FK라 서점에 존재하는 값. ③ FK는 NULL 허용 가능.
🔑 암기 COUNT(PK) = 전체 레코드 수(PK는 NULL·중복 불가). COUNT(칼럼) = NULL 제외 건수.
문 22. 아래 SQL의 실행 결과가 다른 하나는? (단, DBMS는 오라클로 가정함)
- ①
SELECT NVL(NULL, -100) FROM DUAL; - ②
SELECT NVL2(NULL, -100, 0) FROM DUAL; - ③
SELECT NULLIF(-100, 0) FROM DUAL; - ④
SELECT COALESCE(NULL, -100, 0, 200) FROM DUAL;
정답 및 해설 보기
정답 ②
②만 0, 나머지는 -100이다. 실제 실행으로 검산했다.
- ①
NVL(NULL,-100)→ 첫 값이 NULL이라 -100. - ②
NVL2(NULL,-100,0)→ 첫 값이 NULL이라 세 번째 인자 0. - ③
NULLIF(-100,0)→ 두 값이 다르므로 첫 값 -100. - ④
COALESCE(NULL,-100,0,200)→ NULL 아닌 첫 값 -100.
🔑 암기 NVL2(기준, NOT NULL일 때, NULL일 때) · NULLIF(a,b)는 같으면 NULL·다르면 a.
문 23. 모든 도서의 도서명과 해당 도서의 상위분류명을 출력하는 SQL로 가장 적절한 것은?
[테이블]
도서(도서ID, 도서명, 출판년도, 상위분류ID)
* 상위분류ID는 도서ID를 참조한다.
* 최상위분류는 상위분류ID가 NULL이다.
①
SELECT A.도서명, B.도서명 AS 상위분류명
FROM 도서 A LEFT OUTER JOIN 도서 B ON A.상위분류ID = B.도서ID;
②
SELECT A.도서명, B.도서명 AS 상위분류명
FROM 도서 A LEFT OUTER JOIN 도서 B ON A.도서ID = B.상위분류ID;
③
SELECT A.도서명, B.도서명 AS 상위분류명
FROM 도서 A INNER JOIN 도서 B ON A.상위분류ID = B.도서ID;
④
SELECT A.도서명, B.도서명 AS 상위분류명
FROM 도서 A RIGHT OUTER JOIN 도서 B ON A.상위분류ID = B.도서ID;
정답 및 해설 보기
정답 ①
자신을 참조하는 SELF 조인이다. "내 상위분류ID = 상위 도서의 도서ID"이므로 조인 조건은 A.상위분류ID = B.도서ID. 또 모든 도서(상위분류 없는 최상위 포함)를 남겨야 하므로 기준 A를 보존하는 LEFT OUTER JOIN을 쓴다.
오답 ② 조인 조건이 반대. ③ INNER라 최상위(상위분류 NULL)가 누락. ④ RIGHT라 보존 기준이 뒤바뀐다.
🔑 암기 누락 방지 = 기준 집합 쪽으로 OUTER JOIN(LEFT/RIGHT) 방향 잡기.
문 24. 아래 SQL의 실행 결과는?
| 상담코드 | 상담원 | 종료일자 |
|---|---|---|
| C001 | J | 2025-12-01 |
| C002 | J | 2025-12-02 |
| C003 | J | NULL |
| C004 | K | 2025-12-02 |
| C005 | K | 2025-12-03 |
| C006 | L | 2025-12-02 |
| C007 | L | NULL |
| C008 | L | NULL |
| C009 | M | 2025-12-03 |
| C010 | M | NULL |
SELECT 상담원, COUNT(종료일자) AS 종료건수
FROM 상담내역
WHERE 종료일자 IS NULL OR 종료일자 >= DATE '2025-12-02'
GROUP BY 상담원
HAVING COUNT(*) >= 2 AND COUNT(종료일자) >= 2
ORDER BY 상담원;
①
| 상담원 | 종료건수 |
|---|---|
| J | 2 |
| K | 2 |
②
| 상담원 | 종료건수 |
|---|---|
| K | 1 |
③
| 상담원 | 종료건수 |
|---|---|
| J | 1 |
| K | 2 |
④
| 상담원 | 종료건수 |
|---|---|
| K | 2 |
정답 및 해설 보기
정답 ④
WHERE가 종료일자 2025-12-01인 C001만 제외하고 나머지를 남긴다. 그룹별 COUNT(*)와 COUNT(종료일자)(NULL 제외)는 — J(2,1)·K(2,2)·L(3,1)·M(2,1). HAVING COUNT(*)>=2 AND COUNT(종료일자)>=2를 모두 만족하는 건 K뿐, 종료건수 2다. 실제 실행으로 검산했다.
⚠️ 함정 COUNT(*)는 NULL 포함 전체 행, COUNT(종료일자)는 NULL 제외. L은 3행이지만 날짜는 1개라 탈락한다.
🔑 암기 COUNT(칼럼)은 그룹 내 NULL 행을 세지 않는다.
문 25. 집합 연산자에 대한 설명으로 가장 적절하지 않은 것은?
- ① 집합 연산자는 두 집합의 합집합, 교집합, 차집합을 구할 수 있는 연산자를 제공한다.
- ② UNION은 두 질의 결과에서 중복된 행을 제거한 합집합을 반환한다.
- ③ 상호 호환되는 칼럼 타입이라도 반드시 동일한 데이터 타입으로 맞춰야 한다.
- ④ 각 SELECT 문에는 ORDER BY 절을 단독으로 사용할 수 없다.
정답 및 해설 보기
정답 ③
집합 연산은 칼럼 개수·순서가 같고 타입이 상호 호환되면 된다(CHAR↔VARCHAR2, 정수↔실수 등 암시적 형변환). "반드시 동일한 데이터 타입"이라는 단정이 틀렸다.
오답 ④ ORDER BY는 각 SELECT마다 못 쓰고 맨 마지막에 한 번만.
🔑 암기 집합 연산: 칼럼 수·순서 동일 + 타입 호환(동일까진 아님). ORDER BY는 마지막 한 번.
문 26. 가장 높은 연봉을 받는 사원의 사원명, 부서명, 연봉을 조회하는 SQL로 가장 적절한 것은? 🎯 고난도
[테이블]
사원(사원ID, 사원명, 성별)
부서(부서ID, 부서명, 위치)
급여(사원ID, 부서ID, 연봉)
* 급여 테이블의 사원ID와 부서ID는 각각 사원 테이블과 부서 테이블을 참조하는 외래 키이다.
①
SELECT E.사원명, D.부서명, G.연봉
FROM 급여 G
JOIN 사원 E
ON G.사원ID = E.사원ID, 부서 D
WHERE D.부서ID = G.부서ID
AND G.연봉 = (SELECT MAX(연봉)
FROM 급여);
②
SELECT E.사원명, D.부서명, G.연봉
FROM 사원 E
LEFT OUTER JOIN 급여 G
ON E.사원ID = G.사원ID, 부서 D
WHERE D.부서ID = G.부서ID
AND G.연봉 IN (SELECT MAX(연봉)
FROM 급여
GROUP BY 부서ID);
③
SELECT E.사원명, D.부서명, MAX(G.연봉) AS 연봉
FROM 사원 E
NATURAL JOIN 급여 G, 부서 D
WHERE D.부서ID = G.부서ID
GROUP BY E.사원명, D.부서명;
④
SELECT E.사원명, D.부서명, MAX(G.연봉) AS 연봉
FROM 부서 D
RIGHT OUTER JOIN 급여 G
ON D.부서ID = G.부서ID, 사원 E
WHERE E.사원ID = G.사원ID
GROUP BY E.사원명, D.부서명;
정답 및 해설 보기
정답 ①
전체에서 가장 높은 연봉은 = (SELECT MAX(연봉) FROM 급여)라는 단일행 서브쿼리로 잡아 WHERE에서 필터링해야 한다. 그 뒤 급여를 사원·부서와 조인하면 정확히 최고 연봉자가 나온다.
오답 ② GROUP BY 부서ID라 부서별 최고 연봉자가 모두 조회됨(전체 최고가 아님). ③④ SELECT에서 MAX(G.연봉) + GROUP BY를 쓰면 사원별 최고 연봉을 구할 뿐, 전체 최고로 거르는 조건이 없어 모든 사원이 조회된다.
🔑 암기 전체 집계값 비교는 WHERE의 단일행 서브쿼리 = (SELECT MAX ...)로.
문 27. 아래 테이블을 참고할 때 오류가 발생하는 SQL은?
| TBL1.C1 | TBL1.C2 | TBL2.C1 | TBL2.C2 | |
|---|---|---|---|---|
| 2101 | NULL | 10 | 500 | |
| 2102 | 500 | 10 | 700 | |
| 2103 | 700 | 20 | 300 | |
| 2104 | 300 | 10 | 300 | |
| 2105 | NULL | 20 | NULL |
①
SELECT C1, A.C2, B.C2 FROM TBL1 A JOIN TBL2 B USING (C1);
②
SELECT * FROM TBL1 NATURAL JOIN TBL2;
③
SELECT * FROM TBL1 A JOIN TBL2 B ON A.C1 = B.C1
AND (A.C2 = B.C2 OR A.C2 IS NULL OR B.C2 IS NULL);
④
SELECT * FROM TBL1 A NATURAL JOIN TBL2 B USING (C1);
정답 및 해설 보기
정답 ④
NATURAL JOIN은 공통 칼럼을 자동으로 찾아 조인하므로 조건을 따로 줄 수 없다. ON이나 USING을 함께 쓰면 문법 오류다. ④는 NATURAL JOIN ... USING (C1)로 둘을 동시에 써 에러가 난다.
오답 ① USING의 공통 칼럼 C1엔 접두사를 안 붙였으니 정상(A.C2, B.C2는 USING 칼럼이 아니라 가능). ②③ 정상.
⚠️ 함정 USING으로 묶은 공통 칼럼엔 테이블.칼럼 접두사 금지. NATURAL JOIN엔 ON/USING 금지.
문 28. 아래 오라클 SQL을 ANSI SQL로 변환한 것으로 가장 적절한 것은?
SELECT A.COL1, B.COL2 FROM TBL A, TBL B
WHERE A.COL1 = B.COL1(+);
①
SELECT A.COL1, B.COL2 FROM TBL A RIGHT OUTER JOIN TBL B ON A.COL1 = B.COL1;
②
SELECT A.COL1, B.COL2 FROM TBL A LEFT OUTER JOIN TBL B ON A.COL1 = B.COL1;
③
SELECT A.COL1, B.COL2 FROM TBL A INNER JOIN TBL B ON A.COL1 = B.COL1;
④
SELECT A.COL1, B.COL2 FROM TBL A LEFT OUTER JOIN TBL B ON A.COL1 = B.COL1
WHERE B.COL1 IS NOT NULL;
정답 및 해설 보기
정답 ②
(+)는 행이 부족한 쪽(여기선 B)에 붙는다. B쪽에 NULL을 채우며 A를 전부 보존하므로 A LEFT OUTER JOIN B다.
오답 ① RIGHT는 보존 기준이 반대. ③ INNER는 외부조인 아님. ④ LEFT로 해놓고 WHERE B.COL1 IS NOT NULL을 줘 매칭 없는 행을 도로 잘라내 INNER가 돼 버린다.
🔑 암기 (+)는 부족한 반대편에 → 그 반대쪽 표가 보존 기준(LEFT/RIGHT).
문 29. 아래 테이블을 참고할 때 실행 결과가 다른 하나는?
| T1 | T2 | |||
|---|---|---|---|---|
| COL1 | COL2 | COL1 | COL2 | |
| A | 100 | A | 100 | |
| A | 101 | B | 101 | |
| B | 101 | B | 888 | |
| B | 999 | X | 101 | |
| C | 101 | |||
| D | 100 | |||
| E | 500 |
①
SELECT COUNT(*) FROM T1 A
WHERE (A.COL1, A.COL2) IN (SELECT COL1, COL2 FROM T2);
②
SELECT COUNT(*) FROM T1 A, T2 B
WHERE A.COL1 = B.COL1 OR A.COL2 = B.COL2;
③
SELECT COUNT(*) FROM T1 A
WHERE EXISTS (SELECT 1 FROM T2 B WHERE A.COL1 = B.COL1 AND A.COL2 = B.COL2);
④
SELECT COUNT(*) FROM T1 A INNER JOIN T2 B
ON A.COL1 = B.COL1 AND A.COL2 = B.COL2;
정답 및 해설 보기
정답 ②
①③④는 모두 (COL1 AND COL2) 동시 일치만 세므로 (A,100)·(B,101) 2건으로 같다. ②는 OR라 COL1만 같아도, COL2만 같아도 조인돼 매칭이 폭증해 12건이 된다. 실제 실행으로 ①③④=2, ②=12를 확인했다.
🔑 암기 다중 칼럼 IN·EXISTS·INNER JOIN은 AND 결합 / OR 조인은 건수가 기하급수로 늘어난다.
문 30. 아래 조건에서 설명하는 SQL로 가장 적절한 것은?
[데이터 모델]

[조건]
모든 지점을 대상으로 매출구분이 '오프라인'인 매출금액의 합계를 지점ID와 함께 조회한다.
해당 조건에 해당하는 매출이 한 건도 없는 지점은 매출금액의 합계를 0으로 표시한다.
①
SELECT B.지점ID, NVL(SUM(A.매출금액), 0) AS 오프라인매출합계
FROM 매출실적 A JOIN 지점 B
ON A.지점ID = B.지점ID
WHERE A.매출구분 = '오프라인'
GROUP BY B.지점ID;
②
SELECT B.지점ID, NVL((SELECT SUM(A.매출금액)
FROM 매출실적 A
WHERE A.지점ID = B.지점ID
AND A.매출구분 = '오프라인'), 0) AS 오프라인매출합계
FROM 매출실적 B;
③
SELECT B.지점ID, NVL(SUM(A.매출금액), 0) AS 오프라인매출합계
FROM 지점 B LEFT OUTER JOIN 매출실적 A
ON B.지점ID = A.지점ID
WHERE A.매출구분 = '오프라인'
GROUP BY B.지점ID;
④
SELECT B.지점ID, NVL(SUM(A.매출금액), 0) AS 오프라인매출합계
FROM 지점 B LEFT OUTER JOIN 매출실적 A
ON B.지점ID = A.지점ID
AND A.매출구분 = '오프라인'
GROUP BY B.지점ID;
정답 및 해설 보기
정답 ④
모든 지점을 남기려면 지점 기준 LEFT OUTER JOIN을 쓰고, 매출구분 필터를 ON 절 안에 둬야 매출이 없는 지점도 NVL(SUM,0)으로 0을 표시할 수 있다.
오답 ① INNER + WHERE라 오프라인 매출 없는 지점이 빠진다. ② 기준이 매출실적이라 매출 없는 지점이 아예 안 나온다. ③ LEFT라도 필터를 WHERE에 두면 매출 없는 지점(매출구분 NULL)이 잘려 INNER처럼 돼 버린다.
⚠️ 함정 OUTER JOIN에서 조인 대상 테이블의 필터는 WHERE가 아니라 ON 절에. WHERE에 두면 외부조인이 내부조인으로 퇴색한다.
🔑 암기 OUTER JOIN 보존하려면 대상 필터를 ON에.
문 31. 트랜잭션의 특성으로 가장 적절하지 않은 것은?
- ① 원자성(Atomicity)
- ② 일관성(Consistency)
- ③ 독립성(Independence)
- ④ 영속성(Durability)
정답 및 해설 보기
정답 ③
트랜잭션의 4대 특성은 원자성·일관성·고립성(Isolation)·영속성(ACID)이다. 'I'는 고립성/격리성이지 '독립성(Independence)'이 아니다.
⚠️ 함정 고립성(Isolation)을 비슷한 어감의 '독립성'으로 바꾼 단어 함정.
🔑 암기 ACID = 원자성·일관성·고립성·영속성.
문 32. 모든 프로젝트에 참여한 직원의 이름을 조회하는 SQL로 가장 적절한 것은? 🎯 고난도
[테이블]
직원(직원ID, 직원명)
프로젝트(프로젝트ID, 프로젝트명)
참여내역(직원ID, 프로젝트ID)
* 참여내역 테이블의 직원ID와 프로젝트ID는 각각 직원 테이블과 프로젝트 테이블을 참조하는 외래 키이다.
①
SELECT E.직원명
FROM 직원 E JOIN 참여내역 A
ON E.직원ID = A.직원ID
GROUP BY E.직원명
HAVING COUNT(DISTINCT A.프로젝트ID) = (SELECT COUNT(*) FROM 참여내역);
②
SELECT E.직원명
FROM 직원 E
WHERE NOT EXISTS (SELECT A.프로젝트ID
FROM 참여내역 A
WHERE A.직원ID = E.직원ID
EXCEPT
SELECT P.프로젝트ID
FROM 프로젝트 P);
③
SELECT E.직원명
FROM 직원 E
WHERE NOT EXISTS (SELECT P.프로젝트ID
FROM 프로젝트 P
EXCEPT
SELECT A.프로젝트ID
FROM 참여내역 A
WHERE A.직원ID = E.직원ID);
④
SELECT E.직원명
FROM 직원 E
WHERE EXISTS (SELECT 1
FROM 프로젝트 P
WHERE NOT EXISTS (SELECT 1
FROM 참여내역 A
WHERE A.프로젝트ID = P.프로젝트ID
AND A.직원ID = E.직원ID));
정답 및 해설 보기
정답 ③
"모든 프로젝트에 참여" = "참여하지 않은 프로젝트가 하나도 없음". (전체 프로젝트) EXCEPT (그 직원의 참여 프로젝트)가 공집합일 때만 참이므로 NOT EXISTS로 감싼 ③이 정답이다.
오답 ① HAVING 비교 대상이 프로젝트 수가 아니라 참여내역 전체 건수라 틀림. ② EXCEPT 방향이 반대라 항상 공집합 → 모든 직원이 조회됨. ④ 바깥이 EXISTS라 "참여 안 한 프로젝트가 하나라도 있는 직원"을 골라 정반대 결과.
🔑 암기 관계 분할 = (전체) EXCEPT (조건) → NOT EXISTS.
문 33. 아래 SQL에 대한 설명으로 가장 적절한 것은?
SELECT 회원ID, 강좌ID, 결제금액
FROM 수강내역
WHERE 결제금액 >= (SELECT MAX(결제금액)
FROM 수강내역
GROUP BY 강좌ID);
① 모든 강좌에서 최고 결제금액 이상을 결제한 회원이 정상적으로 조회된다.
② 강좌가 두 개 이상 존재하면 서브쿼리가 여러 행을 반환하여 단일행 서브쿼리 비교에서 오류가 발생한다.
③ 아래 SQL과 항상 동일한 결과가 조회된다.
SELECT 회원ID, 강좌ID, 결제금액
FROM 수강내역
WHERE 결제금액 >= ANY (SELECT MAX(결제금액)
FROM 수강내역
GROUP BY 강좌ID);
④ 아래 SQL과 항상 동일한 결과가 조회된다.
SELECT 회원ID, 강좌ID, 결제금액
FROM 수강내역
WHERE 결제금액 >= ALL (SELECT MAX(결제금액)
FROM 수강내역
GROUP BY 강좌ID);
정답 및 해설 보기
정답 ②
GROUP BY 강좌ID 서브쿼리는 강좌마다 한 행씩 여러 행을 반환한다. 그 앞의 >=는 단일행 연산자라 여러 행과 비교하면 ORA-01427류 오류가 난다.
오답 ③ ANY는 최솟값 이상, ④ ALL은 최댓값 이상이라 의미가 다르다(오류 없이 돌지만 결과가 다름).
🔑 암기 다중행 서브쿼리는 IN·ANY·ALL·EXISTS 같은 다중행 연산자와 써야 한다.
문 34. 아래 SQL의 실행 결과는?
| TAB1 | TAB2 | |||
|---|---|---|---|---|
| COL1 | COL2 | COL1 | COL2 | |
| 10 | A | 10 | A | |
| 20 | A | 15 | B | |
| 30 | B | 25 | A | |
| 40 | A | 35 | C | |
| 50 | C | 40 | A | |
| NULL | A | NULL | A |
SELECT SUM(COL1)
FROM (SELECT COL1, COL2 FROM TAB1
UNION ALL
SELECT COL1, COL2 FROM TAB2)
WHERE COL2 IN ('A', 'C') AND COL1 >= 20;
- ① 170
- ② 190
- ③ 210
- ④ 230
정답 및 해설 보기
정답 ③
UNION ALL은 중복 제거 없이 그대로 합친다. 필터(COL2 IN ('A','C') AND COL1 >= 20) 통과분 — TAB1: 20·40·50, TAB2: 25·35·40. 합 = 20+40+50+25+35+40 = 210. 실제 실행으로 검산했다.
🔑 암기 UNION ALL은 중복 유지 · UNION은 중복 제거.
문 35. 아래 SQL의 실행 결과는?
| TBL1.CODE | TBL2.CODE | TBL3.CODE |
|---|---|---|
| 1 | 1 | 2 |
| 1 | 3 | 3 |
| 2 | 3 | 4 |
| 3 | 6 | 4 |
| 4 | 7 | |
| 5 |
SELECT CODE
FROM ( (SELECT CODE FROM TBL1 MINUS SELECT CODE FROM TBL2)
INTERSECT
(SELECT CODE FROM TBL3 MINUS SELECT CODE FROM TBL2) )
ORDER BY CODE;
①
| CODE |
|---|
| 2 |
| 4 |
②
| CODE |
|---|
| 2 |
| 4 |
| 5 |
③
| CODE |
|---|
| 2 |
| 4 |
| 7 |
④
| CODE |
|---|
| 2 |
| 4 |
| 6 |
정답 및 해설 보기
정답 ①
집합 연산자는 자체적으로 중복도 제거한다. (TBL1 MINUS TBL2) = {2,4,5}, (TBL3 MINUS TBL2) = {2,4,7}, 둘의 INTERSECT = {2, 4}. 실제 실행으로 검산했다.
🔑 암기 MINUS는 차집합 · INTERSECT는 교집합 · 모든 집합 연산은 결과 중복 제거(DISTINCT).
문 36. 아래 SQL의 실행 결과와 동일한 것은? 🎯 고난도
SELECT 회원.회원ID, SUM(구매.구매금액) AS 총구매금액
FROM 회원, 구매
WHERE 회원.회원ID = 구매.회원ID
AND 회원.회원ID IN (SELECT 회원ID FROM 구매
WHERE 구매일자 BETWEEN DATE '2025-01-01' AND DATE '2025-12-31')
GROUP BY 회원.회원ID;
①
SELECT 회원.회원ID, SUM(구매.구매금액) AS 총구매금액
FROM 회원 JOIN 구매
ON 회원.회원ID = 구매.회원ID
WHERE 구매.구매일자 BETWEEN DATE '2025-01-01' AND DATE '2025-12-31'
GROUP BY 회원.회원ID;
②
SELECT 회원.회원ID, SUM(구매.구매금액) AS 총구매금액
FROM 회원 JOIN 구매
ON 회원.회원ID = 구매.회원ID
WHERE EXISTS (SELECT 1
FROM 구매
WHERE 구매.회원ID = 회원.회원ID
AND 구매.구매일자 BETWEEN DATE '2025-01-01' AND DATE '2025-12-31')
GROUP BY 회원.회원ID;
③
SELECT 회원.회원ID, SUM(구매.구매금액) AS 총구매금액
FROM 회원, 구매
WHERE 회원.회원ID = 구매.회원ID
AND 구매.구매일자 BETWEEN DATE '2025-01-01' AND DATE '2025-12-31'
GROUP BY 회원.회원ID;
④
SELECT 회원.회원ID, SUM(구매.구매금액) AS 총구매금액
FROM 회원 LEFT JOIN 구매
ON 회원.회원ID = 구매.회원ID
AND 구매.구매일자 BETWEEN DATE '2025-01-01' AND DATE '2025-12-31'
GROUP BY 회원.회원ID;
정답 및 해설 보기
정답 ②
원본은 2025년 구매 이력이 있는 회원ID를 IN 서브쿼리로 고르기만 하고, 메인쿼리의 SUM은 그 회원의 전체 기간 구매금액을 합산한다(날짜 조건이 서브쿼리 안에만 갇혀 있다). 이와 같은 동작은 EXISTS로 회원만 거른 ②다.
오답 ①③ 날짜 조건이 메인 WHERE로 나와 SUM이 2025년분만 합산 → 결과 다름. ④ 날짜 조건이 ON에 있어 조인되는 구매가 2025년분으로 제한 → 역시 2025년분만 합산.
🔑 암기 IN/EXISTS 서브쿼리의 필터는 "어떤 행을 살릴지"만 정하지, 메인의 집계 범위를 줄이지 않는다.
문 37. 아래 SQL의 실행 결과를 순서대로 나열한 것은? 🎯 고난도
SELECT REGEXP_SUBSTR('x7k29k49', 'k.9') FROM DUAL;
SELECT REGEXP_SUBSTR('m2z880z90', 'z[0-9]{2}', 5, 1) FROM DUAL;
SELECT REGEXP_SUBSTR('u1p2p3p4', 'p\d+p', 1, 1) FROM DUAL;
- ① k49, z88, NULL
- ② k49, z90, NULL
- ③ k29, z88, p2p
- ④ k29, z90, p2p
정답 및 해설 보기
정답 ④
실제 실행으로 k29 / z90 / p2p를 확인했다.
'k.9': k + 임의 1글자 + 9 → 앞에서 첫 매칭 k29.'z[0-9]{2}', 5, 1: 5번째 글자부터 검색, z+숫자2자리 → 뒤쪽 z90.'p\d+p', 1, 1: p+숫자(1개 이상)+p → 첫 매칭 p2p.
🔑 암기 REGEXP_SUBSTR(문자열, 패턴, 시작위치, 발생순번) · .=임의 1글자 · [0-9]/\d=숫자 · {2}=2회 · +=1회 이상.
문 38. 아래 테이블을 참고할 때 오류가 발생하는 INSERT 문은?
CREATE TABLE 예약(
예약ID NUMBER PRIMARY KEY,
예약일시 DATE NOT NULL,
고객ID NUMBER,
예약상태 VARCHAR2(3) DEFAULT 'OK',
확인일시 DATE );
①
INSERT INTO 예약 (예약ID, 예약일시, 고객ID, 예약상태, 확인일시)
VALUES (300, SYSDATE, 10, '100', SYSDATE + 1);
②
INSERT INTO 예약 (예약ID, 예약일시, 고객ID)
VALUES (301, TO_DATE('2026/05/01', 'YYYY/MM/DD'), 10);
③
INSERT INTO 예약 (예약ID, 예약일시, 고객ID, 확인일시)
VALUES (302, SYSDATE - 3, 10, 20260501);
④
INSERT INTO 예약 (예약ID, 예약일시, 고객ID, 예약상태, 확인일시)
VALUES (303, SYSDATE, 10, 'END', TO_DATE('2026-05-01'));
정답 및 해설 보기
정답 ③
확인일시는 DATE 타입인데 ③은 확인일시 칼럼에 따옴표·TO_DATE 없는 숫자 20260501을 넣어 타입 불일치 오류가 난다(ORA-00932류).
오답 ① 예약상태(VARCHAR2(3))에 '100' 정상, 날짜는 SYSDATE 연산. ② NULL 허용 칼럼 생략 가능. ④ 'END'(3자) 정상, TO_DATE로 날짜 명시.
🔑 암기 DATE 칼럼엔 날짜 리터럴(DATE '...'/TO_DATE)을. 순수 숫자 직접 INSERT는 오류.
문 39. 아래 실행 결과를 출력하는 SQL로 가장 적절한 것은?
[분류]
| 카테고리 | 상위카테고리 | 조회수 |
|---|---|---|
| 3 | NULL | 90 |
| 2 | NULL | 140 |
| 5 | 3 | 60 |
| 8 | 3 | 110 |
| 6 | 2 | 160 |
| 12 | 6 | 180 |
| 15 | 6 | 210 |
[실행 결과]
| 카테고리 | 상위카테고리 | 조회수 |
|---|---|---|
| 15 | 6 | 210 |
| 6 | 2 | 160 |
| 2 | NULL | 140 |
①
SELECT 카테고리, 상위카테고리, 조회수
FROM 분류
START WITH 카테고리 = 2
CONNECT BY PRIOR 카테고리 = 상위카테고리;
②
SELECT 카테고리, 상위카테고리, 조회수
FROM 분류
START WITH 카테고리 = 2
CONNECT BY 카테고리 = PRIOR 상위카테고리;
③
SELECT 카테고리, 상위카테고리, 조회수
FROM 분류
START WITH 카테고리 = 15
CONNECT BY 카테고리 = PRIOR 상위카테고리;
④
SELECT 카테고리, 상위카테고리, 조회수
FROM 분류
START WITH 카테고리 = 15
CONNECT BY PRIOR 카테고리 = 상위카테고리;
정답 및 해설 보기
정답 ③
결과가 15→6→2로 자식에서 부모로 올라가는 역방향(상향) 전개다. 시작은 15, 조건은 카테고리 = PRIOR 상위카테고리(현재 행의 상위카테고리가 다음 행의 카테고리). 실제 실행으로 15·6·2 경로를 확인했다.
오답 ① 2에서 자식 방향(하향) → 2,6,12,15. ② 2의 상위가 NULL이라 2만. ④ 15의 자식이 없어 15만.
🔑 암기 역방향(상향) 전개 = START WITH 자식 ... CONNECT BY 현재.PK = PRIOR 부모키.
문 40. CTAS(Create Table As Select)에 대한 설명으로 가장 적절하지 않은 것은?
- ① 기본 키, 외래 키, 고유 키 등의 제약 조건은 복사되지 않는다.
- ② 생성된 테이블은 ALTER TABLE 명령으로 변경할 수 있다.
- ③ CTAS로 생성할 때 모든 데이터 타입을 새로 정의해야 한다.
- ④ 원본 테이블의 NOT NULL 제약 조건은 그대로 유지된다.
정답 및 해설 보기
정답 ③
CTAS는 AS SELECT 결과에 따라 칼럼의 데이터 타입이 자동 결정되므로 새로 정의할 필요가 없다.
오답 ① PK·FK·UNIQUE는 복사 안 됨(맞음). ④ NOT NULL은 유일하게 따라옴(맞음).
🔑 암기 CTAS: 데이터 타입·NOT NULL은 따라오고, 그 외 제약(PK/FK/UNIQUE)은 복사 안 됨.
문 41. 아래 [매출]과 실행 결과를 참고할 때 SQL의 빈칸 ㉠에 들어갈 내용은? 🎯 고난도
[매출]
| 카테고리 | 판매일자 | 지역 | 매출액 |
|---|---|---|---|
| 식품 | 20250105 | 서울 | 120000 |
| 식품 | 20250118 | 경기 | 180000 |
| 식품 | 20250203 | 서울 | 210000 |
| 식품 | 20250220 | 경기 | 130000 |
| 식품 | 20250302 | 서울 | 160000 |
| 의류 | 20250122 | 부산 | 300000 |
| 의류 | 20250127 | 서울 | 150000 |
| 의류 | 20250211 | 서울 | 220000 |
| 의류 | 20250310 | 서울 | 250000 |
| 의류 | 20250328 | 대구 | 300000 |
| 가전 | 20250214 | 경기 | 400000 |
| 가전 | 20250228 | 서울 | 210000 |
| 가전 | 20250305 | 대구 | 350000 |
| 가전 | 20250320 | 부산 | 200000 |
SELECT 카테고리, SUBSTR(판매일자, 1, 6) AS 판매월, 지역, SUM(매출액) AS 총매출
FROM 매출
GROUP BY ㉠
ORDER BY 카테고리, 판매월, 지역;
[실행 결과]
| 카테고리 | 판매월 | 지역 | 총매출 |
|---|---|---|---|
| 식품 | NULL | NULL | 800000 |
| 의류 | NULL | NULL | 1220000 |
| 가전 | NULL | NULL | 1160000 |
| NULL | 202501 | NULL | 750000 |
| NULL | 202502 | NULL | 1170000 |
| NULL | 202503 | NULL | 1260000 |
| NULL | NULL | 서울 | 1320000 |
| NULL | NULL | 경기 | 710000 |
| NULL | NULL | 부산 | 500000 |
| NULL | NULL | 대구 | 650000 |
- ①
CUBE(카테고리, SUBSTR(판매일자,1,6), 지역) - ②
CUBE((카테고리, SUBSTR(판매일자,1,6), 지역)) - ③
GROUPING SETS ((카테고리, 지역), (SUBSTR(판매일자,1,6))) - ④
GROUPING SETS ((카테고리), (SUBSTR(판매일자,1,6)), (지역))
정답 및 해설 보기
정답 ④
실행 결과에 (카테고리 단독)·(판매월 단독)·(지역 단독) 합계만 있고, 두 칼럼이 섞인 소계나 전체 총계가 없다. 각 칼럼을 독립 그룹으로 집계하는 GROUPING SETS ((카테고리),(SUBSTR(판매일자,1,6)),(지역))가 정답이다.
오답 ① CUBE는 모든 조합+총계까지 → 결과보다 많음. ② CUBE((...))는 세 칼럼을 한 묶음으로만. ③ (카테고리,지역)을 함께 묶어 단독 합계가 빠짐.
🔑 암기 독립 소계 여러 개 = GROUPING SETS · 모든 조합 = CUBE · 계층 소계 = ROLLUP.
문 42. 아래 SQL의 실행 결과는?
| 주문ID | 고객ID | 쿠폰코드 | 결제금액 |
|---|---|---|---|
| 101 | 1 | A10 | 50000 |
| 102 | 1 | NULL | 30000 |
| 103 | 2 | B20 | NULL |
| 104 | 2 | A10 | 40000 |
| 105 | NULL | B20 | 0 |
| 106 | 3 | NULL | NULL |
| 107 | NULL | NULL | NULL |
| 108 | 3 | C30 | 70000 |
| 109 | 3 | C30 | NULL |
SELECT COUNT(고객ID) AS 고객ID수,
COUNT(DISTINCT 쿠폰코드) AS 쿠폰코드수,
COUNT(결제금액) AS 결제건수
FROM 주문내역;
①
| 고객ID수 | 쿠폰코드수 | 결제건수 |
|---|---|---|
| 7 | 4 | 5 |
②
| 고객ID수 | 쿠폰코드수 | 결제건수 |
|---|---|---|
| 7 | 3 | 5 |
③
| 고객ID수 | 쿠폰코드수 | 결제건수 |
|---|---|---|
| 8 | 4 | 4 |
④
| 고객ID수 | 쿠폰코드수 | 결제건수 |
|---|---|---|
| 8 | 3 | 4 |
정답 및 해설 보기
정답 ②
COUNT(칼럼)은 NULL 제외, COUNT(DISTINCT)는 NULL·중복 제외. 실제 실행으로 검산했다.
- 고객ID: NULL(105,107) 제외 → 7
- 쿠폰코드(DISTINCT): {A10, B20, C30} → 3
- 결제금액: NULL(103,106,107,109) 제외 → 5
🔑 암기 COUNT(칼럼) NULL 제외 · COUNT(DISTINCT 칼럼) NULL+중복 제외 · COUNT(*)만 NULL 포함 전체.
문 43. 아래 SQL의 실행 결과는?
INSERT INTO T1(A) VALUES (5);
INSERT INTO T1(B) VALUES (3);
INSERT INTO T1(A) VALUES (7);
INSERT INTO T2(C) VALUES (100);
INSERT INTO T2(C) VALUES (200);
UPDATE T1 SET B = 4 WHERE B IS NULL;
DELETE FROM T1 WHERE A IS NULL;
TRUNCATE TABLE T2; -- DDL → 자동 COMMIT
INSERT INTO T2(C) VALUES (300);
INSERT INTO T1(B) VALUES (6);
UPDATE T1 SET A = 2 WHERE B = 4;
ROLLBACK;
SELECT SUM(A) + SUM(B) FROM T1;
- ① 8
- ② 12
- ③ 20
- ④ NULL
정답 및 해설 보기
정답 ③
TRUNCATE는 DDL이라 그 시점까지의 모든 DML을 자동 COMMIT한다. TRUNCATE 직전 T1 상태 (5,4)·(7,4)가 영구 확정된다. 이후의 INSERT·UPDATE는 ROLLBACK으로 취소되므로 T1은 (5,4)·(7,4)만 남는다. SUM(A)+SUM(B) = 12 + 8 = 20. 실제 실행으로 검산했다.
⚠️ 함정 ROLLBACK은 TRUNCATE 이후 작업만 되돌린다. TRUNCATE가 앞선 작업을 커밋해 버렸기 때문.
🔑 암기 DDL(TRUNCATE/CREATE/ALTER/DROP) 실행 = 자동 COMMIT(되돌릴 수 없음).
문 44. 아래 SQL의 빈칸 ㉠에 들어갔을 때 실행 결과가 다른 하나는?
CREATE TABLE 일자별방문자 (
방문일자 VARCHAR2(8),
방문자수 NUMBER,
CONSTRAINT 일자별방문자_PK PRIMARY KEY (방문일자)
);
[일자별방문자]
| 방문일자 | 방문자수 |
|---|---|
| 20251201 | 320 |
| 20251202 | 450 |
| 20251203 | 500 |
| 20251204 | 300 |
| 20251205 | 200 |
| 20251206 | 280 |
| 20251207 | 310 |
SELECT 방문일자, ㉠ AS 방문자수
FROM 일자별방문자
ORDER BY 방문일자;
- ①
LAG(방문자수) OVER(ORDER BY 방문일자) - ②
LAG(방문자수) OVER(ORDER BY 방문일자 DESC) - ③
LAG(방문자수, 1) OVER(ORDER BY 방문일자 DESC) - ④
LEAD(방문자수) OVER(ORDER BY 방문일자)
정답 및 해설 보기
정답 ①
①은 오름차순에서의 '이전 행' = 과거(어제) 방문자수다. ②③(내림차순 LAG)과 ④(오름차순 LEAD)는 모두 다음(나중) 날짜의 방문자수라 같은 결과다. 실제 실행으로 ①만 다름을 확인했다(①: 첫 행 NULL→320·450...; ②③④: 450·500...→마지막 NULL).
🔑 암기 LAG(오름차순)=과거 · LAG(내림차순)=LEAD(오름차순)=미래. 정렬 방향이 이전/다음을 뒤집는다.
문 45. 권한 제어에 대한 적절한 설명을 모두 고른 것은?
(가) ROLLBACK 명령 수행 시 트랜잭션 변경 사항뿐 아니라 그동안 수행된 GRANT 명령도 함께 취소된다.
(나) GRANT는 특정 사용자에게 필요한 권한을 명시적으로 부여하기 위해 사용하는 명령어이다.
(다) REVOKE는 권한을 회수하더라도 사용자가 다른 사용자에게 재부여한 권한에는 영향을 주지 않는다.
(라) ROLE은 여러 권한을 하나의 단위로 묶어 관리하며, 사용자에게 한 번에 부여하거나 삭제할 수 있는 논리적 그룹이다.
- ① (가), (다)
- ② (나), (라)
- ③ (나), (다), (라)
- ④ (가), (나), (라)
정답 및 해설 보기
정답 ② (옳은 것: 나, 라)
- (가) ❌ GRANT/REVOKE 같은 DCL은 자동 COMMIT이라 ROLLBACK으로 취소 안 됨.
- (나) ✅ GRANT = 권한 부여 명령.
- (다) ❌
WITH GRANT OPTION으로 재부여된 권한도 회수 시 연쇄(CASCADE) 취소된다. - (라) ✅ ROLE = 권한 묶음(논리 그룹).
🔑 암기 DCL 자동 COMMIT · REVOKE는 재부여 권한까지 CASCADE 회수 · ROLE = 권한 묶음.
문 46. 아래 [지점매출]과 실행 결과를 참고할 때 빈칸 ㉠에 들어갈 내용은?
[지점매출]
| 지점거래ID | 지점코드 | 고객ID | 매출금액 |
|---|---|---|---|
| 1 | S1 | C001 | 100 |
| 2 | S1 | C001 | 250 |
| 3 | S1 | C002 | 150 |
| 4 | S2 | C001 | 200 |
| 5 | S2 | C003 | 300 |
| 6 | S3 | C004 | 400 |
| 7 | S3 | C004 | 100 |
| 8 | S3 | C005 | 50 |
SELECT 지점코드, 고객ID, ㉠ AS 총매출금액
FROM 지점매출
ORDER BY 지점코드, 고객ID;
[실행 결과]
| 지점코드 | 고객ID | 총매출금액 |
|---|---|---|
| S1 | C001 | 350 |
| S1 | C001 | 350 |
| S1 | C002 | 500 |
| S2 | C001 | 200 |
| S2 | C003 | 500 |
| S3 | C004 | 500 |
| S3 | C004 | 500 |
| S3 | C005 | 550 |
- ①
SUM(매출금액) OVER(ORDER BY 고객ID) - ②
SUM(매출금액) OVER(PARTITION BY 지점코드) - ③
SUM(매출금액) OVER(PARTITION BY 지점코드 ORDER BY 고객ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - ④
SUM(매출금액) OVER(PARTITION BY 지점코드 ORDER BY 고객ID RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
정답 및 해설 보기
정답 ④
같은 고객ID 행들이 같은 누적합(예: S1·C001이 둘 다 350)으로 묶이려면, 정렬 동일값(tie)을 한 덩어리로 처리하는 RANGE를 써야 한다. 실제 실행으로 ④가 결과(350,350,500,200,500,500,500,550)와 일치함을 확인했다.
오답 ① PARTITION 없어 지점 구분 안 됨. ② ORDER BY 없어 누적 변화 없음(지점 총합만 반복). ③ ROWS는 행 단위라 같은 고객ID라도 행마다 누적값이 달라짐.
🔑 암기 ROWS=물리적 행 단위 · RANGE=정렬 동일값을 한 묶음으로(tie 동일 누적).
문 47. 아래 SQL의 실행 결과는? 🎯 고난도
| COL1 | COL2 | COL3 |
|---|---|---|
| 15 | 10 | 300 |
| 40 | 20 | 310 |
| 25 | 10 | 305 |
| 55 | 30 | 315 |
| 35 | 10 | 330 |
| 65 | 40 | 345 |
| 45 | 20 | 360 |
SELECT
MAX(COL1) OVER(ORDER BY COL1 DESC
ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING) AS COL1,
SUM(COL2) OVER(ORDER BY COL2, COL1
ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS COL2,
FIRST_VALUE(COL3) OVER(ORDER BY COL3 DESC
RANGE BETWEEN 20 PRECEDING AND 10 FOLLOWING) AS COL3
FROM TBL
ORDER BY COL1;
①
| COL1 | COL2 | COL3 |
|---|---|---|
| 35 | 30 | 360 |
| 40 | 40 | 360 |
| 45 | 50 | 360 |
| 55 | 70 | 360 |
| 65 | 90 | 360 |
| 65 | 70 | 360 |
| 65 | 40 | 360 |
②
| COL1 | COL2 | COL3 |
|---|---|---|
| 25 | 30 | 310 |
| 35 | 40 | 315 |
| 40 | 50 | 330 |
| 45 | 70 | 315 |
| 55 | 90 | 360 |
| 65 | 70 | 315 |
| 65 | 40 | 345 |
③
| COL1 | COL2 | COL3 |
|---|---|---|
| 35 | 10 | 310 |
| 40 | 20 | 315 |
| 45 | 30 | 330 |
| 55 | 50 | 315 |
| 65 | 70 | 360 |
| 65 | 100 | 315 |
| 65 | 140 | 345 |
④
| COL1 | COL2 | COL3 |
|---|---|---|
| 35 | 30 | 315 |
| 40 | 40 | 315 |
| 45 | 50 | 345 |
| 55 | 70 | 330 |
| 65 | 70 | 330 |
| 65 | 40 | 360 |
| 65 | 90 | 360 |
정답 및 해설 보기
정답 ④
세 윈도우(MAX·SUM·FIRST_VALUE)를 각각 계산하면 보기 ④와 정확히 일치한다. 실제 실행으로 7행을 검산했다.
MAX(COL1) ... ORDER BY COL1 DESC ROWS 2 PRECEDING~1 FOLLOWING: COL1 내림차순에서 앞 2행·뒤 1행 범위의 최댓값.SUM(COL2) ... ORDER BY COL2, COL1 ROWS CURRENT ROW~2 FOLLOWING: 현재 행부터 다음 2행까지 COL2 합.FIRST_VALUE(COL3) ... ORDER BY COL3 DESC RANGE 20 PRECEDING~10 FOLLOWING: 내림차순이라 PRECEDING이 더 큰 값 쪽이다. 각 행의 COL3을 기준으로 값 범위[COL3−20, COL3+10]에 드는 행 중 정렬상 가장 먼저(가장 큰 COL3) 값을 가져온다.
⚠️ 함정 RANGE는 정렬이 DESC면 PRECEDING/FOLLOWING의 값 방향이 뒤집힌다(앞=큰 값, 뒤=작은 값). 부호를 ASC 기준으로 착각하면 오답.
🔑 암기 RANGE는 값 기준 구간 · DESC 정렬에선 PRECEDING이 더 큰 값 쪽.
문 48. 아래 테이블에서 가장 높은 총점을 가진 학생 정보를 모두 조회하는 SQL로 가장 적절하지 않은 것은? (단, ①②의 DBMS는 오라클, ③④의 DBMS는 SQL Server로 가정함) 🎯 고난도
[성적]
| 학번 | 이름 | 중간 | 기말 | 총점 |
|---|---|---|---|---|
| 101 | 김민수 | 82 | 91 | 173 |
| 102 | 박지현 | 88 | 92 | 180 |
| 103 | 최현우 | 90 | 94 | 184 |
| 104 | 한지훈 | 92 | 92 | 184 |
| 105 | 정아름 | 75 | 89 | 164 |
①
SELECT 학번, 이름, 총점
FROM 성적
WHERE 총점 = (SELECT MAX(총점) FROM 성적);
②
SELECT 학번, 이름, 총점
FROM (SELECT 학번, 이름, 총점,
RANK() OVER(ORDER BY 총점 DESC) AS R
FROM 성적)
WHERE R = 1;
③
SELECT TOP 1 학번, 이름, 총점
FROM 성적
ORDER BY 총점 DESC;
④
SELECT TOP (1) WITH TIES 학번, 이름, 총점
FROM 성적
ORDER BY 총점 DESC;
정답 및 해설 보기
정답 ③
공동 1등(184점 2명)을 모두 조회해야 하는데, ③ TOP 1은 동점자와 무관하게 첫 1행만 잘라내 1명만 반환한다. 문제 요구("모두 조회")를 못 지키므로 가장 적절하지 않다.
오답 ① MAX와 같은 행 모두 → 공동 1등 다 나옴. ② RANK는 공동 1등에 똑같이 1 부여 → 다 나옴. ④ WITH TIES는 동점자 포함 → 다 나옴.
🔑 암기 공동 순위 포함 Top-N은 SQL Server WITH TIES/표준 RANK. 단순 TOP N은 행 수만 자른다.
문 49. 아래 SQL의 실행 결과는?
[상품재고]
| 상품ID | 상품명 | 카테고리 | 재고수량 | 할인율 |
|---|---|---|---|---|
| P101 | 모니터 | A | 30 | NULL |
| P102 | 키보드 | A | 50 | NULL |
| P103 | 마우스 | B | 80 | NULL |
| P104 | 헤드셋 | B | 20 | NULL |
| P105 | 스피커 | C | 40 | NULL |
SELECT 할인율
FROM 상품재고
WHERE (재고수량 > 0 AND 할인율 > 0)
OR (카테고리 = 'A' AND 할인율 IS NOT NULL)
OR 1 = 2;
- ① 0
- ② NULL
- ③ 오류가 발생한다.
- ④ 공집합
정답 및 해설 보기
정답 ④
할인율이 모두 NULL이라 할인율 > 0은 UNKNOWN, 할인율 IS NOT NULL은 FALSE, 1=2는 FALSE다. 어떤 행도 WHERE를 통과하지 못한다. 집계 함수 없는 일반 칼럼 조회라 결과는 NULL이 아니라 공집합(빈 결과)이다. 실제 실행으로 0행을 확인했다.
⚠️ 함정 집계 함수(MAX/SUM)였다면 빈 결과에 NULL을 반환하지만, 일반 칼럼 조회는 공집합이다.
🔑 암기 조건 만족 행 0 + 일반 조회 = 공집합 · (집계였다면 NULL).
문 50. 아래 SQL의 실행 결과는?
| 주문ID | 주문구분 | 수량 | 취소여부 |
|---|---|---|---|
| G001 | 온라인 | 10 | N |
| G002 | 온라인 | 5 | Y |
| G003 | 오프라인 | 3 | N |
| G004 | 오프라인 | NULL | N |
| G005 | 온라인 | 8 | N |
SELECT SUM(CASE WHEN 주문구분='온라인' AND 취소여부='N' THEN 수량 ELSE 0 END)
+ SUM(CASE WHEN 주문구분='오프라인' AND 취소여부='N' THEN -수량 ELSE 0 END) AS 순수수량
FROM 주문;
- ① 10
- ② 15
- ③ 13
- ④ 18
정답 및 해설 보기
정답 ②
온라인·N: G001(10)+G005(8) = 18. 오프라인·N: G003(−3), G004는 수량 NULL이라 −NULL=NULL → SUM이 무시. 합 = 18 + (−3) = 15. 실제 실행으로 검산했다.
⚠️ 함정 G004는 조건은 맞지만 수량이 NULL이라 집계에서 빠진다(−NULL은 NULL).
🔑 암기 SUM은 NULL을 연산에서 제외 · 조건부 집계는 SUM(CASE WHEN ... THEN ... ELSE 0 END).