SQL · 관계대수 — 결과 행을 손으로 세는 시험
SQL은 2024~2025년에 회차당 0~1문항까지 쪼그라들었다가 2026-2회에 사상 최다 4문항(20점) 으로 반등했다. "요즘 안 나온다"고 버리면 정확히 그 회차에 당한다. 출제는 두 형태뿐 — 실행 결과(행/값) 쓰기와 구문 빈칸 채우기. 본문 쿼리·결과 표는 전부 기출 복원이며 sqlite로 실행 검증했고, 헷갈리는 문항은 결과 도출 과정을 표로 펼쳐 놨다.
1. COUNT 3형제 — NULL이 답을 가른다
집계 함수의 대상이 무엇이냐에 따라 NULL 처리가 갈린다. 이 하나가 SQL 실행 결과형의 최다 함정.
| 형태 | 세는 것 | NULL |
|---|---|---|
COUNT(*) | 행 전체 | 포함 |
COUNT(col) | 그 열의 값 | 제외 ← 최다 함정 |
COUNT(DISTINCT col) | 그 열의 서로 다른 값 | 제외 + 중복 제거 |
워크드 예제 (2025-3 → 4): 테이블 A와 쿼리:
SELECT count(col2) FROM A
WHERE col1 IN (2, 3) OR col2 IN (3, 5);
| col1 | col2 | WHERE 통과? | col2가 NULL? |
|---|---|---|---|
| 2 | NULL | ✓ (col1=2) | NULL → count 제외 |
| 3 | 6 | ✓ (col1=3) | 카운트 |
| 2 | 3 | ✓ | 카운트 |
| NULL | 3 | ✓ (col2=3) | 카운트 |
| 4 | 5 | ✓ (col2=5) | 카운트 |
다섯 행 모두 WHERE는 통과하지만 count(col2)가 NULL 1건을 빼서 → 4. (COUNT(*)였다면 5.)
DISTINCT 예제 (2026-1 → 200 / 3 / 1): STUDENT에 컴퓨터과 50·인터넷과 100·사무자동화과 50(200행).
SELECT DEPT FROM STUDENT; -- 중복 유지 → 200
SELECT DISTINCT DEPT FROM STUDENT; -- 학과 종류 → 3
SELECT COUNT(DISTINCT DEPT) FROM STUDENT WHERE DEPT='컴퓨터과'; -- 한 학과로 좁혀 → 1
읽는 순서 — ① WHERE로 살아남는 행을 표시 → ② COUNT 안이
*인지 속성인지 확인해 NULL을 빼고 → ③ DISTINCT면 중복을 접는다. SUM·AVG도 NULL을 무시한다는 점 함께 기억.
2. WHERE 논리 — AND가 OR보다 먼저 묶인다
워크드 예제 (2024-1 → 1): TABLE = (100,1000),(200,3000),(300,1500).
SELECT COUNT(*) FROM TABLE
WHERE EMPNO > 100 AND SAL >= 3000 OR EMPNO = 200;
-- 우선순위: (EMPNO>100 AND SAL>=3000) OR EMPNO=200
| EMPNO | SAL | (>100 AND ≥3000) | OR =200 | 최종 |
|---|---|---|---|---|
| 100 | 1000 | 거짓 | 거짓 | ✗ |
| 200 | 3000 | 참 | 참 | ✓ |
| 300 | 1500 | 거짓(SAL 미달) | 거짓 | ✗ |
→ 한 행만 → COUNT 1. OR부터 묶으면 오답.
- 연산자 우선순위: 비교(>, =) > NOT > AND > OR. 괄호가 없으면 AND가 먼저 묶인다.
- LIKE 패턴:
'이%'= 이로 시작,'%이'= 이로 끝,'%이%'= 포함,_는 정확히 한 글자. (2026-2: '이'로 시작 + 내림차순 →LIKE '이%'+ORDER BY … DESC.) - NULL 비교는
=가 아니라IS NULL/IS NOT NULL— WHERE에서NULL = x,NULL > x는 전부 UNKNOWN(탈락). §4 상관 서브쿼리 함정이 이걸 노린다. - BETWEEN a AND b(양끝 포함), IN (…), NOT IN 도 자주 등장.
3. JOIN — 작성법과 분류 용어가 따로 나온다
3.1 JOIN 종류별 결과 (작은 예로 눈에 익히기)
E = (1,10),(1,20),(2,30) / D = (1,100),(2,200),(3,300),(4,400) 로 실행하면:
| JOIN | 결과 행수 | 설명 |
|---|---|---|
E INNER JOIN D | 3 | 매칭되는 id 1(E 2행)·2(E 1행)만 → 2+1 |
E RIGHT OUTER JOIN D | 5 | D 전체 보존 → 매칭 3행 + 미매칭 D(3,4) 2행(E쪽 NULL) |
RIGHT JOIN … WHERE E.id IS NULL | 2 | 미매칭 D 행(3,4)만 |
E CROSS JOIN D | 12 | 곱집합 3×4 |
함정 — INNER 조인에서 같은 키가 여러 행이면 결과가 불어난다(id=1이 E에 2행 → 결과 2행). COUNT는 항상 '조인이 끝난 뒤의 행'을 센다.
3.2 실행 결과형 기출
-- 2025-1 → 이순신 1000 (한 행): 암시적 동등 조인 + 필터
SELECT name, incentive FROM emp, sal
WHERE emp.id = sal.id AND incentive >= 500;
-- 2026-2 → 2: RIGHT OUTER JOIN + IS NULL
SELECT COUNT(*) FROM A RIGHT OUTER JOIN B ON A.id = B.id WHERE A.id IS NULL;
-- B의 id 3,4가 A에 미매칭 → A쪽 NULL 행 2개
-- 2025-3 → 4: CROSS JOIN + LIKE
SELECT COUNT(*) FROM 학생 CROSS JOIN 교수 WHERE 교수.이름 LIKE '%신%';
-- 곱집합 4×3=12 → '신' 포함 교수 1명 → 학생 4 × 1 = 4
OUTER JOIN + COUNT 함정 (2026-1 → 2): "조건 만족 부서는 1개니까 1"이 대표 오답 — 조인 후엔 사원 단위 행이 남으므로 그 부서 사원 수(2)를 센다.
3.3 분류 용어형 (2024-1)
포함 관계 세타 ⊃ 동등 ⊃ 자연:
- 세타 조인 —
=, <, >등 일반 비교 연산자 전부 허용. - 동등 조인 — 비교 연산자로
=만 사용. - 자연 조인 — 동등 조인 결과에서 중복 속성(같은 이름 열)을 하나로 제거.
- (세미 조인·안티 조인·외부 조인도 이름 정도는 알아둔다.)
4. 서브쿼리 — 집합을 먼저, 바깥을 나중에
중첩 서브쿼리 (2024-3 → 1): 안에서 밖으로 읽는다.
... WHERE p.name IN (
SELECT name FROM project WHERE project_id IN (
SELECT project_id FROM employee GROUP BY project_id HAVING count(*) < 2
));
-- ① 직원 2명 미만 프로젝트(B) → ② 그 이름('B') → ③ 그 프로젝트 소속 직원 수(1)
상관 서브쿼리 (2026-2 → 3): 바깥 행마다 내부를 다시 계산. A=(1,10),(2,20),(3,30),(4,40), B=(1,5),(1,15),(2,20),(3,35),(5,50).
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));
| A.x | 자기보다 작은 x의 id | B.y 대상 | AVG | A.x > AVG ? |
|---|---|---|---|---|
| 10 | 없음 (공집합) | 없음 | NULL | UNKNOWN → 탈락 |
| 20 | {1} | 5, 15 | 10 | 20 > 10 ✓ |
| 30 | {1,2} | 5,15,20 | 13.33 | 30 > 13.33 ✓ |
| 40 | {1,2,3} | 5,15,20,35 | 18.75 | 40 > 18.75 ✓ |
→ 통과 3행 → 3.
두 규칙 — ① 상관 서브쿼리는 바깥 행마다 행별로 재평가한다(위 표처럼 한 행씩 적어라). ② AVG(빈 집합) = NULL, NULL과의 비교는 UNKNOWN이라 그 행이 조용히 사라진다(x=10). GROUP BY + HAVING: 그룹을 만든 '뒤' 그룹 단위로 거른다(WHERE는 그룹 전 행 필터).
5. 구문 빈칸 — DML·DDL·DCL·TCL
5.1 DML 골격 (2024-2: VALUES · SELECT · FROM · SET)
INSERT INTO 테이블 (컬럼…) VALUES (값…); -- 리터럴 삽입
INSERT INTO 테이블 (컬럼…) SELECT … FROM 원본; -- 질의 결과 삽입
SELECT * FROM 테이블 WHERE … GROUP BY … HAVING … ORDER BY 컬럼 DESC;
UPDATE 테이블 SET 컬럼 = 값 WHERE …; -- SET이 빈칸 단골
DELETE FROM 테이블 WHERE …;
SQL 논리 실행 순서(작성 순서와 다름): FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. 그래서 SELECT의 별칭을 WHERE에서 못 쓴다는 함정이 나올 수 있다.
5.2 DDL — 정의·제약 (2026-1·2026-2)
-- 외래키 (2026-1 빈칸 5개): 방향이 핵심
CONSTRAINT TEAM_TF FOREIGN KEY (TEAM_ID) REFERENCES TEAM (TEAM_ID2)
-- 제약 이름 자기 테이블의 컬럼 참조 대상 테이블(컬럼)
-- 값 검사 (2026-2 → CHECK)
CHECK (VALUE IN ('spring', 'summer', 'autumn', 'winter'))
| 키워드 | 역할 |
|---|---|
| CREATE / ALTER / DROP / TRUNCATE | 정의(생성·수정·삭제·전체비움) |
| CONSTRAINT 이름 | 제약에 이름 부여 (뒤에 실제 제약) |
| FOREIGN KEY(자기컬럼) REFERENCES 남(컬럼) | 참조 무결성 — 방향 주의 |
| CHECK(조건식) | 조건 만족 값만 허용 |
| UNIQUE / NOT NULL / PRIMARY KEY | 중복 금지 / NULL 금지 / 유일+NOT NULL |
| DEFAULT 값 | 생략 시 기본값 |
- 뷰(VIEW) —
CREATE VIEW v AS SELECT …. 실제 데이터 없는 가상 테이블(논리적 독립성). - 인덱스(INDEX) — 검색 속도용.
CREATE INDEX. 삽입·갱신은 느려지는 트레이드오프. - 트리거(TRIGGER) — 특정 이벤트(INSERT 등) 시 자동 실행되는 프로시저.
5.3 DCL·TCL
- DCL(권한):
GRANT 권한 ON 테이블 TO 사용자 [WITH GRANT OPTION](재부여 권한까지),REVOKE … FROM …. - TCL(트랜잭션):
COMMIT(확정),ROLLBACK(취소),SAVEPOINT(부분 취소 지점).
함정 —
CONSTRAINTS처럼 s를 붙이거나, FOREIGN KEY와 REFERENCES의 컬럼을 뒤집으면 전부 오답. DELETE(행 삭제, 조건 가능, 롤백 가능) vs TRUNCATE(전체 비움, 빠름, 롤백 불가) vs DROP(테이블 자체 삭제) 구분도 단골.
6. 관계대수 — 기호가 곧 문제다
| 기호 | 이름 | 동작 |
|---|---|---|
| σ(셀렉트) | 행 선택 | 조건 만족 행만 (수평, WHERE에 해당) |
| π(프로젝트) | 열 추출 | 지정 속성만 + 중복 제거 (수직) |
| ⋈(조인) | 결합 | §3의 자연 조인이 기본형 |
| ÷(디비전) | 나누기 | 나누는 쪽의 모든 값과 짝지어진 것만 |
| ∪ ∩ −(합·교·차) | 집합 연산 | 합집합·교집합·차집합 |
| ×(카티션 곱) | 곱집합 | CROSS JOIN |
| ρ(리네임) | 이름 변경 | 별칭 부여 |
π 예제 (2025-2): π TTL(employee) → TTL 열만 추출 + 중복 제거 → 부장·대리·과장·차장(중복
없어 4행 그대로).
÷ 예제 (2025-3): R(A,B) = (a1,b1),(a2,b2),(a1,b3) / S(B) = (b1),(b3). R ÷ S는?
| A값 | 가진 B | S의 {b1, b3} 모두 커버? |
|---|---|---|
| a1 | b1, b3 | ✓ 둘 다 있음 → 생존 |
| a2 | b2 | ✗ b1·b3 없음 → 탈락 |
→ 결과 A = a1.
÷ 판정법 — 나누는 릴레이션(S)의 값 목록을 적고, 왼쪽 각 값이 그 목록을 전부 커버하는지 본다. 하나라도 빠지면 탈락. π는 중복 제거 내장 — SQL SELECT(기본 ALL, 중복 유지)와 다른 점이 역으로 출제된다.
7. DB 용어·이론 세트 (실행 결과 못지않게 나온다)
- 관계 구조 용어 (2025-1·2025-3): 행 = 튜플, 열 = 속성(애트리뷰트), 속성 수 = 차수(degree), 튜플 수 = 카디널리티, 특정 시점의 튜플 집합(외연) = 릴레이션 인스턴스, 구조 정의(내포) = 스키마, 값의 범위 = 도메인.
- 키 (2024-3): 유일성+최소성 = 후보키, 유일성만(불필요한 속성 포함 가능) = 슈퍼키, 후보키 중 기본키로 안 뽑힌 것 = 대체키, 남의 기본키 참조 = 외래키.
- 무결성 (2025-1): 기본키 유일+NOT NULL = 개체, 외래키가 존재하는 값만 참조 = 참조, 값의 유형·범위 = 도메인 무결성.
- 정규화 (2024-1·2026-2): 원자값 = 1NF, 부분 함수 종속 제거 = 2NF, 이행 함수 종속 제거 = 3NF, 모든 결정자가 후보키 = BCNF, 다치 종속 제거 = 4NF, 조인 종속 = 5NF. "후보키 아닌 결정자(강사→강좌)가 남아 있다"는 지문 → 현재 3NF, BCNF 위반의 고정 시나리오. 성능 위해 의도적으로 되돌리면 반정규화.
- 이상 현상(Anomaly) — 정규화를 안 하면 생기는 삽입·삭제·갱신 이상. 정규화의 이유.
- DB 설계 5단계 (2026-1): 요구사항 분석 → 개념적(E-R) → 논리적(스키마) → 물리적(저장 구조·인덱스) → 구현.
- 파일(레코드) 접근 방법 (2025-2): 물리 설계에서 레코드 접근 세 방식 — 순차 접근(저장 순서대로 훑음), 색인(인덱스) 접근(키 값과 포인터를 쌍으로 저장, 키로 주소를 찾아 직접 접근), 해싱 접근(키를 해시 함수에 넣어 주소를 계산). 결정 키워드 — 주소를 저장해 두면 색인, 주소를 계산하면 해싱.
꼬리질문 대비 — "슈퍼키와 후보키의 차이?"(둘 다 유일성○, 후보키만 최소성○). "정규화의 목적?"(이상 현상 제거·중복 최소화). "반정규화를 언제?"(조인이 잦아 성능이 급할 때 트레이드오프로).
8. 고난도 함정 (자체 출제 hard 세트 대비)
기출엔 아직 안 나왔지만 나올 수 있는 SQL 함정. 각 항목은 hard 세트의 실제 문항이다.
8.1 NOT IN + NULL = 전멸
-- t2 = {3, 4, 5, NULL}
SELECT COUNT(*) FROM t1 WHERE v NOT IN (SELECT v FROM t2); -- 결과 0!
서브쿼리 결과에 NULL이 하나라도 있으면
NOT IN전체가 UNKNOWN이 되어 한 행도 통과하지 못한다.v NOT IN (3,4,5,NULL)은 어떤 v든 "NULL과 다른가?"가 UNKNOWN이라 참이 될 수 없다. 실무·시험 모두 최다 NULL 함정. (IN은 이 문제가 없다.) 대비:NOT IN (SELECT v FROM t2 WHERE v IS NOT NULL).
8.2 집합 연산 — UNION / INTERSECT / EXCEPT
SELECT v FROM t1 UNION SELECT v FROM t2 -- 합집합, 중복 제거 (NULL도 한 값)
SELECT v FROM t1 UNION ALL SELECT v FROM t2 -- 합치고 중복 유지
SELECT v FROM t1 INTERSECT SELECT v FROM t2 -- 양쪽 공통
SELECT v FROM t1 EXCEPT SELECT v FROM t2 -- t1에만 (MINUS)
UNION은 중복을 제거하고UNION ALL은 유지한다 — 개수 문제에서 갈린다.INTERSECT는 교집합,EXCEPT(오라클은MINUS)는 차집합. 모두 열 개수·타입이 맞아야 한다.
8.3 AVG는 NULL을 무시
-- t2 = {3, 4, 5, NULL}
SELECT AVG(v) FROM t2; -- (3+4+5)/3 = 4.0 (NULL을 분모에도 안 넣음)
집계 함수(SUM·AVG·MAX·MIN·COUNT(열))는 NULL을 계산에서 제외한다. NULL을 0으로 세거나 분모(개수)에 넣으면 안 된다(4.0이지 3.0이 아니다). §1의 COUNT와 같은 원리.
8.4 지역별 최대 — 상관 서브쿼리 + 동점
SELECT COUNT(*) FROM sales s
WHERE amt = (SELECT MAX(amt) FROM sales x WHERE x.region = s.region);
각 행이 자기 그룹(지역)의 최댓값과 같은지 비교한다. 한 지역에서 최댓값이 여러 행에 동점이면 그 행들이 모두 걸린다(B 지역 150이 두 행이면 둘 다 카운트). "지역 수"가 아니라 "최댓값과 같은 행 수"임에 주의.
8.5 조건부 집계 (CASE)
SELECT SUM(CASE WHEN amt >= 200 THEN 1 ELSE 0 END) FROM sales; -- 조건 만족 행 수
SUM(CASE WHEN 조건 THEN 1 ELSE 0 END)은 조건을 만족하는 행 수를 센다. 여러 조건별 개수를 한 쿼리로 뽑을 때 쓰는 관용구다(피벗 흉내).
9. 시험장 체크리스트
- COUNT 안을 본다 —
*(NULL 포함) / 속성(NULL 제외) / DISTINCT(중복 제거). SUM·AVG도 NULL 무시. - WHERE는 AND 먼저. NULL 비교는
IS NULL만 참이 될 수 있다. - JOIN 후 행 수를 표로 그린다 — 같은 키 다중 행의 '불어남'과 OUTER의 NULL 행.
- 상관 서브쿼리는 바깥 행마다 재계산 표로. AVG(공집합) = NULL = 탈락.
FOREIGN KEY(자기) REFERENCES 남(컬럼)— 방향. DELETE/TRUNCATE/DROP 구분.- 관계대수 π는 중복 제거, ÷는 "S를 전부 가진 놈만".
출처
- 2024-1 ~ 2026-2 실기 복원 기출 (수록 8회차) — 기사퍼스트·두목넷·수제비(공개 미러)·뉴비티·chobopark 교차 검증
- 본문 쿼리·결과 표 전량 sqlite3 실행 검증 완료 (상관 서브쿼리 행별 AVG, INNER/OUTER/CROSS 행수, R÷S 포함)