작업은 네 건인데 ‘재시도 횟수가 기록된 작업’은 세 건이라고 나온다. 한 건이 사라졌나? 두 숫자가 서로 다른 대상을 세었을 가능성이 크다. COUNT(*)는 결과의 행을, COUNT(컬럼)은 그 컬럼에서 NULL이 아닌 값을 센다.
COUNT 함수의 별표와 컬럼은 무엇을 셀까?
다음 네 작업에서 retry_count가 NULL인 것은 3번 한 건이다. 1번의 0은 ‘재시도하지 않았다’는 기록된 값이지, 값이 없는 상태가 아니다.
도식에서 0은 값이므로 세지만 NULL은 COUNT(retry_count)에서 빠진다. 네 행을 만든 뒤 두 집계 결과를 같은 쿼리에서 비교해 보자.
CREATE TABLE jobs (
id INTEGER PRIMARY KEY,
retry_count INTEGER
);
INSERT INTO jobs (id, retry_count) VALUES
(1, 0), (2, 2), (3, NULL), (4, 1);
SELECT COUNT(*) AS total_jobs,
COUNT(retry_count) AS recorded_jobs
FROM jobs;| total_jobs | recorded_jobs |
|---|---|
| 4 | 3 |
COUNT(*)의 별표는 각 행을 세라는 뜻이다. 반면 COUNT(retry_count)는 그 열의 값이 있는 행만 센다. 그래서 0, 2, 1은 포함되고 NULL 한 건만 빠진다.
NULL인 작업은 어떻게 찾을까?
두 건수의 차이는 같은 입력 행 집합에서 그 컬럼이 NULL인 행의 수다.
SELECT COUNT(*) - COUNT(retry_count) AS missing_retry_count
FROM jobs;
-- 1
SELECT id FROM jobs WHERE retry_count IS NULL;
-- 3missing_retry_count = 1은 누락된 값의 개수, id = 3은 그 값이 없는 작업을 보여 준다. 숫자 0을 누락으로 취급해 세면 답이 틀어진다.
WHERE를 붙이면 왜 두 COUNT가 같아질까?
WHERE가 먼저 NULL 행을 제외하면, 그 뒤에 세는 행에는 빠진 값이 없다.
SELECT COUNT(*) AS rows_after_filter,
COUNT(retry_count) AS recorded_after_filter
FROM jobs
WHERE retry_count IS NOT NULL;
-- 3 | 3따라서 건수가 예상과 다를 때는 괄호 안만 보지 말고 같은 FROM·WHERE 결과를 세고 있는지 확인하자. 조인으로 행이 늘어난 쿼리라면 COUNT(*)도 원본 작업 수가 아니라 늘어난 결과 행을 센다.
핵심 요약
COUNT(*)는 결과 행 전체를 세고, COUNT(컬럼)은 그 열의 NULL을 제외한다. 0은 빠지지 않는다. 차이를 조사할 때는 COUNT(*) - COUNT(컬럼)으로 누락된 값의 수를 보고, IS NULL로 해당 행을 확인하면 된다.
작성자
기초 개념을 구현과 검증, 실제 운영 판단까지 연결해 기록합니다.

