SQL 새 강의에 온 걸 환영해! 오늘은 가장 눈에 잘 안 띄지만 진짜 강력한 연산자 — EXISTS 와 NOT EXISTS를 배워볼 거야. 마치 흔적도 안 남기고 "응, 객체 있어" 아니면 "아니, 여긴 비었어"라고 바로 알려주는 첩보원 같다고 생각해봐. 이 연산자들은 데이터를 직접 반환하진 않지만, 논리적으로 딱 맞는 체크를 쿼리에서 할 수 있게 해줘.
기본부터 시작하자. EXISTS는 레코드가 존재하는지 서브쿼리 결과로 확인하는 연산자야. 만약 서브쿼리가 한 줄이라도 반환하면 EXISTS는 TRUE를, 아니면 FALSE를 반환해.
SELECT 1
WHERE EXISTS (
SELECT *
FROM students
WHERE grade > 3.5
);
보다시피, 서브쿼리의 데이터 자체엔 관심 없고, 그런 행이 존재하는지만 보는 거야. 조건에 맞는 레코드가 하나라도 있으면 쿼리는 1을 반환해.
EXISTS 문법
EXISTS 문법은 진짜 간단해:
SELECT 컬럼들
FROM 테이블
WHERE EXISTS (
SELECT 1
FROM 다른_테이블
WHERE 조건
);
설명:
EXISTS안의 서브쿼리는 아무 쿼리나 올 수 있어.- 서브쿼리 결과가
TRUE또는FALSE를 결정해.
예시: 4점 넘는 학생이 있나?
students 테이블을 상상해보자:
| id | name | grade |
|---|---|---|
| 1 | Otto | 3.2 |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | 2.9 |
이제 4점 넘는 학생이 있는지 확인하고 싶어. 아래 쿼리를 써보자:
SELECT '점수 높은 학생이 있어!'
WHERE EXISTS (
SELECT 1
FROM students
WHERE grade > 4
);
결과:
점수 높은 학생이 있어!
왜 EXISTS가 IN보다 빠를까?
EXISTS의 가장 큰 장점은 서브쿼리에서 첫 번째로 조건에 맞는 행을 찾으면 바로 멈춘다는 것이야. 즉, 데이터가 있는지만 확인할 때 EXISTS는 엄청 효율적일 수 있어.
예를 들어, students 테이블에 수백만 개의 레코드가 있어도, 우리는 단 하나의 조건에 맞는 행(grade > 4)만 찾으면 돼. SQL이 첫 번째로 맞는 행을 찾는 순간 쿼리는 끝나.
NOT EXISTS 사용하기
이제 NOT EXISTS에 대해 얘기해보자. 이 연산자는 EXISTS의 반대야. 서브쿼리가 아무 행도 반환하지 않을 때 TRUE를 반환해.
예시: 점수가 없는 학생 찾기 (NULL)
우리 테이블에 아직 점수가 없는 학생이 있다고 해보자:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | NULL |
점수가 없는 학생만 뽑고 싶으면 NOT EXISTS를 써봐:
SELECT *
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM students
WHERE grade IS NOT NULL
AND id = s.id
);
결과:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 4 | Lina | NULL |
EXISTS vs IN 비교
가끔 EXISTS랑 IN이 똑같아 보일 때가 있어. 얼핏 보면 맞지만, NULL이 끼어들면 얘기가 달라져. 이럴 때 IN은 예측 못한 결과를 주기도 하고, EXISTS가 구세주가 돼.
예제로 보자.
courses 테이블 (수강 가능한 과목):
| course_id | name |
|---|---|
| 1 | 수학 |
| 2 | 역사 |
그리고 학생들:
| student_id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
enrollments 테이블 (누가 어떤 과목에 등록했는지):
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
누군가 등록한 과목 이름을 뽑고 싶어. 쉬워 보이지?
IN 사용:
SELECT name
FROM courses
WHERE course_id IN (
SELECT course_id
FROM enrollments
);
겉보기엔 잘 될 것 같지만, enrollments에 NULL이 들어가 있으면(Maria Chi처럼) IN이... 아무것도 안 줄 수도 있어! 왜냐면 NULL이 서브쿼리를 "불확실"하게 만들어서, SQL이 "혹시 NULL이 우리가 찾는 그 course_id일 수도 있잖아?"라고 헷갈려 하거든.
EXISTS 사용:
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
);
EXISTS는 "course_id가 일치하는 행이 하나라도 있냐?"만 체크해. NULL이 있어도 신경 안 써, 그냥 일치하는 값만 찾으니까.
결론: 서브쿼리에 NULL이 있을 수 있으면 EXISTS를 쓰는 게 안전해. 그래야 이상한 결과 안 나와.
실제 문제 예시
students 테이블:
| id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
enrollments 테이블:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
예시 1. 과목에 등록한 학생들
이미 어딘가 등록한 학생을 찾아보자 (Maria Chi처럼 이상하게 등록했어도):
SELECT name
FROM students s
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
결과:
Alex Lin
Anna Song
Maria Chi
학생이 enrollments에 한 번이라도 나오면, course_id가 뭔지 상관없이 결과에 들어가.
예시 2. 아직 과목에 등록 안 한 학생들
이제 시스템에만 있고 아직 아무데도 등록 안 한 학생을 찾아보자:
SELECT name
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
결과:
Dan Seth
Shadow Moon
이 둘은 아직 마음에 드는 과목을 못 찾았나 봐. 아니면 그냥 등록하는 걸 까먹었을 수도 있지 :)
예시 3. 5명 넘게 등록한 과목 찾기
courses 테이블:
| course_id | name |
|---|---|
| 1 | 수학 |
| 2 | 역사 |
| 3 | 생물학 |
| 4 | 철학 |
enrollments 테이블:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
| 10 | NULL |
5명 넘게 등록한 과목을 찾고 싶어. 여기서 EXISTS는 "이 과목에 학생이 5명 넘게 등록한 그룹이 있냐?"고 묻는 거야.
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
GROUP BY e.course_id
HAVING COUNT(*) > 5
);
결과:
수학
"수학"(course_id = 1)만 6명이 등록했어. 다른 과목은 아직 인기 별로야.
EXISTS 와 NOT EXISTS 쓸 때 자주 하는 실수
- 서브쿼리 문법을 잘못 이해함. 항상 서브쿼리가 외부 테이블을 제대로 참조하는지 확인해.
NULL체크를 빼먹음.EXISTS를 써도 가끔NULL처리를 명확히 해줘야 할 때가 있어.- 서브쿼리 필드에 인덱스가 없음. 이러면 쿼리가 엄청 느려질 수 있어.
오늘은 여기까지! 이제 EXISTS 와 NOT EXISTS로 데이터 존재 여부를 체크하는 법, 그리고 이 연산자들과 IN의 차이까지 알게 됐지. 다음 강의에선 SELECT에서 서브쿼리로 집계 데이터 다루는 법을 더 파볼 거야.
GO TO FULL VERSION