COUNT(*)와 COUNT(컬럼)은 집계 대상이 다르다. 이 차이는 NULL이 포함된 데이터에서 분명하게 드러난다. 행 수, 값의 개수, 중복 제거, 평균의 분모를 구분해 정리한다.
데이터는 별도로 구성한 학습 예제다. 결과는 SQLite 기준이며, 교재나 특정 시험 회차의 문제를 옮긴 글은 아니다.
CREATE TABLE scores (score INTEGER);
INSERT INTO scores VALUES (80), (NULL), (0), (80);
SELECT
COUNT(*) AS row_count,
COUNT(score) AS value_count,
COUNT(DISTINCT score) AS distinct_count,
SUM(score) AS total_score,
AVG(score) AS average_score
FROM scores;
| 집계식 | 결과 | 집계 기준 |
|---|---|---|
COUNT(*) | 4 | 전체 행 수 |
COUNT(score) | 3 | score가 NULL이 아닌 행 수 |
COUNT(DISTINCT score) | 2 | NULL과 중복을 제외한 값의 개수 |
SUM(score) | 160 | NULL을 제외한 값의 합 |
AVG(score) | 약 53.3333 | NULL을 제외한 값의 평균 |
COUNT(*)는 score가 NULL인 행도 포함한다. 반면 COUNT(score)는 해당 값이 NULL인 행을 제외한다. COUNT(DISTINCT score)의 집계 대상은 서로 다른 값인 80과 0이다.
0은 실제 숫자 값이므로 COUNT(score)에 포함된다. 이 예제에서 NULL은 점수가 기록되지 않은 상태로 해석할 수 있다. 점수가 0이라는 사실과 점수를 알 수 없다는 상태는 서로 다르다.
집계 결과를 해석할 때는 NULL을 단순히 0처럼 취급하지 않아야 한다. 두 값을 동일하게 처리하면 데이터가 가진 의미도 달라진다.
AVG(score)는 NULL이 아닌 세 값의 평균을 계산한다.
(80 + 0 + 80) / 3 = 53.3333...
전체 행 수인 4로 나누면 40이 된다. 이는 미기록 점수를 0으로 간주한 경우와 같은 계산이며, 원래의 평균과는 기준이 다르다. NULL을 0으로 대체할지는 함수 사용의 편의가 아니라 데이터의 의미와 업무 규칙으로 결정해야 한다.
NULL인 두 행만 있다고 가정하면 SQLite의 결과는 다음과 같다.
| 집계식 | 결과 |
|---|---|
COUNT(*) | 2 |
COUNT(score) | 0 |
SUM(score) | NULL |
AVG(score) | NULL |
합계와 평균을 계산할 유효한 값이 없다는 상태는 계산 결과가 0이라는 의미와 다르다. 특히 SUM의 결과를 항상 숫자라고 가정하지 않도록 주의해야 한다.
먼저 집계 대상이 행인지 값인지 확인한다. 다음으로 NULL 제외 여부와 중복 제거 여부를 구분한다. 평균은 분자뿐 아니라 분모에 포함되는 값의 개수까지 확인한다. 이 기준을 적용하면 집계 함수별 결과를 일관되게 설명할 수 있다.