안녕하세요. 조재구입니다. 이번 시간에는 이번학기 수업을 듣고 있는 데이터베이스 개론에서 SQL 관련 과제를 하던중 EXIST
와 IN
의 작동방식이 살짝 난해하다는 생각이 들어 정리를 해보려고 합니다.
가장 먼저 FILM_CUSTOMER TABLE입니다.
+-------------+---------+
| customer_id | film_id |
+-------------+---------+
| 0 | 0 |
| 1 | 1 |
| NULL | 2 |
| 5 | 3 |
| 7 | 4 |
| 8 | 5 |
+-------------+---------+
다음은 RENTAL_CUSTOMER TABLE입니다.
+-------------+-----------+
| customer_id | rental_id |
+-------------+-----------+
| 0 | 0 |
| 1 | 1 |
| NULL | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
+-------------+-----------+
IN
IN
에서 가장 두드러지는 특징은 서브쿼리에서 미리 데이터를 가져온 다음 그 데이터를 바탕으로 조건에 맞는 데이터를 출력합니다. 다음의 SQL 쿼리를 보겠습니다.
SELECT *
FROM FILM_CUSTOMER f
WHERE f.cutomer_id IN (SELECT r.customer_id FROM RENTAL_CUSTOMER)
위의 SQL이 작동하는 과정은 다음과 같습니다.
1. SELECT r.customer_id FROM RENTAL_CUSTOMER
이 먼저 실행되어 관련 데이터가 추출됩니다.
2. 추출된 데이터 [0, 1, 2, 3, 4, 5, NULL]
를 바탕으로 상위 쿼리를 실행합니다.
3. 결과는 다음과 같습니다.
+-------------+---------+
| customer_id | film_id |
+-------------+---------+
| 0 | 0 |
| 1 | 1 |
| 5 | 3 |
| 7 | 4 |
| 8 | 5 |
+-------------+---------+
NOT IN
기본적인 연산의 방향은 IN
과 매우 유사합니다. 하지만 NOT
을 판단하는 과정이 다소 직관과 멀게 느껴지는 경우가 있습니다. 다음을 보겠습니다.
SELECT *
FROM FILM_CUSTOMER f
WHERE f.cutomer_id NOT IN (SELECT r.customer_id FROM RENTAL_CUSTOMER)
먼저 실행 결과부터 확인하겠습니다.
+-------------+---------+
| customer_id | film_id |
+-------------+---------+
| 0 | 0 |
| 1 | 1 |
+-------------+---------+
위의 쿼리를 조금 변형시키면 다음과 같습니다.
SELECT *
FROM FILM_CUSTOMER f
WHERE f.cutomer_id NOT IN (1, 2, 3, 4, 5, NULL)
이제부터 차근차근 NOT IN
이 작동하는 방식을 보겠습니다. NOT IN
은 직관적으로 "포함하지 않는다"의 의미를 가지고 있습니다. 이를 쿼리는 다음과 같은 방식으로 해석합니다.
SELECT *
FROM FILM_CUSTOMER f
WHERE f.number != 1 AND
f.number != 2 AND
f.number != 3 AND
f.number != 4 AND
f.number != 5 AND
f.number != NULL;
여기서 우리가 눈여겨 봐야할 계산은 바로 NULL
과의 연산입니다. 익히 알다싶이 NULL
과의 계산은 UNKNOW
을 반환하며 이 값은 TRUE/FALSE
도 아닌 값입니다. 따라서 우리가 직관적을 생각하는 5이상의 수인 6, 7, 8 모두 결과값에 포함되지 않은 것입니다.
이렇듯 NULL
의 경우는 쿼리계산에서 우리의 직관과는 상대적으로 다른 결과값을 유발할 수 있기 때문에 되도록 제약을 걸어서 자용하는 것이 좋으며 IS NULL
을 사용하여 NULL
의 문제를 어느정도는 해결할 수 있습니다.
EXISTS
EXISTS
는 IN
과는 다소 반대로 동작합니다. 먼저 쿼리부터 살펴보겠습니다.
SELECT *
FROM FILM_CUSTOMER f
WHERE EXISTS (SELECT r.customer_id FROM RENTAL_CUSTOMER r)
위의 쿼리가 작동하는 방식은 다음과 같습니다.
1. 먼저 상위 쿼리인 FILM_CUSTOMER
에 접근을 하여 관련된 데이터를 가져옵니다.
2. 이후에 가져온 데이터를 하나씩 가져오고 서브쿼리에 대한 계산 결과가 존재하는지를 확인합니다.
위의 쿼리를 실행한 결과값은 다음과 같습니다.
+-------------+-----------+
| customer_id | rental_id |
+-------------+-----------+
| 0 | 0 |
| 1 | 1 |
| NULL | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
+-------------+-----------+
즉, 상위 쿼리에서 계산한 값들을 하나씩 가져와 서브쿼리의 계산결과가 존재하는지 확인하는 과정에서 실제로 계산 과정에서 서브쿼리와의 연관성은 존재하지 않음과 동시에 서브쿼리는 무조건 결과값을 반환합니다. 이로인해서 결과값이 FILM_CUSTOMER
전체가 되는 것입니다.
이를 직관적으로 우리가 유도하는 데이터를 얻기 위해서는 상위쿼리와 서브쿼리의 연관성을 넣어주면 됩니다.
SELECT *
FROM FILM_CUSTOMER f
WHERE EXISTS (
SELECT r.customer_id F
ROM RENTAL_CUSTOMER r
WHERE f.customer_id = r.customer_id
)
결과값은 다음과 같습니다.
+-------------+-----------+
| customer_id | rental_id |
+-------------+-----------+
| 0 | 0 |
| 1 | 1 |
| 3 | 3 |
+-------------+-----------+
NOT EXISTS
위의 모든 내용을 따라오셨다면, NOT EXISTS
를 이해하는데 큰 어려움은 없을 겁니다. 기본적인 과정은 EXISTS
와 동일합니다.
SELECT *
FROM FILM_CUSTOMER f
WHERE NOT EXISTS (
SELECT r.customer_id F
ROM RENTAL_CUSTOMER r
WHERE f.customer_id = r.customer_id
)
위의 결과는 다음과 같습니다.
+-------------+---------+
| customer_id | film_id |
+-------------+---------+
| NULL | 2 |
| 7 | 4 |
| 8 | 5 |
+-------------+---------+
NULL
과의 계산은 항상 UNKNOWN
을 반환합니다. 이로인해서 (NULL, 2)
의 값이 서브쿼리와 아무런 결과값을 만들어내지 않기 때문에 NOT EXISTS
에 해당이 되어 위와 같은 결과값을 출력하는 것입니다.