호라이즌앱스 로고
← 기술 노트

지점 엑셀을 다시 올리면 매출이 두 배? 중복 업로드를 막는 기준

임균영조회 —

4월분 판매 자료가 5월 12일에 한 번, 5월 20일에 또 한 번 다시 올라왔습니다. 6월분은 7월 23일에 다시 올라왔습니다.

급식 납품업체의 매출 시스템에서 찾아낸 재업로드 흔적입니다.

본사와 지점 열 곳(2026년 8월 기준)이 판매 엑셀을 올려 매출을 합치는 시스템입니다. 지점 담당자는 올렸는지 헷갈리거나 숫자가 이상해 보이면 다시 올리기도 합니다.

시스템은 그렇게 다시 들어온 행을 그대로 받아 줬습니다.

같은 파일이 두 번 들어오면

2026년 8월에 이 시스템을 감사하면서 판매 기록을 훑었습니다. 지점·날짜·거래처·금액까지 똑같은 행이 9,999개 있었습니다.

전체 판매 행의 1% 남짓입니다. 작아 보여도 중복은 고르게 퍼지지 않고 사고가 난 달에 몰립니다.

정리 전후를 비교하니 4월 매출은 실제보다 약 13%, 6월은 약 2% 부풀어 있었습니다. 2026년 8월 17일 정리 시점의 월 매출 기준입니다.

지점 순위와 목표 달성률도 이 숫자로 계산됩니다. 4월에 갑자기 잘한 지점이 사실은 파일을 두 번 올린 지점일 수도 있었습니다.

처음 넣은 중복 방지

처음 넣은 장치는 단순했습니다. 행마다 지점·날짜·거래처에 카테고리·금액·수량까지 묶어 비교 키를 만들었습니다.

같은 키가 이미 있으면 건너뜁니다. 같은 파일을 그대로 다시 올리는 경우는 이걸로 막힙니다.

패치노트에도 재업로드해도 안전하다고 적었습니다. 그런데 이 시스템에는 올린 뒤 금액이나 카테고리를 고치는 기능이 있습니다.

어떤 행의 금액을 화면에서 정정했다고 해 보겠습니다. DB에는 정정 후 값이 남고, 원본 파일에는 정정 전 값이 그대로 있습니다.

원본 파일을 다시 올리면 정정 전 값으로 만든 키는 DB에 없습니다. 시스템은 이걸 새 행으로 보고 넣습니다.

같은 주문이 두 번 잡히는 겁니다. 패치노트가 안전하다고 적은 바로 그 흐름에서 이중 계상이 날 수 있었습니다.

이 구멍은 같은 날 다시 돌린 감사에서 나왔습니다. 발견마다 반박 검증을 붙여 확인한 결과였습니다.

반대쪽 구멍, 합쳐진 두 건

같은 장치에 반대 방향 결함도 있었습니다. 파일 안의 행끼리도 같은 키를 걸러 냈던 것입니다.

급식 납품에서는 같은 거래처에 같은 날 같은 금액으로 두 번 납품하는 일이 생깁니다. 둘 다 정당한 주문입니다.

장치는 이 둘을 한 건으로 합쳤습니다. 매출은 줄었는데 사라진 한 건은 실패로 잡히지 않고 갱신 건수로 들어갔습니다.

묶음 건수로 판정하기

감사에서 처음 나온 제안은 키를 줄이자는 안이었습니다. 금액·수량을 빼고 지점·날짜·거래처만 보자는 것입니다.

이렇게 하면 정정 문제는 풀립니다. 대신 같은 날 같은 거래처의 두 번째 정당한 주문을 건너뜁니다.

그래서 판정 기준 자체를 바꿨습니다. (지점·날짜·거래처) 묶음마다 이미 몇 건 있는지를 먼저 세고, 파일에 그보다 많을 때 초과분만 넣습니다.

첫 파일이 막대로 쌓인 뒤 한 행(노란 칸)의 금액을 화면에서 고치고 원본을 다시 올립니다. 값으로 비교하는 쪽은 정정 전 값이 새 행으로 한 칸 더 얹히고, 묶음 건수를 세는 쪽은 기존 2건과 파일 2건이 같아 넣을 것이 없습니다.

DB에 그 묶음이 2건 있고 파일에도 2건이면 넣을 것이 없습니다. 금액을 고쳤어도 건수는 2건 그대로라 이중 계상이 생기지 않습니다.

파일에 3건이 있으면 1건만 들어갑니다. 같은 날 두 번 납품한 기록도 처음 올릴 때 둘 다 들어갑니다.

상황값으로 비교건수로 비교
같은 파일 재업로드막힘막힘
정정 후 원본 재업로드정정 전 값이 새 행으로 들어감건수가 같아 추가 0건
같은 날 같은 거래처 두 건한 건으로 합쳐짐둘 다 들어감

건수 대조에도 약점은 있습니다. 같은 묶음에서 한 건을 지우고 다른 한 건을 넣은 파일이라면 건수가 같아 새 행이 들어가지 않습니다.

그런 수정은 화면에서 직접 고치는 편이 맞습니다. 업로드 화면 안내문에 이 구분을 적어 두시길 권합니다.

유니크 제약을 걸 수 없었던 이유

데이터베이스에서 같은 키를 아예 못 들어오게 막는 유니크 제약이 정석입니다. 처음엔 그걸 걸려고 했습니다.

제약 생성이 실패했습니다. 앞서 본 9,999개 중복이 이미 들어 있었기 때문입니다.

기존 중복을 지우면 지난달 매출 숫자가 바뀝니다. 그래서 앞으로의 중복 방지는 애플리케이션 층에서 맡고, 기존 정리는 별도 작업으로 떼어 냈습니다.

정리할 때는 중복 쌍이 들어온 시각 차이를 봤습니다. 10초 안에 들어온 쌍은 한 파일 안의 행이고, 10분 넘게 벌어진 쌍은 재업로드입니다.

10분 넘게 벌어진 9,658행만 휴지통으로 옮겼습니다. 30일 안에는 되돌릴 수 있습니다.

같은 초에 들어온 244묶음은 정상 주문일 수 있어 그대로 뒀습니다. 앞에서 말한 같은 날 두 번 납품이 바로 이런 모양입니다.

지금은 매일 아침 8시 정합성 리포트가 재업로드로 보이는 중복을 따로 셉니다.

실패한 파일이 남기는 것

같은 감사에서 순서 문제가 하나 더 나왔습니다. 처음 보는 거래처를 만드는 단계가 날짜·금액 검증보다 앞에 있었습니다.

그래서 모든 행이 실패한 파일도 거래처는 만들어졌습니다. 첫 거래일까지 채워진 유령 거래처입니다.

이 시스템은 첫 거래일로 신규 거래처를 셉니다. 유령이 생기면 신규 거래처 숫자가 함께 틀어집니다.

처리 순서를 두 단계로 다시 짰습니다. 먼저 모든 행을 읽고 검증합니다.

그다음 유효한 행이 하나라도 있는 거래처만 만들고 판매를 넣습니다. 전 행이 실패한 파일은 아무것도 남기지 않습니다.

파일이 통째로 거부되는 경우도 따로 봅니다. 필수 칸이 빠져 거부된 파일은 실패 0건으로 기록돼 리포트에 잡히지 않았습니다.

조회해 보니 2월 말부터 10월 중순까지 48건이 쌓여 있었습니다. 이제는 거부된 날 같은 계정이 같은 종류 파일을 한 번도 반영하지 않았으면 경보가 뜹니다.

거부 전후 순서는 따지지 않습니다. 정상 파일을 거부 직전에 올린 지점도 있어서, 거부 뒤 재업로드만 보도록 짜면 오탐이 납니다.

본사가 대신 올릴 때

지점 판정에도 구멍이 있었습니다. 업로드 계정에 지점이 정해져 있으면 파일에 적힌 지점명을 통째로 무시했습니다.

지점 계정이라면 맞는 동작입니다. 다른 지점 이름을 적어 남의 매출에 끼워 넣으면 안 되기 때문입니다.

본사는 사정이 다릅니다. 본사가 여러 지점이 섞인 파일을 올리면 전체가 한 지점 매출로 들어갔습니다.

지금은 계정 종류로 나눕니다. 지점 계정은 자기 지점으로 고정하고, 본사는 파일의 지점명을 먼저 보고 비어 있을 때만 기본 지점을 씁니다.

엑셀로 합치고 있다면

지점이나 대리점에서 엑셀을 받아 본사가 합치는 회사라면 비슷한 자리에서 막힙니다. 합치는 쪽이 엑셀 매크로든 시스템이든 같습니다.

엑셀로 합치고 있다면 지점·날짜·거래처 묶음별 건수를 피벗으로 세어 보세요. 특정 날짜만 건수가 두 배라면 재업로드를 의심할 만합니다.

시스템으로 옮길 생각이라면 지점별 매출을 한 화면에 모은 모습을 식사 운영 관제 데모에서 먼저 보셔도 됩니다. 가상 브랜드로 만든 시연 화면입니다.

엑셀을 계속 쓸지 시스템으로 갈지가 먼저라면 업무 시스템 도입 교육에서 그 판단표를 같이 만듭니다.

업로드 판정까지 맞춘 매출 시스템이 필요하시면 대시보드 제작 상담에 지금 쓰는 엑셀 양식을 함께 보내 주시면 됩니다. 양식을 보면 어떤 묶음으로 건수를 셀지부터 정할 수 있습니다.

맡기기 전에 시켜 볼 시험

이 시스템도 배포 당일 확인은 빌드와 산출물 검사, 접속 확인까지였습니다. 정정 후 재업로드를 실제 화면에서 끝까지 돌려 본 것은 아니었습니다.

그래서 업로드 기능을 맡길 때는 아래 시험을 개발사에 직접 요구하시길 권합니다. 시험 파일은 지난달 파일 한 장을 복사해 만들면 됩니다.

시험통과 기준
같은 파일을 그대로 두 번 올리기두 번째는 추가 0건, 매출 합계 그대로
화면에서 한 행 금액을 고친 뒤 원본 파일 다시 올리기추가 0건, 고친 금액 유지
같은 날 같은 거래처 같은 금액 두 줄 넣어 올리기두 건 모두 반영
날짜를 전부 틀리게 만든 파일 올리기실패 표시, 새 거래처 0곳
본사 계정으로 두 지점이 섞인 파일 올리기지점별로 나뉘어 반영

결과를 볼 때는 완료 메시지보다 건수와 합계를 먼저 확인하세요. 합쳐진 두 건 중 하나가 갱신 건수로 들어갔던 것처럼, 완료 메시지만 보면 사라진 행을 놓칩니다.

이어서 읽기