집계, JOIN과 DISTINCT에서의 NULL
그룹화, JOIN, 고유성 판단에서 NULL이 서로 다르게 동작하는 방식을 알아봅니다.
집계, JOIN과 DISTINCT에서의 NULL은(는) CoddyKit의 무료 Coding Interview Prep 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Coding Interview Prep 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Coding Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
NULL이 나타나는 놀라운 세 곳
NULL은 모든 곳에서 동일하게 동작하지 않습니다. 마지막 단원에서는 지원자들이 가장 자주 헷갈리는 세 가지 상황인 집계 함수, 조인, DISTINCT / GROUP BY를 다룹니다.
반복해서 등장하는 핵심은 집계 함수와 필터링은 NULL을 '건너뛸 값'으로 처리하지만, 그룹화와 DISTINCT는 NULL을 '다른 NULL과 같은 하나의 값'으로 처리한다는 점입니다. 이러한 불일치를 면접관이 정확히 확인합니다.
이 내용을 익히면 SQL 면접에서 가장 흔한 NULL 질문을 완전히 정복할 수 있습니다.
집계 함수는 NULL을 무시합니다
핵심 규칙은 다음과 같습니다: 집계 함수는 NULL을 건너뜁니다. SUM, AVG, MIN, MAX 및 COUNT(column)은 모두 NULL 입력을 0으로 처리하지 않고 완전히 무시합니다.
이 때문에 AVG가 예상과 다른 값을 반환할 수 있습니다. AVG는 전체 행 수가 아니라 NULL이 아닌 값의 개수로 NULL이 아닌 값의 합을 나눕니다.
-- bonus values: 100, 200, NULL
SELECT
SUM(bonus) AS total, -- 300 (NULL ignored)
AVG(bonus) AS average, -- 150 = 300 / 2, not / 3
COUNT(bonus) AS cnt -- 2 (NULL not counted)
FROM employees;COUNT(*)와 COUNT(column) 비교
가장 자주 나오는 집계와 NULL 관련 질문입니다. COUNT(*)은 NULL이 포함된 행까지 행의 수를 셉니다. COUNT(column)은 해당 열이 NULL이 아닌 행만 셉니다.
따라서 두 함수의 차이는 정확히 해당 열에 있는 NULL의 개수입니다. COUNT(DISTINCT column)은 중복을 제거하면서 NULL도 무시하여 한 단계 더 나아갑니다.
SELECT
COUNT(*) AS rows_total, -- all rows
COUNT(bonus) AS non_null_bonus, -- excludes NULLs
COUNT(DISTINCT bonus) AS distinct_bonus, -- excludes NULLs + dups
COUNT(*) - COUNT(bonus) AS null_bonus
FROM employees;AVG와 SUM/COUNT(*): 고전적인 함정
면접관은 다음과 같이 묻습니다. 'AVG(x)는 SUM(x) / COUNT(*)과 같은가요?' NULL이 있으면 답은 아니요입니다.
AVG(x)는 SUM(x) / COUNT(x)와 같으며, NULL이 아닌 값의 개수로 나눕니다. 대신 COUNT(*)으로 나누면 NULL을 0인 것처럼 처리하여 평균이 낮아집니다.
NULL을 실제로 0으로 세고 싶다면 COALESCE를 사용해 그 의도를 명시해야 합니다.
-- These differ when bonus has NULLs:
SELECT
AVG(bonus) AS avg_ignoring_nulls,
SUM(bonus) * 1.0 / COUNT(*) AS avg_nulls_as_zero,
AVG(COALESCE(bonus, 0)) AS explicit_nulls_as_zero
FROM employees;모든 값이 NULL일 때의 집계 예외 상황
모든 입력이 NULL이거나 행이 하나도 없을 때 집계 함수는 무엇을 반환할까요? 면접관이 좋아하는 정확한 구분은 다음과 같습니다:
- 모든 값이 NULL이거나 행이 0개인 경우
SUM,AVG,MIN,MAX는 NULL을 반환합니다. COUNT는 항상 0을 반환하며, NULL을 반환하지 않습니다.
따라서 보고서에 합계가 비어 있다면 모든 값이 NULL인 SUM이 원인일 가능성이 큽니다. 0을 표시하려면 COALESCE로 감싸십시오.
-- No matching rows or all bonuses NULL:
SELECT SUM(bonus) FROM employees WHERE 1 = 0; -- NULL
SELECT COUNT(bonus) FROM employees WHERE 1 = 0; -- 0
-- Present a clean zero:
SELECT COALESCE(SUM(bonus), 0) FROM employees;JOIN 조건에서의 NULL
조인의 ON 절에서도 NULL = NULL은 여전히 UNKNOWN이므로, NULL 키는 동등 조인에서 절대 일치하지 않습니다. 조인 키가 모두 NULL인 두 행도 서로 연결되지 않습니다.
이는 선택적 외래 키를 기준으로 조인할 때 자주 발생하는 문제입니다. NULL과 NULL을 일치시키는 것이 의도한 동작이라면 앞 단원에서 배운 NULL 안전 연산자(IS NOT DISTINCT FROM 또는 <=>)가 필요합니다.
-- Rows with region IS NULL on both sides do NOT match
SELECT *
FROM a JOIN b ON a.region = b.region;
-- To match NULL-to-NULL (ANSI):
SELECT *
FROM a JOIN b ON a.region IS NOT DISTINCT FROM b.region;외부 조인이 만드는 NULL
외부 조인은 일치하지 않는 행에 NULL을 만듭니다. LEFT JOIN 후에는 일치하는 행을 찾지 못한 왼쪽 행의 오른쪽 열이 모두 NULL이 됩니다.
이는 안티 조인 패턴의 기반입니다. WHERE right_table.key IS NULL로 필터링하면 주문이 없는 고객처럼 일치하는 행이 없는 행을 찾을 수 있습니다.
다만 주의해야 합니다. 외부 조인으로 생긴 열을 WHERE에서 필터링하면 외부 조인이 실수로 내부 조인으로 바뀔 수 있습니다. 다음 장면에서 이 주제를 다룹니다.
-- Find customers who have never ordered (anti-join)
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;외부 조인에서 WHERE로 NULL을 필터링할 때의 함정
자주 등장하는 함정입니다. orders를 LEFT JOIN한 다음 WHERE o.status = 'shipped'를 추가하면 주문이 없는 고객이 갑자기 사라져 외부 조인이 사실상 내부 조인으로 바뀝니다.
왜 그럴까요? 일치하지 않는 행에서는 o.status가 NULL이고, NULL = 'shipped'가 UNKNOWN이므로 WHERE가 해당 행을 제외하기 때문입니다. 일치하지 않는 행을 유지하려면 조건을 ON 절 안으로 옮기십시오.
-- Accidental inner join: drops customers with no orders
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'shipped';
-- Correct: keep unmatched customers
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id AND o.status = 'shipped';DISTINCT는 모든 NULL을 동일하게 처리합니다
여기에는 모두를 놀라게 하는 불일치가 있습니다. 집계 함수는 NULL을 건너뛰지만, DISTINCT는 정확히 하나의 NULL을 유지하며 모든 NULL을 서로 중복된 값으로 처리합니다.
따라서 값이 100, 100, NULL, NULL일 때 SELECT DISTINCT bonus를 실행하면 100, NULL, 그리고 그뿐인 세 행이 반환됩니다. 다른 곳에서는 NULL = NULL이 UNKNOWN으로 평가되는데도 두 NULL은 하나로 합쳐집니다.
-- bonus: 100, 100, NULL, NULL, 200
SELECT DISTINCT bonus FROM employees;
-- Returns: 100, 200, NULL (the two NULLs become one row)GROUP BY는 NULL을 하나의 그룹으로 묶습니다
GROUP BY는 DISTINCT와 같은 규칙을 따릅니다. 모든 NULL 키를 하나의 그룹으로 모읍니다. NULL은 서로 같은 것으로 평가되지 않는 비교 논리와는 반대입니다.
따라서 NULL을 허용하는 열로 그룹화하면 NULL 키를 가진 모든 행을 나타내는 행이 하나 생성되며, 이는 보고서 작성에서 보통 원하는 결과입니다. 깊이 있는 이해를 보여 주려면 이 대조, 즉 그룹화와 비교의 차이를 언급하십시오.
-- All employees with NULL department form ONE group
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department;
-- A single row where department is NULL totals all of them면접에서 말할 핵심 내용
면접관에게 좋은 인상을 주는 통합 요약은 다음과 같습니다.
- 집계 함수는 NULL을 무시합니다. AVG는 COUNT(열)로 나누며 COUNT(*)로 나누지 않습니다.
- COUNT(*)는 행을 셉니다. COUNT(열)과 COUNT(DISTINCT 열)은 NULL을 건너뜁니다.
- 행이 없을 때 SUM/AVG/MIN/MAX는 NULL을 반환하고 COUNT는 0을 반환합니다.
- 조인에서는 NULL 키가 절대 일치하지 않습니다. 외부 조인된 열을 WHERE에서 필터링하면 조용히 내부 조인으로 바뀝니다.
- DISTINCT와 GROUP BY는 모든 NULL을 같은 것으로 처리합니다. 이는 비교 논리와는 반대입니다.
한 문장으로 말하면 다음과 같습니다. 'NULL은 집계하고 비교할 때는 무시되지만, 중복을 제거할 때는 함께 그룹화됩니다.'
빠른 확인
그룹화와 집계의 차이를 확인해 보십시오.
복습
면접을 위한 NULL 처리 학습을 완료하셨습니다.
- 집계 함수는 NULL을 건너뜁니다. AVG는 NULL이 아닌 값의 개수로 나누며, 모든 값이 NULL인 SUM은 NULL이고 COUNT는 0입니다.
COUNT(*)는 NULL 행을 포함하지만COUNT(열)은 포함하지 않습니다. 두 결과의 차이는 NULL 개수와 같습니다.- NULL인 조인 키는 절대 일치하지 않습니다. 외부 조인된 열을 WHERE에서 필터링하면 내부 조인으로 축소될 수 있습니다.
- DISTINCT와 GROUP BY는 모든 NULL을 하나로 합칩니다. 이는 비교 논리와는 반대입니다.
다음 원칙을 기억하십시오. NULL은 집계하고 비교할 때는 무시되지만, 중복을 제거할 때는 함께 그룹화됩니다. 이 한 가지 통찰만으로도 대부분의 NULL 관련 면접 질문에 답할 수 있습니다.
자주 묻는 질문
“집계, JOIN과 DISTINCT에서의 NULL” 강의는 무료인가요?
네 — “집계, JOIN과 DISTINCT에서의 NULL” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Coding Interview Prep 강의 전체를 잠금 해제할 수 있습니다. Coding Interview Prep 강의에는 총 4개의 강의가 포함되어 있습니다.
“집계, JOIN과 DISTINCT에서의 NULL”에서 뭘 배우나요?
그룹화, JOIN, 고유성 판단에서 NULL이 서로 다르게 동작하는 방식을 알아봅니다. 브라우저에서 직접 실행하는 실습 코드로 Coding Interview Prep을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Coding Interview Prep을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Coding Interview Prep은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“집계, JOIN과 DISTINCT에서의 NULL” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Coding Interview Prep 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Coding Interview Prep 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 3값 논리와 UNKNOWN
- IS NULL, IS NOT NULL과 NULL 안전 동등성
- COALESCE, NULLIF와 ISNULL
- 집계, JOIN과 DISTINCT에서의 NULL