DBAtlas · 데이터베이스 운영 아틀라스

EXISTS / IN / NOT EXISTS(서브쿼리)

Oracle

-- Customers with orders (EXISTS)
SELECT name FROM customer m
WHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);

-- List membership with IN
SELECT name FROM customer
WHERE city IN ('Ankara', 'Izmir');

-- Customers with no orders at all (NOT EXISTS)
SELECT name FROM customer m
WHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);

EXISTS는 상관 서브쿼리가 최소 한 행을 반환하는지 확인한다(첫 일치에서 멈추므로 빠르다). IN은 값이 목록/서브쿼리 결과에 있는지 확인한다. 주의: 서브쿼리가 NULL을 생성할 수 있으면 NOT IN은 예상치 못한 결과를 내므로 NOT EXISTS가 더 안전하다. Oracle 옵티마이저는 둘을 같은 실행 계획으로 변환하는 경우가 많다.

PostgreSQL

SELECT name FROM customer m
WHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);

SELECT name FROM customer
WHERE city IN (SELECT city FROM target_cities);

SELECT name FROM customer m
WHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);

PostgreSQL은 EXISTS/IN/NOT EXISTS를 완전히 지원한다. NOT IN에서 서브쿼리에 NULL이 있으면 전체 결과가 비어 버릴 수 있으므로 NOT EXISTS나 LEFT JOIN ... IS NULL이 권장된다. 플래너는 대부분의 경우 IN과 EXISTS를 동일하게 semi-join으로 처리한다.

MySQL

SELECT name FROM customer m
WHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);

SELECT name FROM customer
WHERE city IN ('Ankara', 'Izmir');

SELECT name FROM customer m
WHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);

MySQL/MariaDB는 EXISTS/IN/NOT EXISTS를 지원한다. MySQL 5.6 이전에는 상관 서브쿼리가 느릴 수 있었으며, 5.6+는 semi-join과 materialization 최적화로 IN 서브쿼리를 가속했다. NOT IN + NULL 함정은 여기서도 적용된다.

SQL Server

SELECT name FROM customer m
WHERE EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);

SELECT name FROM customer
WHERE city IN (SELECT city FROM target_cities);

SELECT name FROM customer m
WHERE NOT EXISTS (SELECT 1 FROM orders s WHERE s.customer_id = m.id);

SQL Server는 EXISTS/IN/NOT EXISTS를 지원하며, 옵티마이저는 대부분 이를 semi/anti-join으로 처리한다. NOT IN 서브쿼리에 NULL이 있으면 결과가 예상치 않게 비어 버릴 수 있으므로 NOT EXISTS가 안전한 선택이다. 비슷한 멤버십 로직에 INTERSECT/EXCEPT도 사용할 수 있다.

MongoDB

// IN equivalent: the $in operator
db.customer.find({ city: { $in: ["Ankara", "Izmir"] } })

// EXISTS equivalent: match check with $lookup + $match
db.customer.aggregate([
  { $lookup: { from: "orders", localField: "id",
      foreignField: "customer_id", as: "s" } },
  { $match: { "s.0": { $exists: true } } }  // at least one order
])

MongoDB에서 값 목록 멤버십은 $in / $nin 연산자로 처리한다. 서브쿼리 EXISTS에 해당하는 방법은 $lookup으로 일치하는 문서 배열을 가져와 배열이 비었는지로 필터링하는 것이다. 필드 존재 확인은 별도의 { field: { $exists: true } } 연산자다(행 관계가 아니라 필드의 존재 여부).

Redis

# Membership check with Set/Hash:
SISMEMBER target_cities "Ankara"   # is it in the set? (IN-like)
EXISTS customer:1                 # does the key exist?

Redis에는 서브쿼리가 없다. 값 멤버십에는 Set에 SISMEMBER를 사용한다(IN과 유사). EXISTS 명령은 서브쿼리가 아니며 지정한 키가 존재하는지를 반환한다. 관계형 존재 확인은 여러 키를 읽어 애플리케이션 측에서 수행한다.

Elasticsearch

# IN equivalent: terms query
GET customer/_search
{ "query": { "terms": { "city": ["Ankara", "Izmir"] } } }

# Field existence: exists query
GET customer/_search
{ "query": { "exists": { "field": "phone" } } }

Elasticsearch에서 IN에 해당하는 것은 'terms' 쿼리(여러 값 중 하나와 일치)이며, NOT IN은 bool must_not 안의 terms다. 'exists' 쿼리는 SQL EXISTS에 해당하지 않으며, 문서에서 필드에 값이 있는지 확인한다. 테이블 간 상관 EXISTS에는 보통 비정규화가 필요하다.

DBAtlas에서 열기 →