Devin.KR

지역별 합계와 건수

100분 안팎

학습 목표

지역·날짜별 SUM·COUNT와 NULL 제외 건수를 계산합니다.

개념

합계 옆에 관측 수를 둡니다

통행량 0대와 측정 누락은 보고서에서 모두 빈 막대로 보일 수 있습니다. 지역·날짜별 합계만 보내면 수치가 없는 이유를 알 수 없습니다. SUM(vehicle_count)는 수량을 더하고 COUNT(vehicle_count)는 수량이 알려진 행 수를 셉니다. 이 둘을 같이 출력하면 “관측 1행의 0대”와 “관측 0행의 미관측”을 구분할 수 있습니다. COUNT(*)는 수량의 유무와 관계없이 그룹에 들어온 전체 행을 셉니다.

집계 함수는 여러 행을 요약합니다. GROUP BY 없이 SUM을 조회하면 필터를 통과한 전체 표가 한 요약으로 나옵니다. GROUP BY region, date는 같은 지역·날짜 조합끼리 묶습니다. 그룹당 결과 한 행을 만들기 때문에 원본 행 수와 출력 행 수는 달라질 수 있습니다. SELECT에 지역·날짜를 함께 적고 수량은 집계 함수로 감싸면 출력 행의 관측 단위를 명확하게 읽을 수 있습니다.

원본 일별 그룹을 손으로 계산합니다

A의 9월 1일은 100 하나이므로 합계100, 전체행1, 유효관측1입니다. A의 9월 2일은 80 두 행이므로 합계160, 전체행2, 유효관측2입니다. A의 9월 3일은 NULL 한 행이므로 합계NULL, 전체행1, 유효관측0입니다. B의 9월 2일은 0 한 행이므로 합계0, 전체행1, 유효관측1입니다. 이 네 그룹만 정확히 구분해도 중복과 결측이 집계에 미치는 영향을 확인할 수 있습니다.

SUM은 NULL을 합산에서 제외합니다. 그룹에 숫자가 하나도 없으면 합계는 NULL입니다. COUNT는 그 경우에도 0을 반환합니다. COALESCE(SUM(vehicle_count),0)으로 표시를 바꿀 수 있지만 미관측을 실제 0대와 같은 숫자로 만들기 때문에 이번 점검 표에서는 쓰지 않습니다. 화면 표시 규칙과 분석용 값의 의미가 달라지지 않도록 빈칸의 의미를 CSV 사전에 남깁니다.

행 필터와 그룹 필터를 나눕니다

WHERE는 묶기 전에 적용합니다. 지역·기간을 먼저 제한한 뒤 일별 합계를 만듭니다. HAVING은 묶은 그룹에 조건을 적용합니다. HAVING COUNT(vehicle_count)=0이면 측정값이 없는 그룹만 찾을 수 있습니다. WHERE COUNT(vehicle_count)=0이라고 쓰면 행을 고르는 단계에 집계 함수를 사용하여 오류가 납니다. 같은 “필터”라는 말이어도 대상이 원본 행인지 요약 그룹인지 나눠 생각합니다.

NULL 행을 WHERE에서 미리 제외하면 미관측만 있는 A 9월 3일 그룹은 아예 만들어지지 않습니다. 그룹을 남기고 COUNT=0으로 드러내려면 NULL 행을 입력에 유지해야 합니다. 결과 표의 행 수가 줄었을 때 값이 0인 그룹이 빠졌는지, 미관측 그룹이 빠졌는지, 기간 밖 그룹만 빠졌는지 따로 확인합니다. 합계가 같아도 그룹 누락 때문에 날 수나 평균 분모가 바뀔 수 있습니다.

GROUP BY 열이 출력 단위입니다

GROUP BY region만 쓰면 세 날짜가 지역별 한 행으로 합쳐집니다. GROUP BY date만 쓰면 A와 B가 같은 날짜로 합쳐집니다. 둘 다 문법상 가능한 질문이지만 “지역·날짜별 표” 요구를 만족하지 않습니다. 출력의 그룹 열과 집계식을 한 문장으로 설명해 봅니다. “대상 기간의 각 지역·날짜마다 통행량 합계와 값이 있는 원본 행 수”라고 설명할 수 있어야 합니다.

SQLite에서 집계되지 않았고 GROUP BY에도 없는 열이 SELECT에 남아도 문장이 실행될 수 있습니다. 하지만 해당 값이 그룹 전체를 대표한다는 뜻은 아닙니다. region으로만 묶고 date를 그대로 출력해 나온 날짜를 기간 대표값으로 믿지 않습니다. 이 레슨에서는 SELECT의 비집계 열을 모두 GROUP BY에 포함해 조회 의도를 고정합니다. 다른 DB의 엄격한 검사에서도 설명 가능한 형태를 습관으로 삼습니다.

중복 정책은 합계의 일부입니다

이번 브라우저 과제는 원본 traffic을 집계하므로 80대 두 행이 160대가 됩니다. 프로젝트 미션은 앞 계약에 따라 전체 열이 완전히 같은 행을 하나만 유지하는 traffic_unique 뷰를 집계합니다. 그러면 해당 날짜 합계는80, 전체 합계는390입니다. 원본의470과 다른 이유는 SQL이 틀려서가 아니라 입력 행 정책이 달라졌기 때문입니다. 단계 이름을 붙여 어떤 표의 합계인지 보고합니다.

SUM(DISTINCT vehicle_count)를 중복 행 제거의 대체로 쓰지 않습니다. 값만 같고 날짜나 지역이 다른 관측을 같은 숫자라는 이유로 하나만 셀 수 있기 때문입니다. 중복은 관측을 구성하는 전체 열 또는 정의한 키를 확인해 처리하고 그 뒤 SUM합니다. 같은 지역·날짜인데80과90이 함께 있으면 완전 일치 중복이 아니므로 원본을 확인해야 합니다. 미션 로더는 이 충돌을 KEY 오류로 거부합니다.

misuse of aggregate: COUNT()는 행 필터 위치에 집계를 썼는지 점검하는 신호입니다. 합계는 맞는데 유효 건수가1 부족하면 COUNT(*)와 COUNT(열)의 차이, 0을 제외한 조건을 살펴봅니다. 소수나 문자열이 수량에 들어간 상태를 집계로 감추지 않고 적재 시 자료형을 검사합니다. 정확한 계산과 적절한 해석은 구분하며 실제 교통 경향은 이 합성값으로 추정하지 않습니다.

부분 합계와 전체 합계를 교차 확인합니다

원본의 지역별 합계는 A가 260, B가 210으로 둘을 더하면 470입니다. 일별 그룹의 합계를 더해도 같은 입력 범위라면 470이 되어야 합니다. 반면 그룹 수는 6이고 원본 행 수는 7이므로 두 수가 같아야 한다고 검사하면 안 됩니다. 일별 raw_rows를 모두 더하면 원본 행 수 7로 돌아오고 valid_observations를 더하면 비결측 행 수 6으로 돌아옵니다. 수량, 그룹 수, 입력 행 수를 각각 다른 지표로 검산합니다.

입력이 비었을 때도 GROUP BY 유무를 구분합니다. 빈 traffic에 GROUP BY region, date를 적용하면 그룹이 없어 결과도 0행입니다. GROUP BY 없이 전체 SUM과 COUNT를 조회하면 요약 1행이 나오며 값은 NULL과 0입니다. 따라서 빈 일별 결과를 오류라고 단정하거나 지역별 0행을 임의로 만들어 넣지 않습니다. 요구된 출력 단위가 실제 관측 그룹인지 전체 표 요약인지 먼저 확인합니다.

집계 별칭은 값의 의미를 전달합니다. total_vehicles는 수량 합계, raw_rows는 입력 행 수, valid_observations는 수량이 존재하는 행 수입니다. 셋 모두 정수처럼 보여도 서로 바꾸어 쓰면 안 됩니다. 정렬은 집계값을 변경하지 않지만 결과를 검산하기 쉽게 만듭니다. 지역·날짜를 기준으로 정렬한 뒤 한 그룹씩 기준 표와 비교하고, 차이가 생긴 그룹의 원본 행으로 돌아가 원인을 찾습니다.

빈 표와 미관측 그룹을 구별합니다

입력 표가 비어 있고 GROUP BY region, date를 사용하면 그룹 자체가 없으므로 결과도 빈 표입니다. 반면 NULL 한 행이 입력되면 그 지역·날짜 그룹은 존재하여 SUM은 NULL, COUNT(*)는 1, COUNT(vehicle_count)는 0이 됩니다. GROUP BY 없는 전체 집계는 빈 입력에서도 요약 한 행을 반환하며 SUM은 NULL, 두 COUNT는 0입니다. 결과 행이 없는 상태와 결과 행의 합계가 NULL인 상태를 같은 것으로 처리하지 않습니다.

여러 그룹의 합계를 다시 더할 때는 누락된 관측이 있는지도 함께 보고합니다. 알려진 숫자만 더해 470이 나왔다는 사실은 모든 날짜의 통행량이 관측되었다는 뜻이 아닙니다. 원본 일곱 행 중 수량이 있는 행은 여섯이며 A의 9월 3일은 합계에 기여하지 않습니다. 전체 합계 옆에 전체 행 수와 유효관측 수를 두면 계산 대상의 완전성을 확인할 수 있습니다. 미관측을 임의로 0으로 채우면 이 차이를 잃습니다.

평균의 분모도 확인합니다

AVG(vehicle_count)는 NULL을 제외한 값의 평균입니다. 원본 A는 100, 80, 80을 대상으로 평균을 내므로 세 행이 분모가 됩니다. 날짜가 세 개라는 이유만으로 NULL 날짜를 포함한 세 날짜의 평균이라고 설명해서는 안 됩니다. 중복을 제거한 A의 알려진 값은 100과 80이며 평균은 90입니다. 같은 함수라도 입력 정책이 다르면 결과가 달라지므로 원본 행 평균인지 정제된 관측 평균인지 이름에 드러냅니다.

그룹별 결과를 손으로 검토할 때는 그룹 키부터 적고 입력 수량 목록을 이어 씁니다. 목록의 전체 길이는 COUNT(*), NULL을 뺀 길이는 COUNT(vehicle_count), 알려진 값의 덧셈은 SUM과 대조합니다. 마지막으로 ORDER BY region, date를 확인하여 결과 비교 순서를 고정합니다. GROUP BY가 우연히 정렬된 결과를 반환해도 그 순서는 보장되지 않습니다. 집계식의 정확성과 파일 전달 순서를 따로 점검하면 오류 위치를 좁힐 수 있습니다.

따라하기

원본을 지역·날짜로 집계

A2일 두80을 원본 기준으로160으로 계산합니다.

SELECT region, date, SUM(vehicle_count) AS total_vehicles, COUNT(*) AS raw_rows, COUNT(vehicle_count) AS valid_observations FROM traffic GROUP BY region, date ORDER BY region, date;

실행 결과

A 2026-09-01 100 1 1
A 2026-09-02 160 2 2
A 2026-09-03 NULL 1 0
B 2026-09-01 120 1 1
B 2026-09-02 0 1 1
B 2026-09-03 90 1 1

전체 합계와 관측 수 대조

원본 기준 전체 합계470·전체행7·유효관측6입니다.

SELECT SUM(vehicle_count), COUNT(*), COUNT(vehicle_count) FROM traffic;

실행 결과

470 7 6

미관측 그룹 확인

HAVING으로 유효관측 없는 그룹을 찾습니다.

SELECT region, date, COUNT(vehicle_count) FROM traffic GROUP BY region,date HAVING COUNT(vehicle_count)=0 ORDER BY region,date;

실행 결과

A 2026-09-03 0

확인 문제

실습

traffic 원본 전체를 지역·날짜로 집계합니다. 출력 열은 region, date, total_vehicles, raw_rows, valid_observations입니다. SUM·COUNT(*)·COUNT(vehicle_count)를 사용하고 지역→날짜 오름차순입니다. NULL 그룹과 반복 행을 유지합니다.

모범 답안
SELECT region, date, SUM(vehicle_count) AS total_vehicles, COUNT(*) AS raw_rows, COUNT(vehicle_count) AS valid_observations FROM traffic GROUP BY region, date ORDER BY region, date;

더 읽기

면접 질문

  • COUNT(*)와 COUNT(열)은 NULL이 있는 그룹에서 어떻게 다른가요?
  • WHERE와 HAVING의 적용 대상은 어떻게 다른가요?