품목 하나에 한 행이라는 가정부터 확인하기
카운터와 창고에서 같은 종이컵을 세었다면 품목 코드가 두 번 나오는 것이 정상입니다. 어제와 오늘의 기록을 붙였을 때도 마찬가지입니다. 코드가 반복됐다는 이유로 한 행을 지우면 위치별 수량이나 날짜별 변화를 잃습니다. 여기서는 하루에 한 번, 구역별 품목 잔량을 기록하는 표를 가정하고 날짜·구역·품목의 조합으로 중복 후보를 찾습니다.
이 표의 한 행은 한 번의 입출고 거래가 아니라 특정 날짜·구역·품목의 마감 잔량입니다. 같은 날 오전과 오후를 모두 기록해야 한다면 현재 조합은 충분하지 않습니다. 기준 시각이나 회차를 키에 추가해야 합니다. 중복 판정 전에 한 행이 나타내는 사건을 한 문장으로 정하는 것이 출발점입니다. 아래 숫자와 구역은 설계 설명을 위한 가상값입니다.
키를 정하기 전에 같은 표에 넣지 않을 기록 고르기
마감 실사와 입고 건수를 한 표에 넣지 않습니다. 실사는 상태를, 입고는 이동 사건을 표현합니다. 품목·구역·날짜가 같아도 두 기록의 의미는 다릅니다. 입고표에는 별도 거래 ID를, 잔량표에는 실사 기준일을 두고 나중에 연결합니다. 키를 더 길게 만들어 다른 사건까지 억지로 구분하면 표의 수량 합계가 무엇인지 설명하기 어려워집니다.
날짜는 파일 생성일 대신 실제 잔량을 센 기준일로 정합니다. 구역은 카운터 C-01과 창고 S-01처럼 승인된 코드 사전에서 고릅니다. 품목 코드 P-001의 앞자리나 하이픈을 임의로 지우지 않습니다. 사람이 읽는 품목명은 보조 열이며 키에는 넣지 않는 예로 설계합니다. 이름이 조금 수정됐다는 이유로 같은 품목이 새 기록으로 나뉘지 않도록 하기 위해서입니다.
내보낸 파일의 행 번호를 키로 쓰면 정렬할 때 번호가 바뀝니다. 그래서 원본 행 ID R-01은 추적용으로 보존하고 업무 키는 따로 만듭니다. 데이터베이스의 복합 고유키 개념을 참고하지만 이 CSV가 중복 저장을 차단하는 프로그램은 아닙니다. 표시된 후보는 담당자가 원본을 대조해 처리해야 합니다.

8행을 읽고 중복·정상·보류를 나누기
R-01과 R-02는 날짜 2026-10-12, 구역 C-01, 품목 P-001이 모두 같으므로 중복 후보입니다. 수량도 둘 다 20이지만 이것만으로 복사 실수라고 확정하지 않습니다. 어느 행이 최초 기록인지, 재계수 결과인지 원본 위치와 작성 시각을 확인합니다. 두 행을 더해 40으로 합치면 잔량이 두 배가 됩니다.
R-03은 같은 품목이지만 창고 S-01의 10개이므로 다른 키입니다. R-04는 다음 날의 15개라 다른 키입니다. R-06은 날짜가 빠져 있어 고유 여부부터 판단할 수 없습니다. R-07의 구역 C|01은 이 예제의 허용 코드 형식에 맞지 않습니다. 빈 날짜나 구분자 문자를 정상 값처럼 연결해 키를 만들지 않는 점이 중요합니다.
| 원본 행 ID | 기준일 | 구역 | 품목 코드 | 잔량(개) | 조합 키 | 동일 키 수 | 판정 |
|---|---|---|---|---|---|---|---|
| R-01 | 2026-10-12 | C-01 | P-001 | 20 | 2026-10-12|C-01|P-001 | 2 | 중복 후보 |
| R-02 | 2026-10-12 | C-01 | P-001 | 20 | 2026-10-12|C-01|P-001 | 2 | 중복 후보 |
| R-03 | 2026-10-12 | S-01 | P-001 | 10 | 2026-10-12|S-01|P-001 | 1 | 단일 |
| R-04 | 2026-10-13 | C-01 | P-001 | 15 | 2026-10-13|C-01|P-001 | 1 | 단일 |
| R-05 | 2026-10-12 | C-01 | P-002 | 8 | 2026-10-12|C-01|P-002 | 1 | 단일 |
| R-06 | C-01 | P-002 | 8 | 기준일 확인 | |||
| R-07 | 2026-10-12 | C|01 | P-002 | 6 | 구역 형식 확인 | ||
| R-08 | 2026-10-12 | S-01 | P-003 | 0 | 2026-10-12|S-01|P-003 | 1 | 단일 |
이 글의 기록 양식 내려받기
품목 코드가 같아도 중복이 아닐 때: 날짜·구역을 묶은 키 점검 — CSV 양식
UTF-8 BOM이 있는 기록용 CSV입니다. 계산식·드롭다운·서식은 들어 있지 않습니다. 첫 행 8열, 설명용 데이터 8레코드가 있습니다.
설명용 8행입니다. 동일 키 수와 판정은 본문 규칙으로 채운 예시값이며 CSV에 자동 수식은 없습니다. 원본 행 ID는 삭제하지 않고 키 변경 시 이전 값을 함께 보관합니다.
CSV 구조와 식별자 점검 도구에서 사본의 열 수와 문자열을 확인할 수 있습니다.
세 열 그대로 세는 방법과 문자열 키 만드는 방법
스프레드시트에서 원본 ID가 A열, 날짜 B열, 구역 C열, 품목 D열, 수량 E열이라고 가정합니다. 유효한 B2:D2만을 대상으로 COUNTIFS($B$2:$B$9,B2,$C$2:$C$9,C2,$D$2:$D$9,D2)를 적용하면 R-01의 조합은 2개가 됩니다. 범위의 시작과 끝은 모두 같게 맞추고 새 행을 추가하면 세 범위를 함께 늘립니다.
이 수식은 빈 키, 대소문자, 코드에 포함된 와일드카드를 자동으로 해결하지 않습니다. 예제 코드는 대문자·숫자·하이픈으로 제한했습니다. 자유 문자가 들어가는 식별자는 COUNTIFS의 조건 해석과 실제 문자 일치가 같은지 따로 검토해야 합니다. 특히 *와 ?가 코드의 일부라면 문자 그대로 세는 별도 비교 방법을 마련합니다.
외부 전달용으로 조합 문자열이 필요하면 유효한 날짜·구역·코드만 2026-10-12|C-01|P-001로 연결합니다. 세 열에 | 문자를 허용하지 않는 규칙을 먼저 정했기 때문에 경계가 분명합니다. AB와 C를 붙인 ABC, A와 BC를 붙인 ABC가 같은 값이 되는 단순 연결은 사용하지 않습니다. 이 제한이 없는 시스템에서는 길이 표시나 별도 직렬화 방식을 검토합니다.
앞뒤 공백을 고치는 일도 변경으로 남기기
P-001 뒤에 공백이 붙은 원문은 화면에서 같은 코드처럼 보일 수 있습니다. 원문 열과 정규화 열을 나누고, 공백 제거를 허용한다는 코드 사전 규칙이 있을 때만 변환합니다. 모든 문자열에 TRIM을 일괄 적용하면 공백을 실제 구분 요소로 쓰는 외부 코드가 바뀔 수 있습니다. 정규화 전후 값을 함께 남겨 거래처 문의 때 원문을 제시할 수 있게 합니다.
대소문자를 구분하지 않는 코드 정책이라면 승인된 대문자 형태로 바꿀 수 있습니다. 그러나 이 정책은 표 작성자의 편의로 정하지 않습니다. 공급자의 P-001과 p-001이 다른 품목이면 두 코드를 유지해야 합니다. 샘플처럼 허용 코드가 좁은 경우에는 일치하지 않는 입력을 보류하고 선택 목록에서 다시 고르게 하는 편이 명확합니다.
날짜도 화면 모양 대신 실제 기준일을 비교합니다. 10/12만 적힌 행은 연도가 빠져 있어 다른 해와 충돌할 수 있습니다. 원본에 연도가 없다면 파일 저장일을 임의로 붙이지 않고 작성자에게 확인합니다. 같은 키 판정은 필수 구성 값이 모두 확정된 뒤 수행합니다.
후보 행을 삭제하지 않고 닫는 순서
첫째, 원본 파일을 읽기 전용 사본으로 보관하고 확인용 표에 행 ID를 넣습니다. 둘째, 필수 값과 코드 형식을 검사해 보류 행을 분리합니다. 셋째, 유효한 키의 개수를 세어 2개 이상인 조합만 후보 목록에 옮깁니다. 넷째, 후보마다 원본 위치, 작성 시각, 수정 사유를 대조합니다. 삭제·유지·재계수 중 무엇을 할지는 이 확인 후 정합니다.
예를 들어 R-02가 R-01의 복사였다는 확인을 받으면 R-02를 원본에서 지우기보다 제외 표시에 사유와 확인자를 남깁니다. 반대로 R-02가 재계수라면 같은 실사 사건의 수정본으로 연결하고 어느 값을 최종 잔량으로 채택했는지 기록합니다. 두 값을 평균하거나 작은 값을 선택하는 방법은 이 표의 규칙에 없습니다.
정리 후 집계는 채택 행만 대상으로 다시 계산합니다. 카운터 20개와 창고 10개를 합쳐 30개를 얻더라도 기준일이 같은지 먼저 확인합니다. 다음 날의 15개를 함께 더하면 날짜별 상태를 합친 다른 숫자가 됩니다. 고유키 검사가 합계의 의미까지 자동으로 보증하지는 않습니다.
경계 입력을 바꾸어 점검하기
R-01의 구역을 S-01로 바꾸면 R-03과 같은 키가 됩니다. 날짜를 2026-10-13으로 바꾸면 R-04와 같아집니다. 수량만 20에서 18로 바꿔도 키는 변하지 않으므로 여전히 중복 후보입니다. 이 세 번의 비교로 키에 포함한 열과 단순 속성 열을 구분할 수 있습니다.
모든 구성 값이 같으면서 메모만 다르면 메모 내용이 중복 해소 근거인지 확인합니다. “재고 정리”처럼 일반 설명은 별도 사건의 증거가 아닙니다. 실제로 오전·오후 실사 두 회차였다면 행을 삭제하는 대신 표의 사건 단위와 키를 재설계합니다. 새 규칙으로 과거 행을 다시 분류하기 전에는 예전 기준을 보관합니다.
규칙을 바꿀 때 되돌릴 기준
구역 통합으로 C-01과 C-02를 C로 합치면 과거에 다른 키였던 행이 충돌할 수 있습니다. 변경 사전, 적용일, 원문 코드, 새 코드, 충돌 수를 함께 저장하고 충돌이 해결되기 전까지 집계를 전환하지 않습니다. 새 코드만 남기면 어디에서 원래 수량이 나왔는지 되찾기 어렵습니다.
복합키는 기록의 정체성을 정하는 설계입니다. 이 글은 잔량표의 중복 후보를 찾는 범위에 한정하며 실제 물건의 동일성, 재고 소유권, 회계 마감이나 재계수 결과를 판정하지 않습니다. 결과물이 후보 목록에서 끝나지 않도록 마지막으로 각 후보의 채택 행·보류 이유·다음 확인자를 남깁니다.
원문에서 확인할 범위
- PostgreSQL 18: Unique constraints·Primary keys — 여러 열 조합의 고유성과 기본키의 빈값 배제를 설명하는 개념 참고. 이 작업표가 데이터베이스 제약을 자동 수행한다는 뜻은 아님.
- Google Sheets COUNTIFS — 복수 조건의 개수를 세는 문법과 조건 범위의 크기 일치.
- Google Sheets COUNTIF — 조건 개수, 대소문자 비구분과 와일드카드 동작. 코드 사전의 대소문자 정책은 별도 설계.