Devin.KR

집계값을 원본과 대조

100분 안팎

학습 목표

일별 집계 SQL을 파일로 저장하고 작은 원본 표본의 손계산 결과와 비교합니다.

개념

맞는 숫자인지 설명할 수 있어야 합니다

보고서의 합계가 원본과 다르면 쿼리를 바로 고치기 전에 두 합계가 같은 대상을 세는지 확인합니다. 원본 합계470, 완전 일치 중복 제거 후390이라는 차이80은 A의 9월2일 반복 행으로 설명됩니다. 대조는 아무 원본 합계와 아무 집계 합계를 비교하는 일이 아닙니다. 기간·지역·결측·중복 정책을 양쪽에 동일하게 적용하고, 같아야 하는 수치와 달라도 되는 수치를 구분하는 일입니다.

이 레슨은 모듈 미션 ZIP의 queries/daily.sql을 작성합니다. 시작 ZIP에는 앞 모듈의 검증된 계약 파일과 두 원본, 기존 계약 테스트가 들어 있습니다. 새 pipeline.py는 SQLite 원본 테이블을 만들고 전체 열 완전 일치 중복을 제거한 뷰를 제공합니다. 학습자는 제공된 로더와 테스트를 유지하고 집계 SQL의 잘못된 함수를 고칩니다. Python 문법 전체를 익히기 전에도 SQL과 기대 표를 연결할 수 있게 실행 틀을 제공합니다.

세 단계의 행 수를 추적합니다

traffic_raw는 원본7행, 비결측6행입니다. traffic_unique는 전체 열 완전 일치 중복 제거 후6행, 비결측5행입니다. daily.csv는 지역·날짜별6행이며 유효관측 합은5입니다. 미관측 A 9월3일 그룹을 남기므로 일별 행 수는 비결측 수와 다릅니다. 앞 모듈 audit.csv의 unique5는 비결측을 고른 뒤 중복을 제거한 수입니다. 지금 뷰의6과 모순이 아니라 결측 유지 여부가 다른 단계입니다.

원본은 그대로 두고 뷰에서 중복 정책을 표현합니다. SELECT DISTINCT region,date,vehicle_count는 세 열 전체가 같은 행을 한 번만 반환합니다. 날짜와 지역이 같은데 수량이 다르면 두 행이 남습니다. 이를 합산하면 하나의 일별 관측을 두 번 세게 될 수 있어 제공 코드가 그룹 건수로 충돌을 확인하고 KEY 오류를 냅니다. 키를 원본 테이블의 기본키로 강제하여 반복 행을 조용히 버리지 않습니다.

기준 표를 먼저 씁니다

정제 기준 표는 A 9월1일100/1, 2일80/1, 3일NULL/0, B 1일120/1, 2일0/1, 3일90/1입니다. 슬래시 뒤는 유효관측 수입니다. 합계는100+80+120+0+90=390, 유효관측 수는5입니다. CSV에서는 NULL을 빈 문자열로 내보내므로 A3일은 두 쉼표가 연속으로 나옵니다. B2일의0은 문자0으로 기록합니다. 빈칸과0을 스프레드시트에서 같은 셀로 바꾸지 않습니다.

queries/daily.sql은 region,date,total_vehicles,valid_observations 순서로 열을 냅니다. A/B와 9월1~3일을 필터링하고 region,date로 묶고 같은 순서로 정렬합니다. pipeline.py는 쿼리 결과 헤더를 읽어 daily.csv를 생성합니다. 날짜와 지역의 출력 순서가 반대이거나 별칭이 빠지면 숫자가 맞아도 인계 규격이 다릅니다. 테스트는 행 값뿐 아니라 헤더와 결측 CSV 표현도 비교합니다.

테스트는 반례를 포함합니다

전체 표본 하나만 통과하면 종료일이나0 처리 오류가 숨을 수 있습니다. 새 테스트는 시작일과 종료일, 바로 앞뒤 날짜, 대상 밖C, NULL과0, 빈 표, 세 번 반복된 같은 행, 같은 키의 값 충돌을 각각 입력으로 둡니다. 빈 입력에서는 헤더만 있는 파일이 자연스러운 결과이며 없는 날짜를0으로 생성하지 않습니다. 실제 달력 기준 누락 행 생성은 별도 요구가 있어야 합니다.

starter는 앞 모듈 계약 검사를 통과하지만 수량 대신 COUNT(*)를 합계로 내므로 새 테스트 일부가 실패합니다. AssertionError의 첫 차이를 보고 예를 들어 기대100과 실제1이 합계 열에서 다른지 확인합니다. COUNT(*)를 무작정 제거하기보다 수량 합계와 관측 건수의 역할을 구분해 SUM(vehicle_count)와 COUNT(vehicle_count)를 각각 사용합니다. 모범 답안을 읽기 전에 한 그룹을 손으로 계산해 원인을 설명합니다.

명령과 결과를 함께 인계합니다

ZIP 루트에서 python3 -m unittest discover -s tests -v를 실행합니다. 계약10개와 SQL 확장9개가 통과하면 구조·경계·대조 검증을 마칩니다. 이어 python3 pipeline.py를 실행하면 원본행7, 원본합계470, 정제합계390, 일별행6이 한 줄에 나옵니다. 같은 명령을 두 번 실행해 daily.csv 바이트가 같고 raw의 바이트가 보존되는지 테스트로 확인합니다. 중간 DB는 메모리 연결이며 상주 서버를 실행하지 않습니다.

FileNotFoundError가 나면 ZIP을 푼 실제 루트와 queries/daily.sql 위치를 확인합니다. OperationalError의 no such table은 traffic_unique 이름과 제공 로더 실행 여부를 확인합니다. ValueError: KEY는 잘못된 SQL 문법이 아니라 충돌한 입력이 있다는 뜻입니다. HASH는 앞 모듈 출처 해시와 원본이 달라졌다는 뜻이며 새 해시로 덮어 문제를 숨기지 않습니다. 각 오류의 책임 단계부터 확인하면 수정 범위를 좁힐 수 있습니다.

다음 담당자에게 raw·계약·SQL·실행 코드·daily.csv를 함께 넘깁니다. 날씨와 결합할 때 지역·날짜 키와 결측0 구분을 유지해야 한다는 메모를 남깁니다. 이 단계의390은 합성 교통 표의 정제 합계이며 비 오는 날의 합계가 아닙니다. 날씨 자료와 결합하고 최종 비교 대상이 정해지기 전에는 질문 계약의 결론을 쓰지 않습니다. 자동 검증 결과와 사람이 판단할 한계를 함께 기록합니다.

대조 기록을 재현 가능한 증거로 남깁니다

차이 80을 발견했다면 “중복 때문”이라는 설명에서 멈추지 않습니다. 원본에서 A, 2026-09-02, 80이 두 번 나타나는지 확인하고 뷰에서는 한 번인지 확인합니다. 그 다음 같은 지역·기간의 합계 차이가 정확히 80인지 대조합니다. 행 단위 증거와 합계 차이가 이어져야 설명을 검증할 수 있습니다. 기간 밖 행이 추가되었다면 전체 파일 행 수는 늘어도 대상 기간 합계는 그대로일 수 있으므로 두 지표를 분리합니다.

인계 메모에는 사용한 SQL 파일 경로, 실행 명령, 대상 기간, 중복 정책, 결측 표현을 적습니다. 특히 SQL 단계의 NULL 표기와 CSV 단계의 빈 필드는 같은 미관측을 서로 다른 형식으로 보여 줍니다. 화면의 공백 구분 출력만 CSV 파일로 복사하면 헤더와 구분자가 달라지므로 pipeline.py가 만든 파일을 전달합니다. 다음 담당자가 같은 입력으로 다시 실행했을 때 값과 열 순서가 일치하는지 확인할 수 있어야 합니다.

차이 표로 원인을 좁힙니다

합계만 맞으면 서로 다른 날짜의 오류가 상쇄될 수 있습니다. A의 9월 1일을 90으로 낮추고 9월 2일을 90으로 높이면 두 날짜 합은 여전히 180입니다. 따라서 전체 390 대조와 함께 지역·날짜별 기대값도 비교합니다. 기대 표와 실제 표를 키로 연결해 빠진 키, 추가된 키, 같은 키의 수량 차이를 나누어 기록합니다. 정렬 차이를 값 차이로 오해하지 않도록 두 표의 비교 순서도 맞춥니다.

중복 제거의 영향을 확인할 때는 원본 A의 9월 2일 두 행이 정제 뷰의 한 행으로 바뀌는 지점에 집중합니다. 원본 합계에서 정제 합계를 뺀 80은 반복된 한 행의 수량과 일치합니다. 이 차이는 기대된 처리 결과이지만 어떤 원본에서나 차이가 80이라고 고정하면 안 됩니다. 새로운 표본에서는 중복 행의 수량과 개수가 달라질 수 있으므로 각 테스트의 입력에서 기준을 다시 계산합니다.

출력 파일과 진단 로그를 나눕니다

pipeline.py의 한 줄 요약은 실행 상태를 빠르게 보는 진단 출력입니다. raw_rows는 적재한 원본 전체 행 수이고 raw_total과 unique_total은 지정 지역·기간의 합계입니다. 입력에 대상 밖 자료가 있으면 행 수와 합계의 범위가 다를 수 있습니다. 따라서 요약 줄만으로 필터의 정확성을 증명하지 않고 daily.csv의 각 행과 테스트 반례를 함께 확인합니다. 실제 전달 산출물은 헤더와 일별 값이 담긴 CSV입니다.

재실행 검증에서는 첫 실행의 파일 바이트를 보관한 뒤 같은 입력과 SQL로 다시 실행해 비교합니다. 원본 보존은 raw 파일의 실행 전후 바이트를 비교하고, 결과 재현성은 daily.csv의 두 실행 결과를 비교합니다. 둘은 서로 다른 검사입니다. 결과가 같더라도 원본이 바뀌었으면 출처 계약을 어긴 것이고, 원본이 같더라도 정렬이 달라지면 파일 대조가 불안정합니다. 오류를 고친 뒤 두 조건을 함께 확인하고 실제 사용한 명령을 인계합니다.

따라하기

중복 제거 전후 계산

앞 audit의 unique5는 비결측 이후의 수입니다. 뷰는 NULL도 유지합니다.

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

실행 결과

470 7 6
390 6 5

미션 SQL의 기준 표 실행

아래를 queries/daily.sql로 저장합니다. 실제 ZIP에는 traffic_unique가 로더에 의해 준비됩니다.

SELECT region, date, SUM(vehicle_count) AS total_vehicles,
       COUNT(vehicle_count) AS valid_observations
FROM traffic_unique
WHERE region IN ('A', 'B')
  AND date >= '2026-09-01' AND date <= '2026-09-03'
GROUP BY region, date
ORDER BY region, date;

실행 결과

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

로컬 테스트와 CSV 생성

data-sql-summary 시작 ZIP의 README가 있는 폴더에서 실행합니다. 첫 명령의 starter 실패를 읽고 queries/daily.sql의 합계 함수를 수정합니다. 통과 후 두 번째 명령으로 CSV를 만듭니다. 아래 출력은 모범 답안에서 실행한 pipeline.py의 출력입니다.

python3 -m unittest discover -s tests -v
python3 pipeline.py

실행 결과

raw_rows=7 raw_total=470 unique_total=390 daily_rows=6

생성 결과와 다음 단계 인계

모범 답안 루트에서 아래 명령을 실행해 실제 생성 결과를 확인합니다. daily.csv의 빈칸과0을 구분하고 원본470에서 반복80을 뺀390을 대조합니다. 시작본도 수정 후 같은 출력이어야 합니다.

python3 pipeline.py

실행 결과

raw_rows=7 raw_total=470 unique_total=390 daily_rows=6

확인 문제

실습

앞 미션 solution을 포함한 시작 ZIP에서 queries/daily.sql을 고칩니다. traffic_unique를 지역·날짜로 집계하고 total_vehicles, valid_observations를 반환합니다. A/B·9월1~3일 양 끝 포함, 지역·날짜 정렬, NULL 유지입니다. 테스트 통과 후 python3 pipeline.py로 daily.csv를 생성합니다. README의 원본470·정제390 대조를 설명하고 다음 모듈에 계약·원본·SQL·CSV를 인계합니다.

시작 코드·테스트 내려받기

실행 명령

python3 -m unittest discover -s tests -v

기대 결과

계약10개와 SQL 확장9개: Ran 19 tests, OK. starter는 계약 통과·합계 검사 일부 실패.

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 원본 합계와 보고서 합계가 다를 때 중복·결측·기간 정책을 어떻게 대조하나요?
  • 전체 합계는 같지만 일별 값이 틀린 상황을 어떻게 검증하나요?