LEFT JOIN을 사용했는데 오른쪽 테이블의 값이 NULL로 나오거나, 조건을 추가한 뒤 왼쪽 행까지 사라지는 경우가 있다. 두 현상을 구분하려면 조인 조건과 결과 필터 조건을 따로 확인해야 한다.
고객과 주문으로 구성한 자체 예제를 사용한다. 본문의 쿼리와 결과는 SQLite에서 확인했다.
CREATE TABLE customers (
customer_id TEXT PRIMARY KEY,
customer_name TEXT NOT NULL
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id TEXT NOT NULL,
amount INTEGER NOT NULL
);
INSERT INTO customers VALUES
('C01', '민수'),
('C02', '지연'),
('C03', '현우');
INSERT INTO orders VALUES
(101, 'C01', 12000),
(102, 'C01', 5000),
(103, 'C02', 8000);
C01은 주문이 두 건, C02는 한 건이다. C03은 주문이 없다. 이 구성은 ‘여러 건 일치’, ‘한 건 일치’, ‘일치 없음’을 함께 확인하기 위한 것이다.
SELECT c.customer_id, c.customer_name, o.order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
ON c.customer_id = o.customer_id
ORDER BY c.customer_id, o.order_id;
| customer_id | customer_name | order_id | amount |
|---|---|---|---|
| C01 | 민수 | 101 | 12000 |
| C01 | 민수 | 102 | 5000 |
| C02 | 지연 | 103 | 8000 |
| C03 | 현우 | NULL | NULL |
C03은 조인 조건에 맞는 주문이 없어 오른쪽 컬럼이 NULL로 채워진다. 고객 정보는 왼쪽 테이블에서 선택했으므로 그대로 남는다.
반대로 주문 테이블 쪽의 o.customer_id를 선택했다면 C03 행의 해당 값도 NULL이다. 이름이 같은 컬럼이라도 어느 테이블에서 가져왔는지 확인해야 한다.
또한 LEFT JOIN이 왼쪽 행을 정확히 한 번씩 출력한다는 뜻은 아니다. C01처럼 오른쪽에서 두 행이 일치하면 결과에도 두 행이 생긴다.
SELECT c.customer_id, o.order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
ON c.customer_id = o.customer_id
AND o.amount >= 10000
ORDER BY c.customer_id, o.order_id;
| customer_id | order_id | amount |
|---|---|---|
| C01 | 101 | 12000 |
| C02 | NULL | NULL |
| C03 | NULL | NULL |
금액 조건을 만족하는 주문만 연결하면서, 일치하지 않는 고객도 남긴다. C02는 주문 자체가 없는 것이 아니라 조건에 맞는 주문이 없는 고객이다. NULL을 해석할 때 어떤 조인 조건을 적용했는지 함께 봐야 한다.
SELECT c.customer_id, o.order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
ON c.customer_id = o.customer_id
WHERE o.amount >= 10000
ORDER BY c.customer_id, o.order_id;
| customer_id | order_id | amount |
|---|---|---|
| C01 | 101 | 12000 |
이 조건은 조인 결과에서 금액을 검사한다. C02의 8,000원 주문은 기준 미달로 제외되고, C03의 NULL도 >= 10000을 참으로 만족하지 않아 제외된다.
여기서는 오른쪽 금액에 대한 조건이 NULL로 확장된 행을 제거한다. 다만 모든 WHERE 조건이 같은 효과를 내는 것은 아니다. 어떤 컬럼과 조건식을 사용하는지가 중요하다.
WHERE o.amount >= 10000 OR o.order_id IS NULL로 바꿔도 ON에 조건을 둔 결과와 같지 않다. C02는 이미 8,000원 주문과 일치했기 때문에 order_id가 NULL인 행이 만들어지지 않는다. 이 조건에서는 C01과 C03만 남는다.
SELECT c.customer_id, c.customer_name
FROM customers AS c
LEFT JOIN orders AS o
ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL
ORDER BY c.customer_id;
결과는 C03, 현우다. 일치 여부를 판별할 때는 실제 주문에 반드시 존재하는 키인 order_id를 사용한다. 값이 원래 NULL일 수 있는 일반 컬럼을 기준으로 삼으면, ‘일치한 행의 값이 NULL’인 경우와 ‘일치한 행이 없음’을 혼동할 수 있다.
조회 목적이 ‘모든 고객을 남기되 조건에 맞는 주문만 연결’이라면 조건을 ON에 둔다. ‘조건을 만족하는 주문이 연결된 결과만 조회’라면 WHERE 필터를 적용한다.
NULL이 보이면 먼저 조인 키와 일치 여부를 확인하고, 그다음 SELECT에서 선택한 테이블과 ON·WHERE의 조건을 점검한다. 화면의 빈칸만으로 원본 데이터가 누락됐다고 판단하지 않는 것이 중요하다.