기본키와 관측 단위
65분 안팎
학습 목표
지역·날짜 키가 유일한지 조회하고 중복 키 목록을 반환합니다.
개념
한 행의 의미부터 정합니다
JOIN을 쓰기 전에 한 행이 무엇을 나타내는지 설명합니다. daily의 한 행은 지역 한 곳의 하루 교통량입니다. region만으로는 여러 날짜가 섞이고 date만으로는 여러 지역이 섞입니다. 두 열의 조합이 행을 식별하므로 복합키 후보가 됩니다. 숫자가 같다는 사실은 같은 행이라는 뜻이 아닙니다. A와 B가 같은 날 100대를 기록해도 서로 다른 관측입니다.
기본키는 저장할 행을 식별하도록 선택한 키입니다. 조인 키는 두 표를 연결할 때 비교하는 열입니다. 둘은 같을 수도 있지만 항상 같지는 않습니다. 날씨 원본의 기본키 후보는 weather_region,date,station이고 지역별 교통에 붙이는 조인 키는 weather_region,date입니다. 원본에서 조인 키가 반복되는 것은 관측소 여러 곳이 있다는 뜻일 수 있습니다. 합법적인 반복과 중복 적재를 구분하지 않으면 정상 관측소를 삭제합니다.
유일성은 조회와 제약으로 확인합니다
SELECT region,date,COUNT(*) FROM daily GROUP BY region,date HAVING COUNT(*) > 1은 같은 키가 두 번 이상 나타난 그룹만 반환합니다. WHERE는 원본 행을 고르고 HAVING은 집계한 그룹을 고릅니다. COUNT(total_vehicles)를 쓰면 결측값이 빠지므로 키 중복을 놓칠 수 있습니다. 키 중복 검사에는 COUNT(*)를 사용하고 키 NULL 검사는 별도 IS NULL 조건으로 실행합니다.
결과가 비어 있으면 이번 입력에서 중복 그룹을 찾지 못했다는 뜻입니다. 앞으로도 유일하다는 보장은 아니므로 적재 표에는 PRIMARY KEY(region,date)를 선언합니다. 이번 SQLite 표는 두 키 열에 NOT NULL도 명시합니다. 제약을 붙이기 전 중복을 조회하면 위반 행의 위치를 설명하기 쉽습니다. UNIQUE constraint failed가 나오면 오류에 표시된 표와 열의 기존 행을 조회합니다.
중복을 임의로 없애지 않습니다
같은 키에 80대와 90대가 있으면 DISTINCT로는 해결되지 않습니다. 두 값이 수정 이력인지 센서별 관측인지 원본 오류인지 확인해야 합니다. 같은 키의 모든 행을 SUM하면 하루 교통량을 두 번 더할 수 있고 MAX로 고르면 근거 없는 값을 선택합니다. 앞 단계는 완전 일치 중복만 제거하고 충돌은 거부했습니다. 이번 daily는 그 정책의 결과를 이어받습니다.
대응표는 region마다 weather_region 하나를 갖습니다. A가 WA와 WB에 동시에 대응되면 어느 날씨를 붙여야 하는지 불명확하므로 거부합니다. A와 B가 모두 WA를 참조하는 것은 다른 경우입니다. 하나의 날씨 지역을 여러 교통 지역이 사용할 수 있지만 대표성의 한계를 설명해야 합니다. 유일성을 검사할 방향은 대응표의 region이며 두 코드 모두를 묶으면 모호한 대응을 놓칩니다.
프로젝트의 출발점과 입력 계약
앞 모듈은 원본 교통 7행의 완전 일치 중복을 점검하고 지역·날짜별 daily.csv 6행을 만들었습니다. 합계는 390대이며 A의 9월 3일 통행량은 빈칸입니다. B의 9월 2일은 0대이므로 관측 누락과 다르게 취급합니다. 이번 단계는 이 파일을 입력으로 사용합니다. 원본 470대와 비교해 결합 오류라고 판단하면 앞 단계 중복 제거를 되돌리는 실수를 할 수 있습니다. 결합 직전 파일의 행 수와 합계를 기준선으로 기록합니다.
교통 코드 A/B와 날씨 코드 WA/WB는 작성팀이 만든 합성 교육 표본입니다. 실제 지역 이름이나 관측소의 공개 자료라고 해석하지 않습니다. 날씨의 rain_mm은 관측소별 하루 누적 강수량을 뜻하며 station은 관측소 식별자입니다. 날짜는 유효한 YYYY-MM-DD 문자열입니다. 동일 지역이라도 시간대와 하루 경계가 다르면 결합 의미가 달라지므로 실제 자료를 받을 때는 출처 문서에서 집계 기간을 함께 확인합니다.
실행과 결과를 재현합니다
따라하기의 SQL은 각 단계에서 독립된 메모리 SQLite 연결로 실행됩니다. 초기화 SQL이 표를 준비한 다음 조회문을 실행하므로 앞 단계의 변경에 의존하지 않습니다. 브라우저 실습 tests.input은 초기화 SQL이며 제출하는 코드는 조회문입니다. 출력은 헤더 없이 열 사이 공백, 행 사이 줄바꿈으로 비교합니다. NULL은 화면에서 NULL로 보이고 로컬 CSV에서는 빈칸으로 저장됩니다. ORDER BY로 지역과 날짜를 고정해 파일 차이를 읽기 쉽게 합니다.
로컬에서는 Python 3의 표준 라이브러리 sqlite3를 사용합니다. 별도 데이터베이스 서버나 패키지 설치가 필요하지 않습니다. ZIP을 푼 폴더에서 python3 -m unittest discover -s tests -v를 실행합니다. 실패 목록의 test 이름은 깨진 계약을 알려 줍니다. expected와 actual을 비교해 값 문제인지 행 수 문제인지 먼저 구분합니다. 전체 원본을 수정해 숫자를 맞추지 말고 조회 조건과 키를 작은 fixture에서 점검합니다.
동료에게 넘길 근거를 남깁니다
교통량과 강수량을 함께 보았다고 비가 통행량 변화를 일으켰다는 결론을 바로 내리지 않습니다. 기간이 짧고 가상 지역만 포함된 표본입니다. 이번 단계의 책임은 결합 과정에서 교통 행이 늘거나 사라지지 않았는지 증명하는 것입니다. 결측값을 삭제할지 채울지와 비교 지표를 어떻게 정의할지는 후속 모듈에서 다룹니다. 지금은 누락을 보존하여 다음 작성자가 선택의 영향을 계산할 수 있게 합니다.
작업을 마치면 입력 파일, SQL, 출력 파일, 감사 기록을 함께 남깁니다. 입력의 키와 출력의 키가 무엇인지 한 문장으로 적고 미매칭 상태별 건수를 붙입니다. 행 수와 합계가 같아도 두 행이 바뀌거나 값이 우연히 상쇄될 수 있으므로 키 유일성과 작은 표본의 개별 값도 검사합니다. 테스트 통과는 이 계약에 대한 근거이며 모든 실제 자료의 품질을 보장하는 증명은 아닙니다.
따라하기
키별 행 수 보기
아래 준비 표에서 조회를 실행하고 결과를 비교합니다.
SELECT region,date,COUNT(*) FROM daily GROUP BY region,date ORDER BY region,date;실행 결과
A 2026-09-01 1 A 2026-09-02 1 B 2026-09-01 1 B 2026-09-02 1
중복을 주입해 찾기
아래 준비 표에서 조회를 실행하고 결과를 비교합니다.
SELECT region,date,COUNT(*) AS key_rows FROM daily GROUP BY region,date HAVING COUNT(*)>1 ORDER BY region,date;실행 결과
A 2026-09-01 2
결측이어도 행을 세기
아래 준비 표에서 조회를 실행하고 결과를 비교합니다.
SELECT region,date,COUNT(*) AS key_rows FROM daily GROUP BY region,date HAVING COUNT(*)>1 ORDER BY region,date;실행 결과
A 2026-09-01 2
확인 문제
실습
daily(region,date,total_vehicles,valid_observations)에서 지역·날짜 중복 키를 region,date,key_rows 순서로 반환합니다. COUNT(*)가 2 이상인 그룹만, 지역·날짜 오름차순입니다. 빈 표와 유일 키 표는 빈 결과입니다.
모범 답안
SELECT region,date,COUNT(*) AS key_rows FROM daily GROUP BY region,date HAVING COUNT(*)>1 ORDER BY region,date;
더 읽기
면접 질문
- 기본키와 조인 키가 다를 수 있는 예를 설명해 주세요.
- 지역·날짜 키의 중복을 COUNT(*)로 검사하는 이유는 무엇인가요?