IN, NOT IN, EXISTS, NOT EXISTS의 차이와 동작 방식

IN 연산자

IN 연산자는 특정 값이 지정한 리스트(또는 서브쿼리 결과) 내에 존재하는지 확인하는 데 사용된다.

SELECT * FROM orders o WHERE o.customer_id IN (1, 2);

동작 방식

  1. IN 구문은 내부적으로 OR 연산자로 변환되어 실행된다.

    SELECT * FROM orders o WHERE (o.customer_id = 1 OR o.customer_id = 2);
    
  2. 따라서 리스트에 포함된 값들 중 하나라도 일치하면 해당 행이 조회된다.

서브쿼리를 사용하는 경우

SELECT * FROM orders o
WHERE o.customer_id IN (SELECT c.customer_id FROM customers c);
  1. 서브쿼리를 먼저 실행하여 customer_id 목록을 가져온다.
  2. orders 테이블에서 한 행씩 가져와 customer_id 값이 서브쿼리 결과 중 하나라도 일치하면 출력한다.
  3. 이 과정은 orders 테이블의 모든 행에 대해 반복된다.

NULL 값이 포함된 경우

IN 연산자를 사용할 때 서브쿼리 결과에 NULL 값이 포함되어 있으면 주의해야 한다. 예를 들어, customers 테이블의 customer_id 값이 (1, 2, 3, 4, NULL)이라면 다음과 같은 동작이 발생한다.

  • orders 테이블의 customer_id 값이 NULL일 경우, 서브쿼리 결과에 NULL이 포함되어 있어도 IN 조건에서 비교 연산이 UNKNOWN(즉, FALSE)이 되어 해당 행은 출력되지 않는다.
  • 이는 MySQL에서 NULL 값이 포함된 비교 연산은 항상 FALSE를 반환하기 때문이다.

NOT IN 연산자

NOT IN 연산자는 특정 값이 지정한 리스트(또는 서브쿼리 결과)에 존재하지 않는 경우를 필터링하는 데 사용된다.

not in 구문의 경우 and 연산자로 변경된다는 차이점이 있다.

SELECT * FROM orders o
WHERE o.customer_id NOT IN (SELECT c.customer_id FROM customers c);

NULL 값이 포함된 경우 문제 발생

  1. 서브쿼리 결과가 (1, 2, 3, 4, NULL)이면, NOT IN 연산자가 customer_id = 5인 데이터를 비교할 때 문제 발생
  2. customer_id = 51, 2, 3, 4 비교 결과는 TRUE지만, 5 != NULL 비교 결과는 UNKNOWN(즉, FALSE)
  3. AND 연산자로 묶이므로 최종 결과는 FALSE, 따라서 customer_id = 5 데이터도 출력되지 않음

NULL 문제 해결 방법

SELECT * FROM orders o
WHERE o.customer_id NOT IN (SELECT c.customer_id FROM customers c WHERE c.customer_id IS NOT NULL);
  • IS NOT NULL 조건을 추가하면 NULL 값을 제외하고 비교 가능

NULL 비교 연산의 주의점

  1. = NULL 또는 <> NULL은 MySQL에서 FALSE를 반환한다. 따라서 IS NULL 또는 IS NOT NULL을 반드시 사용해야 한다.
  2. IN 또는 NOT IN 연산을 사용할 때 NULL 값이 포함되어 있으면 결과가 예상과 다를 수 있기에 주의!

EXISTS 연산자

EXISTS는 서브쿼리에서 한 개 이상의 결과가 존재하는 경우 TRUE를 반환하며, 존재하지 않으면 FALSE를 반환한다.

SELECT * FROM orders o
WHERE EXISTS (
    SELECT c.customer_id FROM customers c
    WHERE c.customer_id = o.customer_id
);

동작 방식

  1. orders 테이블의 각 행에 대해 서브쿼리를 실행한다.
  2. 서브쿼리가 한 개 이상의 행을 반환하면 TRUE를 반환하여 해당 행을 출력한다.
  3. EXISTS는 서브쿼리에서 한 개의 행이라도 찾으면 즉시 TRUE를 반환하므로, 불필요한 검색을 줄일 수 있다.

EXISTS vs. IN 차이점

  • EXISTS는 서브쿼리에서 해당하는 값을 찾으면 즉시 종료하므로, 서브쿼리의 결과 크기가 클수록 성능이 유리하다.
  • 반면, IN 연산자는 서브쿼리의 모든 결과를 가져와서 비교해야 하므로 성능이 떨어질 수 있다.

NULL 값이 포함된 경우

EXISTS 연산자는 NULL 값이 포함된 경우에도 정상적으로 동작한다.

SELECT * FROM orders o
WHERE EXISTS (
    SELECT c.customer_id FROM customers c
    WHERE c.customer_id = o.customer_id OR c.customer_id IS NULL
);
  • EXISTS 연산자는 단순히 서브쿼리 결과가 있는지 확인하는 역할을 하기 때문에 NULL 값이 포함되어 있어도 문제가 되지 않는다.
  • 위 쿼리에서는 customers.customer_idNULL인 경우에도 결과가 존재하면 TRUE를 반환하여 해당 행이 출력 된다.

그러나 NULL을 제외하고 비교하고 싶다면 다음과 같이 IS NOT NULL 조건을 추가할 수 있다.

SELECT * FROM orders o
WHERE EXISTS (
    SELECT c.customer_id FROM customers c
    WHERE c.customer_id = o.customer_id AND c.customer_id IS NOT NULL
);

이렇게 하면 customers 테이블에 NULL 값이 있어도 EXISTS 연산자의 비교에서 제외된다.

NOT EXISTS 연산자

동작 방식

  • NOT EXISTS서브쿼리 결과가 존재하지 않는 경우 TRUE를 반환하여 해당 행을 출력함.
  • 서브쿼리 결과가 하나라도 존재하면 FALSE를 반환하여 해당 행을 제외함.
SELECT * FROM orders o
WHERE NOT EXISTS (
    SELECT 1 FROM customers c
    WHERE c.customer_id = o.customer_id
);
  • orders 테이블에서 customer_idcustomers 테이블에 존재하지 않는 경우만 반환됨.

NULL 값이 포함된 경우의 동작

  • NOT EXISTSNULL 값이 포함된 서브쿼리에서도 정상적으로 동작함.
  • IN이나 NOT IN과 달리 NULL 값으로 인해 결과가 UNKNOWN(즉, FALSE)이 되는 문제가 없음.
SELECT * FROM orders o
WHERE NOT EXISTS (
    SELECT 1 FROM customers c
    WHERE c.customer_id = o.customer_id
);
  • orders.customer_idNULL인 경우에도 정상적으로 비교됨.
  • customers.customer_idNULL 값이 있어도 NOT EXISTS는 단순히 서브쿼리가 결과를 반환하는지만 확인하므로 문제 발생하지 않음.

NULL 값을 제외하는 방법

  • NULL 값을 제외하고 비교하려면 IS NOT NULL을 추가해야 함.
SELECT * FROM orders o
WHERE NOT EXISTS (
    SELECT 1 FROM customers c
    WHERE c.customer_id = o.customer_id AND c.customer_id IS NOT NULL
);
  • 이렇게 하면 customers.customer_idNULL인 경우 비교 대상에서 제외됨.
IN, NOT IN, EXISTS, NOT EXISTS의 차이와 동작 방식
IN, NOT IN, EXISTS, NOT EXISTS의 차이와 동작 방식

Start the conversation