1. IN
서브쿼리의 결과를 모두 가져온 후, 메인 쿼리의 WHERE 절에서 결과 집합과 비교하는 방식이다.
서브쿼리의 결과에 NULL 이 포함되는 경우에는 결과가 의도와 다르게 동작할 수 있으므로 주의해야 한다.
아래는 IN 을 사용하여 작성한 쿼리의 예시이다.
SELECT *
FROM a
WHERE a.key IN (
SELECT b.key
FROM b
);
b테이블에 먼저 접근한다.b.key를IN리스트에 나열한 후a.key에 공급한다.- 즉, 이 쿼리에서
b테이블은 공급자 역할을 수행한다.
2. EXISTS
서브쿼리에서 조건을 만족하는 행이 존재하는지 여부를 확인하는 방식이다.
즉, 서브쿼리에서 결과 집합을 반환하는 것이 아니라, 서브쿼리에서 조건을 만족하는 행을 찾는 즉시 반환하는 것이다. 따라서 대량의 데이터 처리에서 더 효율적일 수 있다.
EXISTS 의 특징은 다음과 같다.
- 대량의 데이터 처리에서 더 효율적일 수 있다.
NULL값 문제를 피할 수 있다는 장점이 있다.EXISTS뒤에 오는 SELECT 절에서*이나1대신 무엇을 넣어도 상관없다. 어차피 조건을 만족하는 행이 존재하는지 여부만 확인하기 때문이다.- 서브쿼리에서 조건을 만족하는 행이 최소 하나가 있다면 바로
TRUE로 판단한다.
아래는 EXISTS 를 사용하여 작성한 쿼리의 예시이다.
SELECT *
FROM a
WHERE EXISTS (
SELECT 1
FROM b
WHERE b.key = a.key
);
a테이블에 먼저 접근한다.a테이블의 각 행에 대하여EXISTS가TRUE인지 아닌지를 체크한다. 서브쿼리에서 조건을 만족하는 행이 존재한다면EXISTS는TRUE가 되고, 그렇지 않다면EXISTS는FALSE가 된다.- 즉, 이 쿼리에서
b테이블은 확인자 역할을 수행한다.
3. IN과 EXISTS 비교
IN 과 EXISTS 의 공통점은 다음과 같다.
IN과EXISTS는 둘 다 WHERE 절에서 사용되며, 조건에 따라 데이터를 걸러내어 결과를 조회할 때 사용된다.
IN 과 EXISTS 의 차이점은 다음과 같다.
IN에서는 서브쿼리 → 메인 쿼리의 방향으로 진행된다. 반면EXISTS에서는 메인 쿼리 → 서브쿼리의 방향으로 진행된다.IN은 값이 특정 목록에 존재하는지 확인하기 위한 방식이다. 반면EXISTS는 서브쿼리 조건을 만족하는 행이 존재하는지 확인하기 위한 방식이다.IN은 작은 데이터 집합에 적합하다. 반면EXISTS는 큰 데이터 집합에 적합하다.IN방식에서는 서브쿼리에NULL이 있는 경우 결과가 의도와 다를 수 있다. 반면EXISTS방식은NULL에 영향을 받지 않는다.IN방식에서는 서브쿼리 결과를 가져와서 모두 비교한다. 반면EXISTS방식에서는 서브쿼리에서 조건을 만족하는 즉시 반환하므로 더 빠를 가능성이 있다.
따라서 서브쿼리 결과가 크거나 NULL 문제를 방지해야 하는 경우 EXISTS 방식이 적합하다.
4. IN에서 서브쿼리 결과에 NULL이 포함되는 경우, 왜 문제가 되는가?
간단히 말하면, 서브쿼리 결과에 NULL 이 포함되는 경우, IN 조건은 NULL 로 평가될 수 있으며, 이로 인해 메인 쿼리의 결과에도 영향을 미칠 수 있다.
이는 WHERE ... IN (...) 이 내부적으로 조건을 OR 연산으로 풀어서 평가하기 때문이다.
- 예를 들어,
car_id IN (1, NULL)은 내부적으로car_id = 1 OR car_id = NULL로 평가된다. car_id = 1→TRUE또는FALSEcar_id = NULL→NULL- 따라서 위의
IN조건은 다음과 같이 평가된다.TRUE OR NULL인 경우 →TRUEFALSE OR NULL인 경우 →NULL
- 즉, 특정 상황에서는
NULL이라는 결과로 인해 이후 과정에서 의도와 다른 결과가 나올 수 있다.
SQL의 WHERE 절은 조건이 TRUE 로 평가된 행만 결과에 포함하며, 조건이 FALSE 나 NULL 로 평가된 행은 결과에서 제외한다.
따라서 IN 조건의 결과가 NULL 로 나오는 경우로 인해 WHERE 절에서 의도와 다르게 일부 행이 제외될 수 있고, 경우에 따라 메인 쿼리의 결과에서 0개의 행이 반환될 수도 있다.
5. NOT IN과 NOT EXISTS의 차이를 보여주는 예제
(1) 데이터 준비
CREATE TABLE car (car_id INT, car_name VARCHAR(50));
INSERT INTO car VALUES
(1, '세단'),
(2, 'SUV'),
(3, '트럭');
CREATE TABLE rental_history (history_id INT, car_id INT, start_date DATE, end_date DATE);
INSERT INTO rental_hisory VALUES
(1, 1, '2024-11-24', '2024-11-27'),
(2, NULL, '2024-12-07', '2024-12-16');
(2) NOT IN
NOT IN 을 사용한 쿼리이다.
SELECT *
FROM car
WHERE car_id NOT IN (
SELECT car_id
FROM rental_history
);
아래는 서브쿼리의 결과이다.
car_id
------
1
NULL
메인 쿼리의 WHERE 절 평가 과정은 다음과 같다.
NOT IN은 각 행에 대해 서브쿼리 결과와 비교한다.car_id = 1행 :1 NOT IN (1, NULL)→NOT (TRUE OR NULL)→NOT TRUE→FALSE(1이 서브쿼리에 존재) → 메인 쿼리 결과에서 제외car_id = 2행 :2 NOT IN (1, NULL)→NOT (FALSE OR NULL)→NOT NULL→NULL→ 메인 쿼리 결과에서 제외car_id = 3행 :3 NOT IN (1, NULL)→NOT (FALSE OR NULL)→NOT NULL→NULL→ 메인 쿼리 결과에서 제외
아래는 메인 쿼리의 결과이다. (결과 없음)
car_id | car_name
------ | --------
(3) NOT EXISTS
NOT EXISTS 를 사용한 쿼리이다.
SELECT *
FROM car c
WHERE NOT EXISTS (
SELECT 1
FROM rental_history h
WHERE h.car_id = c.car_id
);
NOT EXISTS 는 메인 테이블의 각 행에 대해 서브쿼리 조건을 각각 평가한다.
car_id = 1행 : 서브쿼리 조건TRUE(조건 만족하는 행이 존재함) → 메인 쿼리 결과에서 제외car_id = 2행 : 서브쿼리 조건FALSE(조건 만족하는 행이 존재하지 않음) → 메인 쿼리 결과에 포함car_id = 3행 : 서브쿼리 조건FALSE(조건 만족하는 행이 존재하지 않음) → 메인 쿼리 결과에 포함
아래는 메인 쿼리 결과이다. ( NOT IN 과 달리 결과가 나온다.)
car_id | car_name
------ | --------
2 | SUV
3 | 트럭
6. 참고 자료
- https://velog.io/@wogud9675/MySQL-NOT-IN-과-NOT-EXISTS의-차이점
- https://inpa.tistory.com/entry/MYSQL-📚-서브쿼리-연산자-EXISTS-총정리-성능-비교
7. MySQL 실습 코드
-- # 1. 실습 전 스키마 및 테이블 준비
CREATE SCHEMA study_in_exists DEFAULT CHARACTER SET utf8;
USE study_in_exists;
CREATE TABLE car (car_id INT, car_name VARCHAR(50));
INSERT INTO car VALUES
(1, '세단'),
(2, 'SUV'),
(3, '트럭');
SELECT * FROM car;
CREATE TABLE rental_history (history_id INT, car_id INT, start_date DATE, end_date DATE);
INSERT INTO rental_history VALUES
(1, 1, '2024-11-24', '2024-11-27'),
(2, NULL, '2024-12-07', '2024-12-16');
SELECT * FROM rental_history;
-- # 2. NOT IN 사용
SELECT *
FROM car
WHERE car_id NOT IN (
SELECT car_id
FROM rental_history
);
-- # 3. NOT EXISTS 사용
SELECT *
FROM car c
WHERE NOT EXISTS (
SELECT 1
FROM rental_history h
WHERE h.car_id = c.car_id
);
-- # 4. 실습 후 테이블 및 스키마 지우기
drop table rental_history;
drop table car;
drop schema study_in_exists;
show databases;
(2024.12.31 화 작성)
(2025.02.12 수 수정 및 업로드)
'MySQL' 카테고리의 다른 글
| [MySQL] WITH 절과 공통 테이블 표현식 (0) | 2025.02.12 |
|---|