NULL은 0이 아닙니다 —
"값이 없다"가 만드는 3가지 함정
LEFT JOIN 결과의 NULL, COUNT(*)와 COUNT(컬럼)의 차이, 그리고 NULL과의 비교 — AI가 만든 SQL에서 NULL 함정이 숨어드는 3곳을 shop 데이터로 직접 재현합니다. NULL은 어려운 개념이 아니에요. 규칙만 알면 COALESCE와 IS NULL로 전부 잡힙니다.
이번 섹션의 여정
완료 ✓
지금 여기
집계 단위 함정
의심 루틴 완성
다음 마일스톤
NULL — 0도 아니고 빈 문자열도 아닙니다
3-2 WHERE에서 한 번 다뤘죠 — NULL은 “값이 없다”는 특수 상태라서 = NULL로는 못 찾고 IS NULL 을 써야 한다고요. 오늘은 그 NULL이 AI가 만든 SQL에서 어떻게 사고를 치는지 — 가장 흔한 3가지 사례를 직접 재현하고, 잡는 법까지 챙깁니다.
NULL = “값이 없다, 모른다”. 0도 아니고 빈 문자열도 아닙니다. 그래서 NULL이 낀 연산의 결과는 — 항상 “모름(UNKNOWN)“이 됩니다.
함정 ① — LEFT JOIN이 만드는 NULL ⭐
요구사항: “모든 회원의 매출을 보여줘 — 주문 안 한 회원도 포함해서.” AI는 배운 대로 LEFT JOIN을 정확히 썼습니다. 주문 없는 회원도 안 잘리고요. 그런데 —
-- AI가 만든 SQL — LEFT JOIN 자체는 정확합니다
SELECT
m.member_name,
SUM(p.price * oi.quantity) AS 매출
FROM
members m
LEFT JOIN orders o
ON m.member_id = o.member_id
LEFT JOIN order_items oi
ON o.order_id = oi.order_id
LEFT JOIN products p
ON oi.product_id = p.product_id
GROUP BY
m.member_id,
m.member_name
ORDER BY
매출 DESC;| member_name | 매출 |
|---|---|
| 조성민 | 600000 |
| 김철수 | 498000 |
| 박민수 | 356000 |
| ⋮ | |
| 황태웅 | NULL |
| 안소영 | NULL |
| 류재민 | NULL |
| 백서윤 | NULL |
황태웅·안소영처럼 주문 안 한 회원의 매출이 NULL로 나옵니다. 0이 아니에요 — “값이 없다, 모른다”입니다. 이대로 두면 이런 문제가 생깁니다.
- 보고서에 NULL 글자가 그대로 찍힘 — 비전공자는 “오류인가?” 하고 헷갈립니다.
- 정렬하면 NULL이 맨 끝(또는 맨 앞)으로 — 의도와 다른 위치로 밀립니다.
- 이 결과 위에 평균·합계 같은 2차 계산을 얹으면 — NULL 회원이 통째로 빠집니다.
- NULL이 낀 산술식(예: 매출 + 100)은 — 결과가 통째로 NULL이 됩니다.
위 결과로 회원 평균 매출을 내면 AVG(매출)은 NULL인 4명을 무시하고
3,013,000 ÷ 16 ≈ 188,313원. 주문 안 한 회원까지 포함한 진짜 평균은
3,013,000 ÷ 20 = 150,650원입니다. NULL을 그대로 두면 평균이 25% 부풀어
보고됩니다.
처방은 함수 하나입니다 — COALESCE(값1, 값2), “값1이 NULL이면 값2를 대신 써.”
-- 처방 — COALESCE로 NULL을 0으로 명시
SELECT
m.member_name,
COALESCE(SUM(p.price * oi.quantity), 0) AS 매출
FROM
members m
LEFT JOIN orders o
ON m.member_id = o.member_id
LEFT JOIN order_items oi
ON o.order_id = oi.order_id
LEFT JOIN products p
ON oi.product_id = p.product_id
GROUP BY
m.member_id,
m.member_name
ORDER BY
매출 DESC;| member_name | 매출 |
|---|---|
| 조성민 | 600000 |
| 김철수 | 498000 |
| ⋮ | |
| 황태웅 | 0 |
| 안소영 | 0 |
| 류재민 | 0 |
| 백서윤 | 0 |
IFNULL(값, 대체값)은 같은 의미의 MySQL 전용 함수예요. COALESCE는 표준
SQL인 데다 인수를 여러 개 받아 앞에서부터 NULL이 아닌 첫 값을 고를 수도 있어서
— 이 강의에서는 COALESCE로 통일합니다.
LEFT JOIN + 집계 결과는 — 항상 COALESCE(..., 0)로 NULL → 0을 명시합니다.
함정 ② — COUNT(*) vs COUNT(컬럼)
둘의 차이를 정확히 알고 계세요? 모르면 큰 사고가 나는 차이입니다.
NULL이어도 행이 있으면 1
LEFT JOIN에서 매칭이 없어도 행 자체는 1행 존재합니다 — 오른쪽 컬럼이 전부 NULL인 채로요. 이 버전은 그 행도 셉니다. 주문 0건인 황태웅이 1로 나오는 이유.
그 컬럼이 NULL이 아닌 행만
COUNT(o.order_id)는 order_id가 NULL인 행을 빼고 셉니다. 주문 0건인 황태웅이 정확히 0 — 우리가 원한 답입니다.
shop에서 두 버전을 나란히 놓고 비교해보세요.
-- 모든 회원 + 주문 LEFT JOIN — 두 COUNT를 나란히
SELECT
m.member_name,
COUNT(*) AS count_all, -- 행 수 (NULL 포함)
COUNT(o.order_id) AS count_order_id -- 값 수 (NULL 제외)
FROM
members m
LEFT JOIN orders o
ON m.member_id = o.member_id
GROUP BY
m.member_id,
m.member_name;| member_name | count_all | count_order_id |
|---|---|---|
| 김철수 | 6 | 6 |
| 이영희 | 4 | 4 |
| 조성민 | 4 | 4 |
| ⋮ | ||
| 황태웅 | 1 | 0 |
| 안소영 | 1 | 0 |
주문이 있는 회원(김철수 6건)은 두 숫자가 같지만, 주문 0건인 황태웅에서 갈립니다. COUNT(*) = 1, COUNT(o.order_id) = 0 — 우리가 원한 답은 0이죠.
AI는 습관처럼 COUNT(*)만 쓰는 경우가 많아요. 그러면 주문 0건 회원이 “1건
주문한 것처럼” 보고됩니다. LEFT JOIN 뒤의 COUNT는 무조건 한 번 의심하세요.
주문 안 한 회원의 주문 수를 정확히 0으로 세려면 — COUNT(*)가 아니라
COUNT(자식 테이블의 컬럼).
함정 ③ — NULL과 비교하면 UNKNOWN
-- AI가 만든 SQL — 언뜻 보면 "30살이 아닌 회원"
SELECT
*
FROM
members
WHERE
age != 30;멀쩡해 보이지만 — age가 NULL인 회원은 이 결과에 안 나옵니다. 30살이 “아닌”데도요. SQL에서 NULL과 어떤 값을 비교하면 결과는 TRUE도 FALSE도 아닌 UNKNOWN이기 때문입니다.
NOT (age = 30)으로 바꿔 써도 똑같이 당합니다 — NOT UNKNOWN도 여전히 UNKNOWN이거든요. 해결은 NULL 분기를 명시하는 것.
-- NULL인 회원도 결과에 포함하려면
SELECT
*
FROM
members
WHERE
age != 30 OR age IS NULL;우리 members.age는 NOT NULL 컬럼이라 빠지는 행이 없어요 — age != 30의
결과는 19명(30살 노수아 1명만 제외)으로 정상입니다. 하지만 실무 DB의
나이·전화번호·탈퇴일 같은 NULL 허용 컬럼에서는 정말 자주 터지는
함정입니다.
비교 연산 결과가 이상할 땐 — 그 컬럼의 NULL 가능성부터 점검하고, 필요하면
OR ... IS NULL 분기를 추가합니다.
AI에게 NULL 점검을 시키세요
6-2의 검산 협업과 같은 패턴입니다. AI한테 SQL을 받자마자 — 아래 후속 메시지 하나로 NULL 처리를 자체 점검시키세요.
방금 만든 SQL을 NULL 처리 관점에서 점검해줘.
1. LEFT JOIN 결과에 NULL이 들어가는 컬럼이 있어?
있다면 COALESCE로 처리해야 하지 않아?
2. COUNT(*)와 COUNT(컬럼) 중 어느 쪽을 썼어? 의도에 맞아?
3. WHERE 조건에 쓴 컬럼이 NULL일 가능성은 없어?
IS NULL 분기가 필요하지 않아?
위 3가지를 점검하고, 필요하면 SQL을 수정해줘.이 한 메시지로 AI가 본인이 만든 SQL의 NULL 처리를 스스로 점검하고 고쳐줍니다. 단 — 이 질문은 검증 포인트를 아는 사람만 던질 수 있어요. 그 포인트가 바로 오늘 배운 3가지입니다.
한눈에 정리
| # | 함정 | 증상 | 잡는 법 |
|---|---|---|---|
| 1 | LEFT JOIN 결과의 NULL | 매출이 0 대신 NULL | COALESCE(SUM(...), 0) |
| 2 | COUNT(*) vs COUNT(컬럼) | 0건이 1건으로 둔갑 | COUNT(자식 테이블 컬럼) |
| 3 | 비교 연산의 UNKNOWN | NULL 행이 조용히 증발 | OR ... IS NULL 추가 |
NULL = “값이 없다”. 0도 아니고 빈 문자열도 아니며, NULL이 낀 연산은
항상 “모름(UNKNOWN)“입니다. SQL의 모든 NULL 처리는 — COALESCE / IS NULL /
COUNT(컬럼) 셋 중 하나로 명시하세요.
자주 막히는 지점
3-2에서 본 규칙 그대로입니다 — NULL과의 비교는 TRUE가 아니라 UNKNOWN이에요.
=로 못 찾습니다 — IS NULL로. = NULL은 항상 UNKNOWN이라 모든 행이 버려집니다.IFNULL은 MySQL 전용이고 인수가 2개로 고정입니다. 나중에 다른 DB로 옮겨도 그대로 도는 건 COALESCE 쪽이에요.
결과 그리드에서 NULL은 연한 글씨의 [NULL]로 표시됩니다. ‘NULL’이라는 글자가 저장된 게 아니에요.
NULL 함정 3가지 — 결국 전부 “값이 없다”는 한 가지 성질에서 나옵니다. 연습 문제로 손에 익히고 나면, 다음 강의에서는 AI가 집계 단위를 잘못 잡는 GROUP BY 함정으로 넘어갑니다.