SQL 조회 — SELECT · JOIN · 서브쿼리 · 집계 E08

쿼리 한 줄을 종이 위에서 실행해 결과 표를 그려 내는 장이다. 문법 암기가 아니라 실행 순서가 답을 만든다

왜 이 장을 하나
SQL 조회 문제는 주어진 표에 쿼리를 손으로 돌려 결과를 적는 것이다. 실행 순서 6단계, JOIN 의 행 보존 규칙, NULL 의 세 규칙, 서브쿼리를 값으로 바꾸는 순서 — 이 네 가지만 손에 붙으면 매 회 1~3문항이 확실한 점수가 된다.
다루는 것SELECT 실행 순서 · JOIN · NULL · 서브쿼리
문항기출 13 · 예상 2 · 훈련 5
끝나면쿼리를 보고 결과 표를 그린다

01왜 이 장인가

왜 이걸 하나 — 2회에서 가장 약했던 묶음이 SQL 이다. 이 장이 그 5~15점을 되찾는 첫 장이다.

11회분 220문항 중 SQL 조회는 13문항이다. 11회 중 8회에 나왔고, 2026년 2회에는 3문항이 나왔다. 정의·조작·제어(E09)와 DB 이론(E10)까지 합치면 SQL·DB 묶음이 39문항, 18%다.

몇 문항무엇을 적나보기
실행 결과 — 행 수 · 값 하나8COUNT 의 값 하나2024-1회 18번 · 2024-3회 3번 · 2025-3회 11번 · 12번 · 2026-1회 9번 · 13번 · 2026-2회 11번 · 18번
실행 결과 — 표3열 이름과 행을 표 꼴로2023-3회 8번 · 2024-1회 13번 · 2025-1회 7번
문법 빈칸1LIKE 패턴 · DESC2026-2회 6번
쿼리 작성1SELECT 문 한 줄 전부2023-1회 16번

→ 13문항 중 11문항이 「실행 결과」다. 문법을 아는 것과 결과를 정확히 세는 것은 다른 일이고, 틀리는 곳은 거의 언제나 NULL · 중복 · AND/OR 우선순위 · 열 이름 누락이다.

E01 진단의 SQL 4문항 중 3문항이 이 장 것이었다. 2회에서 SQL 이 샜다면 그 원인은 문법이 아니라 표를 손으로 돌리는 절차가 없었기 때문일 가능성이 크다. 이 장은 그 절차를 03절에서 한 줄씩 세운다.

문항 수는 이 노트의 문제 데이터(`_제작/기출/*.json`)에서 ch=E08 인 것을 센 값이다(2026-09-14). 회차별 복원본은 grandlife.co.kr, 2023~2026년 1회 문항은 이전 자료의 복원을 옮긴 것이라 문항 번호가 복원본마다 다를 수 있다.

02한눈에

왜 이걸 하나 — 시험 전날 이 절만 다시 본다. 답안에 적는 표기까지 여기 있다.

실행 순서FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. 쓰는 순서와 다르다. WHERE 는 묶기 전, HAVING 은 묶은 뒤. 별칭(AS)은 SELECT 에서 붙으므로 WHERE 에서는 못 쓴다.
JOIN 4종INNER — 짝 있는 행만. LEFT — 왼쪽 전부 + 짝 없으면 오른쪽 NULL. RIGHT — 오른쪽 전부. FULL — 양쪽 전부. CROSS — 모든 짝(m × n 행). FROM 에 쉼표로 두 테이블 + WHERE 등호는 INNER 와 같다.
집계와 NULLCOUNT(*) 는 행 수, COUNT(열) 은 NULL 을 뺀 개수. SUM · AVG · MAX · MIN 은 NULL 을 빼고 계산한다. 맞는 행이 하나도 없으면 COUNT 는 0, 나머지는 NULL.
NULL 비교= · > · < 로 NULL 과 비교하면 참도 거짓도 아니라 WHERE 를 통과하지 못한다. NULL 을 찾을 때는 IS NULL, 아닌 것은 IS NOT NULL.
서브쿼리 3꼴값 하나(= · > 비교) · 집합(IN · NOT IN · EXISTS) · 상관(바깥 행의 값을 안에서 쓴다 — 바깥 행마다 다시 계산). 안에서 밖으로 값으로 바꾼다.
LIKE% 는 0글자 이상, _ 는 딱 1글자. '이%' 시작, '%이' 끝, '%이%' 포함. 작은따옴표 필수.
정렬 · 중복ORDER BY 열 ASC(기본) · DESC. DISTINCT 는 중복 행 제거. UNION 은 합치며 중복 제거, UNION ALL 은 그대로. GROUP BY 결과는 묶음당 한 행.
답안 표기「결과를 쓰시오」면 열 이름 + 값. 「행 수」면 숫자 하나. 「표 형태」면 열 이름 줄과 값 줄을 나눠 적고, 열 순서는 SELECT 순서. 문자열은 작은따옴표, 키워드는 대문자.

03밑바닥

왜 이걸 하나 — 답이 왜 그 값인지 밑에서부터 세워야, 표가 바뀌어도 같은 절차로 푼다.

3-1. 쿼리는 행 집합을 깎아 내려간다

SELECT 문은 위에서 아래로 읽히지 않는다. 데이터베이스는 먼저 FROM 으로 표를 집고, WHERE 로 행을 버리고, GROUP BY 로 묶고, HAVING 으로 묶음을 버리고, 그제야 SELECT 의 열을 고르고, 마지막에 ORDER BY 로 줄을 세운다. 그래서 손으로 풀 때도 이 순서로 표를 깎아야 중간 행 수가 맞는다.

2026년 2회 6번의 학생 표(5행)에 아래 쿼리를 돌리는 과정을 단계별로 세어 보자.

SELECT 학년, COUNT(*) AS 인원 FROM 학생 WHERE 이름 LIKE '이%' GROUP BY 학년 HAVING COUNT(*) >= 2 ORDER BY 학년 DESC;
단계무엇을 하나남는 것
FROM 학생표를 집는다5행
WHERE 이름 LIKE '이%'이 로 시작하는 이름만이순신(3) · 이상(4) · 이정재(3) → 3행
GROUP BY 학년학년별로 묶는다3학년 묶음(2행) · 4학년 묶음(1행) → 묶음 2개
HAVING COUNT(*) >= 22행 미만 묶음을 버린다3학년 묶음만 → 1묶음
SELECT 학년, COUNT(*)열을 고른다3, 2
ORDER BY 학년 DESC줄을 세운다한 행이라 그대로

→ WHERE 와 HAVING 의 차이가 여기서 보인다. WHERE 는 행을 거르고 HAVING 은 묶음을 거른다. 그래서 HAVING 에는 COUNT · AVG 같은 집계가 올 수 있고 WHERE 에는 못 온다.

별칭도 같은 이유로 갈린다. AS 인원 은 SELECT 단계에서 붙는 이름이라 그보다 먼저 도는 WHERE 나 HAVING 에서는 존재하지 않는다. 문제에 HAVING 인원 >= 2 처럼 적혀 있다면 그 DBMS 가 허용하는지 여부와 별개로, 답안에는 HAVING COUNT(*) >= 2 로 적는 것이 안전하다.

3-2. JOIN 은 곱한 뒤 거른다

두 표를 잇는 원리는 하나다. 먼저 모든 행의 짝을 만들고(m × n), ON 조건이 참인 짝만 남긴다. 이것이 INNER JOIN 이고, FROM 에 두 표를 쉼표로 놓고 WHERE 에 등호를 쓴 것도 같은 결과다.

OUTER 는 거기에 한 가지를 더한다. 짝을 못 찾은 행을 버리지 않고 상대편 열을 NULL 로 채워 남긴다. LEFT 는 왼쪽 표의 행을, RIGHT 는 오른쪽 표의 행을, FULL 은 양쪽을 남긴다.

2026년 2회 18번의 A(3행) · B(4행)로 RIGHT OUTER JOIN 의 결과를 그리면 이렇다.

A.idA.vB.idB.w어떻게 생겼나
1101100B 의 1 에 A 의 첫 행이 짝
1201100B 의 1 에 A 의 둘째 행도 짝 — B 행이 두 번 나온다
2302200짝 하나
NULLNULL3300A 에 3 이 없어 A 쪽이 NULL
NULLNULL4400A 에 4 가 없어 A 쪽이 NULL

→ 5행이다. B 의 4행보다 많은 이유는 id 1 이 A 에 두 번 있어서고, 여기에 WHERE A.id IS NULL 을 걸면 아래 2행만 남아 답이 2 가 된다. 행 수를 셀 때는 한쪽에 같은 키가 여러 번 있는지를 먼저 본다.

같은 표로 LEFT 를 돌리면 A 의 3행이 각각 짝을 찾아 3행이고, INNER 도 3행이다. CROSS 는 조건 없이 3 × 4 = 12행이다. 2025년 3회 11번의 CROSS JOIN … WHERE A.NAME LIKE B.RULE 은 이 12행 같은 곱을 만든 뒤 LIKE 로 거르는 꼴이다.

3-3. NULL 은 값이 아니라 「모른다」다

NULL 은 0 도 빈 문자열도 아니다. 그래서 x > NULL 이나 x = NULL 은 참도 거짓도 아닌 모른다(UNKNOWN)가 되고, WHERE 는 참인 행만 통과시키므로 그 행은 빠진다. NULL 인 행을 잡으려면 IS NULL 뿐이다.

집계에서는 규칙 세 개가 전부다. COUNT(*) 는 행 자체를 세니 NULL 도 센다. COUNT(열) · SUM · AVG · MAX · MIN 은 그 열이 NULL 인 행을 계산에서 아예 뺀다. 맞는 행이 하나도 없으면 COUNT 는 0 이지만 AVG · SUM 은 NULL 이다.

2025년 3회 12번이 이 세 규칙을 한 문제에 담았다. 표 5행에 WHERE COL1 IN (2, 3) OR COL2 IN (3, 5) 는 5행을 다 통과시키지만, COUNT(COL2) 는 COL2 가 NULL 인 첫 행을 빼서 4 다. COUNT(*) 였다면 5 다.

세 번째 규칙이 상관 서브쿼리와 만나면 함정이 된다. 2026년 2회 11번에서 x = 10 인 행은 「자기보다 작은 x」가 없어 안쪽 AVG 가 NULL 이 되고, 10 > NULL 이 통과하지 못해 그 행이 빠진다. 「빈 집합의 평균은 0」이라고 생각하면 답이 4 로 틀린다.

3-4. 서브쿼리는 안에서 밖으로 값으로 바꾼다

서브쿼리는 세 꼴뿐이다. 값 하나를 돌려주면 = · > 로 비교하고, 여러 값이면 IN 으로 집합에 드는지 보고, 바깥 행의 값을 안에서 쓰면 상관 서브쿼리라 바깥 행마다 안쪽을 다시 계산한다. 어느 꼴이든 푸는 순서는 같다 — 가장 안쪽을 값으로 바꾸고, 그 값을 바깥에 넣는다.

2024년 3회 3번은 서브쿼리가 3겹이다. 가장 안쪽 GROUP BY project_id HAVING COUNT(*) < 2 → 20. 그 위 SELECT name … WHERE project_id IN (20) → Beta. 바깥 JOIN 에서 p.name IN ('Beta') → Charlie 1행. 세 겹이라도 한 겹씩 값으로 바꾸면 한 줄짜리 문제가 된다.

상관 서브쿼리는 표로 푼다. 바깥 행을 한 줄씩 놓고, 그 행의 값으로 안쪽을 계산한 결과를 옆 칸에 적고, 조건이 참인지를 마지막 칸에 적는다. 2026년 2회 11번을 이 표로 풀면 x = 10 · 20 · 30 · 40 네 줄에 AVG 가 NULL · 10 · 13.3 · 18.75 로 적히고, 참인 줄이 3개다.

이 장을 통과했다고 말할 수 있는 기준은 넷이다. 첫째, 실행 순서 6단계를 안 보고 적는다. 둘째, 표 두 개를 주면 INNER · LEFT · RIGHT · CROSS 의 행 수를 손으로 센다. 셋째, NULL 세 규칙(비교는 통과 못 함 · COUNT(열)은 뺀다 · 빈 집합 AVG 는 NULL)을 말한다. 넷째, 서브쿼리를 안에서 밖으로 값으로 바꿔 적는다. 넷 중 하나라도 막히면 04절 기출을 풀기 전에 이 절로 돌아온다.
COUNT(열)도 행 수니까 COUNT(*)와 같다.COUNT(열) 은 그 열이 NULL 인 행을 세지 않는다. 2025년 3회 12번은 이 차이 하나로 4 와 5 가 갈렸다. 문제에 COUNT 가 보이면 괄호 안이 * 인지 열 이름인지부터 동그라미 친다.
AND 와 OR 는 왼쪽부터 차례로 계산된다.AND 가 먼저 묶인다. KIND = 'A' AND SCORE >= 80 OR KIND = 'C' 는 (A 이면서 80 이상) 또는 (C) 다. 2024년 1회 18번의 답 1 은 이 우선순위에서 나왔고, 괄호를 옮긴 훈련 1번은 0 이 된다.

04기출

왜 이걸 하나 — 03절의 절차를 기출 13문항과 예상 2문항에 그대로 대 본다. 회차순이라 꼴이 어떻게 반복되는지도 보인다.

푸는 법은 하나다. 표를 종이에 옮기고, 실행 순서대로 행을 지워 가며 남는 행 수를 적고, 마지막에 SELECT 열만 골라 답 칸에 옮긴다. 머릿속으로만 세지 않는다 — 2회에서 샌 점수는 거기서 나왔을 가능성이 가장 크다.

2023-1회 · 16번SQL

성적 테이블에서 과목이름별 최소점수·최대점수를 구하되, 평균 점수가 90 이상인 과목만 조회하는 SQL을 작성하시오. (컬럼 별칭: 최소점수, 최대점수)

정답SELECT 과목이름, MIN(점수) AS 최소점수, MAX(점수) AS 최대점수 FROM 성적 GROUP BY 과목이름 HAVING AVG(점수) >= 90;
"과목별로 묶어라"=GROUP BY, "묶음의 평균 조건"=HAVING(WHERE 아님!). 그룹 조건에 WHERE를 쓰면 오답 — WHERE는 묶기 전 개별 행, HAVING은 묶은 후 그룹 조건입니다.
2023-3회 · 8번SQL

다음 두 테이블과 SQL문의 실행 결과를 순서대로 쓰시오.

T1.A
1
2
3
3
T2.A
2
4
SELECT A FROM T1 UNION SELECT A FROM T2 ORDER BY A DESC;
정답4
3
2
1 (열 이름 A, 4행)
UNION 은 두 결과를 합치면서 중복을 지운다. T1 의 3 이 두 번 있고 2 가 양쪽에 있어도 결과에는 한 번씩 → 1·2·3·4. UNION ALL 이었다면 1·2·3·3·2·4 여섯 행이다. ORDER BY A DESC 로 4·3·2·1. sqlite 로 실행해 확인했다. 이전 자료는 이 문항을 「출제 오류로 전원 정답 처리」라고 적었는데 복원본마다 달라 확정할 수 없다 — 여기서는 복원된 문제를 그대로 푼다. 복원 출처는 grandlife.co.kr 회차별 복원 페이지(2026-09-14 대조). 공식 원문이 아니라 문장·값이 실제와 다를 수 있고, 문항 번호도 복원본마다 다르다.
2024-1회 · 13번SQL

다음 SQL을 실행한 결과를 쓰시오.

SAMPLE

AB
1a
2b
3c
SELECT B FROM SAMPLE WHERE A IN ( SELECT A FROM SAMPLE WHERE A <= 2 );
정답열 이름 B, 값 a · b (2행)
안쪽 서브쿼리가 먼저 A ≤ 2 인 {1, 2} 를 만들고, 바깥은 A 가 그 집합에 든 행의 B 만 고른다. 결과를 「쓰시오」라고 했으니 열 이름 B 와 값 a, b 를 위아래로 적는다. sqlite 로 실행해 확인했다. 복원 출처는 grandlife.co.kr 회차별 복원 페이지(2026-09-14 대조). 공식 원문이 아니라 문장·값이 실제와 다를 수 있고, 문항 번호도 복원본마다 다르다.
2024-1회 · 18번SQL

다음 SQL문의 실행 결과를 쓰시오.

RESULT

IDKINDSCORE
1A70
2B90
3A50
4C95
SELECT COUNT(*) FROM RESULT WHERE KIND = 'A' AND SCORE >= 80 OR KIND = 'C';
정답1
AND 가 OR 보다 먼저 묶인다. 조건은 (KIND = A AND SCORE ≥ 80) OR (KIND = C). A 이면서 80 이상인 행은 없고, C 인 행은 ID 4 하나 → 1. 괄호 없이 AND 와 OR 가 섞이면 이 우선순위가 답을 가른다. sqlite 로 실행해 확인했다. 복원 출처는 grandlife.co.kr 회차별 복원 페이지(2026-09-14 대조). 공식 원문이 아니라 문장·값이 실제와 다를 수 있고, 문항 번호도 복원본마다 다르다.
2024-3회 · 3번SQL

다음 테이블과 SQL문을 보고 출력값을 쓰시오.

employee

employee_idnameproject_id
1Alice10
2Bob10
3Charlie20
4Dana30
5Ethan30

project

project_idname
10Alpha
20Beta
30Gamma
SELECT COUNT(*) FROM employee AS e JOIN project AS p ON e.project_id = p.project_id WHERE p.name IN ( SELECT name FROM project WHERE project_id IN ( SELECT project_id FROM employee GROUP BY project_id HAVING COUNT(*) < 2 ) );
정답1
가장 안쪽부터 푼다. employee 를 project_id 로 묶어 인원이 2명 미만인 것 → 20 하나(Charlie 뿐). 그 프로젝트 이름 → Beta. JOIN 결과에서 이름이 Beta 인 행 → Charlie 한 명 → COUNT 1. 서브쿼리가 3겹이라도 안에서 밖으로 한 겹씩 값으로 바꾸면 된다. sqlite 로 실행해 확인했다. 복원 출처는 grandlife.co.kr 회차별 복원 페이지(2026-09-14 대조). 공식 원문이 아니라 문장·값이 실제와 다를 수 있고, 문항 번호도 복원본마다 다르다.
2025-1회 · 7번SQL

다음 두 테이블과 SQL문을 보고 조회 결과를 테이블 형태로 쓰시오.

직원

직원번호이름
101이순신
102강감찬
103김유신

보상

직원번호성과급
1011000
102300
103450
SELECT e.이름, b.성과급 FROM 직원 e, 보상 b WHERE e.직원번호 = b.직원번호 AND b.성과급 >= 500;
정답이름 | 성과급
이순신 | 1000
FROM 에 테이블 둘을 쉼표로 적고 WHERE 로 직원번호를 맞추는 것은 내부 조인과 같다. 세 직원이 짝지어진 뒤 성과급 ≥ 500 은 이순신 1000 뿐. 「테이블 형태」로 쓰라고 했으니 열 이름 줄 + 값 줄, 열 순서는 SELECT 순서대로 이름 · 성과급. sqlite 로 실행해 확인했다. 복원 출처는 grandlife.co.kr 회차별 복원 페이지(2026-09-14 대조). 공식 원문이 아니라 문장·값이 실제와 다를 수 있고, 문항 번호도 복원본마다 다르다.
2025-3회 · 11번SQL

다음 A, B 테이블과 SQL문을 보고 출력되는 행의 수를 쓰시오.

A 테이블

NAME
Smith
Allen
Scott

B 테이블

RULE
S%
%T%
SELECT COUNT(*) AS CNT FROM A CROSS JOIN B WHERE A.NAME LIKE B.RULE;
정답4
CROSS JOIN 은 3 × 2 = 6쌍을 만들고, LIKE 가 쌍마다 맞는지 본다. S% 는 S 로 시작 → Smith · Scott. %T% 는 T 가 들어 있음 → Smith · Scott. 4쌍. 가답안은 4 이고 sqlite(LIKE 가 대소문자를 구분하지 않음)로도 4 다. 다만 Scott 의 t 는 소문자라 대소문자를 구분하는 DBMS 라면 %T% 에 안 걸려 3 이 된다 — 복원본이 대문자 SCOTT 였을 수 있다. 복원 출처는 grandlife.co.kr 회차별 복원 페이지(2026-09-14 대조). 공식 원문이 아니라 문장·값이 실제와 다를 수 있고, 문항 번호도 복원본마다 다르다.
2025-3회 · 12번SQL

다음 SQL문의 실행 결과를 쓰시오. 테이블 A의 데이터가 다음과 같을 때 조회 결과를 구하시오.

A

COL1COL2
2NULL
36
23
NULL3
45
SELECT COUNT(COL2) FROM A WHERE COL1 IN (2, 3) OR COL2 IN (3, 5);
정답4
WHERE 는 OR 라 다섯 행이 전부 통과한다(4행은 COL1 이 NULL 이지만 COL2 = 3 으로 통과). 그런데 COUNT(COL2) 는 NULL 을 세지 않으므로 첫 행이 빠져 4. COUNT(*) 였다면 5. sqlite 로 둘 다 실행해 4 와 5 를 확인했다. 복원 출처는 grandlife.co.kr 회차별 복원 페이지(2026-09-14 대조). 공식 원문이 아니라 문장·값이 실제와 다를 수 있고, 문항 번호도 복원본마다 다르다.
2026-1회 · 9번SQL

STUDENT 테이블(총 200명, 학과 3개)에서: ① SELECT COUNT(*) FROM STUDENT;SELECT COUNT(DISTINCT 학과) FROM STUDENT;SELECT COUNT(DISTINCT 학과) FROM STUDENT WHERE 학과='전산과'; 각각의 결과는?

정답① 200 ② 3 ③ 1
③이 함정: WHERE로 전산과만 남긴 뒤 DISTINCT 학과를 세면 '전산과' 하나뿐이므로 1 (전산과 학생 수가 아님!). DISTINCT+WHERE 조합을 침착하게 순서대로.
2026-1회 · 13번SQL

다음 두 테이블과 SQL문을 보고 반환되는 값을 쓰시오.

employee 테이블

dep_id
10
20
20

dept 테이블

dept_idbudget
10100
20300
30200
SELECT COUNT(*) FROM employee e JOIN dept d ON e.dep_id = d.dept_id WHERE d.budget > ( SELECT AVG(budget) FROM dept );
정답2
서브쿼리는 상관이 없는 단일 값이라 한 번만 계산된다. AVG(budget) = (100+300+200)/3 = 200. 200 보다 큰 부서는 20 하나, employee 에 dep_id 20 이 2행 → 2. sqlite 로 실행해 확인했다. 이전 자료는 「테이블명 오타로 전원 정답 처리」라고 적었으나 확정할 수 없다. 복원 출처는 grandlife.co.kr 회차별 복원 페이지(2026-09-14 대조). 공식 원문이 아니라 문장·값이 실제와 다를 수 있고, 문항 번호도 복원본마다 다르다.
2026-2회 · 6번SQL

다음 [학생] 테이블을 이용해 이름이 ‘이’로 시작하는 학생들을 이름 기준 내림차순으로 정렬하려고 한다. [쿼리]의 빈칸 ①과 ②에 들어갈 알맞은 내용을 쓰시오.

[학생]

학번이름학년학과
202101이순신3컴퓨터공학
202102김영희1전기공학
202103이상4건축공학
202104임꺽정2전자공학
202105이정재3토목공학

[쿼리]

SELECT * FROM 학생 WHERE 이름 LIKE ① ORDER BY 이름 ②;
정답① '이%' ② DESC
% 는 0글자 이상 아무 글자다. 「이로 시작」이면 '이%', 「이로 끝」이면 '%이', 「이가 들어 있다」면 '%이%'. 내림차순은 DESC, 생략하면 ASC(오름차순)다. sqlite 로 돌리면 이정재 · 이순신 · 이상 순으로 3행이 나온다. 작은따옴표까지 적는 것이 안전하다. 복원 출처는 grandlife.co.kr 2026-2회 복원 풀이(2026-09-14 대조). 공식 원문이 아니라 문장·변수명·수치가 실제와 다를 수 있다.
2026-2회 · 11번SQL

다음 테이블과 SQL문을 보고 실행 결과를 쓰시오.

[A]

idx
110
220
330
440

[B]

idy
15
115
220
335
550
SELECT COUNT(*) FROM A WHERE A.x > ( SELECT AVG(B.y) FROM B WHERE B.id IN ( SELECT A2.id FROM A AS A2 WHERE A2.x < A.x ) );
정답3
바깥 A 의 행마다 안쪽이 다시 계산되는 상관 서브쿼리다. x=10 → 더 작은 x 가 없어 id 집합이 비고 AVG 는 NULL, 10 > NULL 은 참이 아니다. x=20 → id{1} → AVG(5,15)=10, 20>10 참. x=30 → id{1,2} → AVG(5,15,20)=13.3, 참. x=40 → id{1,2,3} → AVG(5,15,20,35)=18.75, 참. 3행. sqlite 로 돌려 3 을 확인했다. NULL 비교가 거짓이 되는 것이 이 문제의 함정이다. 복원 출처는 grandlife.co.kr 2026-2회 복원 풀이(2026-09-14 대조). 공식 원문이 아니라 문장·변수명·수치가 실제와 다를 수 있다.
2026-2회 · 18번SQL

다음 테이블과 SQL문의 실행 결과를 쓰시오.

[A]

idv
110
120
230

[B]

idw
1100
2200
3300
4400
SELECT COUNT(*) FROM A RIGHT OUTER JOIN B ON A.id = B.id WHERE A.id IS NULL;
정답2
RIGHT OUTER JOIN 은 오른쪽 B 의 모든 행을 남긴다. id 1 은 A 에 2행이라 2행, id 2 는 1행, id 3 · 4 는 짝이 없어 A 쪽이 NULL 인 채 1행씩. WHERE A.id IS NULL 은 그 짝 없는 2행만 고른다. sqlite 3.49 로 돌려 2 를 확인했다. 복원 출처는 grandlife.co.kr 2026-2회 복원 풀이(2026-09-14 대조). 공식 원문이 아니라 문장·변수명·수치가 실제와 다를 수 있다.
예상 1회 · 10번SQL빈출 원리: GROUP BY + HAVING

성적 테이블이 다음과 같을 때 쿼리 결과를 쓰시오.

이름 과목 점수 SELECT 이름, AVG(점수) AS 평균 김A 수학 90 FROM 성적 김A 영어 80 GROUP BY 이름 이B 수학 70 HAVING AVG(점수) > 85; 이B 영어 100 박C 수학 95
정답박C | 95
이름별 평균: 김A=(90+80)/2=85, 이B=(70+100)/2=85, 박C=95. HAVING > 85(초과!)이므로 85는 탈락, 박C만 통과. 부등호에 =이 포함되는지가 승부처 — 2023 1회 Q16 유형.
예상 2회 · 10번SQL빈출 원리: INNER vs LEFT JOIN

사원(사번, 이름, 부서코드): (1,김,A) (2,이,B) (3,박,NULL) / 부서(부서코드, 부서명): (A,영업) (B,개발)
SELECT COUNT(*) FROM 사원 s INNER JOIN 부서 d ON s.부서코드 = d.부서코드;
SELECT COUNT(*) FROM 사원 s LEFT JOIN 부서 d ON s.부서코드 = d.부서코드; 각각의 결과는?

정답① 2 ② 3
INNER = "양쪽에 모두 있는 짝만"(박은 부서코드 NULL이라 탈락 → 2). LEFT = "왼쪽(사원)은 전원 유지, 짝 없으면 NULL 채움"(박도 포함 → 3). JOIN 행 수 계산은 2023 2회·2024 2회 기출 흐름.

05틀리는 자리

왜 이걸 하나 — 아는 문제에서 깎이는 자리는 정해져 있다. 채점자의 눈으로 답안을 다시 본다.

자리새는 꼴적는 법기출
결과의 꼴행 수를 묻는데 값을 적는다, 값을 묻는데 행 수를 적는다문제의 마지막 문장을 다시 읽는다 — 「행의 수」「반환되는 값」「결과를 쓰시오」「테이블 형태로」2025-3회 11번 · 2025-1회 7번
열 이름값만 적고 열 이름을 뺀다「결과를 쓰시오」면 열 이름 줄부터 적는다. 별칭이 있으면 별칭이 열 이름이다2024-1회 13번 · 2023-3회 8번
행 순서ORDER BY 를 무시하고 표 순서대로 적는다DESC 면 큰 것부터. ORDER BY 가 없으면 표 순서대로 적되, 순서가 채점 대상이 아닐 수 있다2023-3회 8번
NULLNULL 을 0 으로 세거나, NULL 비교를 통과시킨다COUNT(열) · AVG 는 NULL 을 뺀다. 빈 집합 AVG 는 NULL 이고 비교는 통과 못 한다2025-3회 12번 · 2026-2회 11번
중복UNION 에서 중복을 남기거나, RIGHT JOIN 에서 같은 키의 곱을 놓친다UNION 은 중복 제거, UNION ALL 은 유지. 한쪽에 같은 키가 n 개면 n 행이 된다2023-3회 8번 · 2026-2회 18번
AND · OR왼쪽부터 계산한다AND 를 먼저 괄호로 묶고 본다2024-1회 18번
따옴표 · 대소문자LIKE 패턴에 따옴표를 빼거나, 키워드를 소문자로 적는다문자열은 '이%' 처럼 작은따옴표까지, 키워드는 대문자2026-2회 6번
빈칸 여러 개첫 칸만 맞고 둘째 칸은 비워 둔다소문항은 부분 점수가 있다. 모르는 칸도 가장 그럴듯한 것을 적는다2026-2회 6번 · 2026-1회 9번

「결과를 쓰시오」 유형에서 열 이름을 빼는 것이 가장 흔한 감점이다. 채점자는 정답표의 표 꼴과 대조하므로, 열 이름 한 줄 + 값 줄을 기본 꼴로 삼고 행 수만 묻는 문제에서만 숫자 하나를 적는다.

06훈련

왜 이걸 하나 — 기출과 같은 표에서 조건 하나만 바꾼 문제다. 답이 바뀌는 이유를 말할 수 있으면 원리가 손에 붙은 것이다.

5문항은 전부 04절의 표를 다시 쓴다. 괄호 위치, COUNT 의 괄호 안, JOIN 의 방향, LIKE 의 % 자리, 빈 집합 — 바뀐 것은 하나씩이고 답은 전부 바뀐다.

훈련 SQL · 1번SQL

다음 테이블과 SQL문의 실행 결과를 쓰시오.

RESULT

IDKINDSCORE
1A70
2B90
3A50
4C95
SELECT COUNT(*) FROM RESULT WHERE KIND = 'A' AND (SCORE >= 80 OR KIND = 'C');
정답0
2024-1회 18번과 같은 표에 괄호만 옮겼다. 이제 KIND = A 가 바깥 조건이라 A 인 두 행(70 · 50)만 남고, 그 안에서 SCORE ≥ 80 도 KIND = C 도 아니므로 0. 괄호 하나가 1 을 0 으로 바꾼다. sqlite 로 확인했다.
훈련 SQL · 2번SQL

다음 테이블과 SQL문의 실행 결과를 쓰시오.

A

COL1COL2
2NULL
36
23
NULL3
45
SELECT COUNT(*), COUNT(COL1), AVG(COL2) FROM A;
정답5, 4, 4.25
COUNT(*) 는 행 수 5. COUNT(COL1) 은 NULL 인 4행을 빼 4. AVG(COL2) 는 NULL 을 분모에서도 빼 (6+3+3+5)/4 = 4.25. NULL 을 0 으로 치면 17/5 = 3.4 가 나와 틀린다. sqlite 로 확인했다.
훈련 SQL · 3번SQL

다음 두 테이블과 SQL문의 실행 결과 행 수를 쓰시오.

A

idv
110
120
230

B

idw
1100
2200
3300
4400
SELECT COUNT(*) FROM A LEFT OUTER JOIN B ON A.id = B.id;
정답3
2026-2회 18번의 표를 방향만 바꿨다. LEFT 는 왼쪽 A 의 행을 전부 남긴다. A 의 3행이 각각 B 의 짝을 하나씩 찾으므로 3. RIGHT 였다면 B 의 4행에 id 1 이 두 번 붙어 5 다. sqlite 로 둘 다 확인했다.
훈련 SQL · 4번SQL

다음 테이블과 SQL문의 실행 결과를 쓰시오.

학생

학번이름학년학과
202101이순신3컴퓨터공학
202102김영희1전기공학
202103이상4건축공학
202104임꺽정2전자공학
202105이정재3토목공학
SELECT 학년, COUNT(*) AS 인원 FROM 학생 WHERE 이름 LIKE '%이%' GROUP BY 학년 HAVING COUNT(*) >= 1 ORDER BY 학년;
정답학년 | 인원
3 | 2
4 | 1
2026-2회 6번의 표다. '%이%' 는 어디든 이 가 들어 있는 이름 → 이순신 · 이상 · 이정재 (임꺽정은 아니다). 학년으로 묶으면 3학년 2명, 4학년 1명. HAVING 은 묶은 뒤의 조건이라 여기서는 아무것도 안 거른다. 결과는 열 이름과 함께 두 줄. sqlite 로 확인했다.
훈련 SQL · 5번SQL

다음 테이블과 SQL문의 실행 결과를 쓰시오.

A

idx
110
220
330
440
SELECT COUNT(*) FROM A WHERE x > (SELECT AVG(x) FROM A WHERE x > 100);
정답0
서브쿼리의 WHERE 에 맞는 행이 없으면 AVG 는 NULL 이다. x > NULL 은 참도 거짓도 아닌 UNKNOWN 이라 WHERE 를 통과하지 못해 0. 「빈 집합의 평균은 0」이라고 생각하면 4 라고 틀린다. 2026-2회 11번의 x = 10 행이 이 원리로 빠졌다. sqlite 로 확인했다.

직접 돌려 보는 법

파이썬에 sqlite 가 들어 있어 설치 없이 쿼리를 확인할 수 있다. 파워셸에서 아래를 그대로 치면 2026년 2회 18번의 답 2 가 찍힌다. 표와 쿼리만 바꿔 가며 손으로 센 값과 기계가 센 값을 맞춰 보는 것이 이 장의 훈련이다.

python -c "import sqlite3; c=sqlite3.connect(':memory:'); c.executescript('create table A(id,v); insert into A values(1,10),(1,20),(2,30); create table B(id,w); insert into B values(1,100),(2,200),(3,300),(4,400);'); print(c.execute('SELECT COUNT(*) FROM A RIGHT OUTER JOIN B ON A.id=B.id WHERE A.id IS NULL').fetchone()[0])"

위 명령은 이 노트를 만든 컴퓨터의 파이썬 3.12(sqlite 3.49.1)에서 실행해 2 가 나온 것이다. RIGHT JOIN 은 sqlite 3.39 부터 지원한다. 이 장의 기출 13개 · 예상 2개 · 훈련 5개의 정답도 같은 방법으로 전부 돌려 확인했다(쿼리 작성 문항은 그 문장을 표본 표에 실행해 문법을 확인).

07백지 재현

왜 이걸 하나 — 아래를 안 보고 적을 수 있어야 시험장에서 표를 돌릴 수 있다.