집계가 0으로 나온 원인은 쿼리가 아니라 분류체계 입력 칸이었습니다
프랜차이즈 ERP 에는 매입을 전년 같은 기간과 견주는 비교 화면이 있습니다. 어느 품목을 전보다 얼마나 더 들여왔는지를 중분류 단위로 묶어서 보여주는 화면입니다. 그 화면에서 축산 쪽 한 중분류의 수치가 두 곳에서 0 으로 나왔습니다. 전년에도 올해도 꾸준히 들어오던 품목이라, 0 이 나올 자리가 아니었습니다.
집계가 0 이면 집계부터 의심하게 됩니다. 그런데 이번 건의 원인은 쿼리 밖에 있었고, 더 정확히는 그 데이터를 고치라고 만들어 둔 입력 칸에 있었습니다.
0 은 집계가 틀렸다는 신호가 아니었습니다
쿼리는 맞았습니다. 분류 소속을 열어 보니, 그 중분류에 들어가야 할 품목 몇 개가 중분류가 아니라 대분류에 직접 붙어 있었습니다. 품목은 대분류 밑의 중분류에 붙는다는 것이 이 체계의 전제였고, 비교 화면은 그 전제를 믿고 중분류 단위로 묶었습니다. 전제가 깨진 품목은 묶는 기준에서 그대로 빠졌습니다.
빠진 품목이 하필 그 분류의 물량 대부분을 차지하는 쪽이었습니다. 그래서 숫자가 조금 작아지는 것이 아니라 0 이 됐습니다. 숫자가 틀린 것이 아니라 묶는 기준에서 빠진 것이고, 집계가 0 으로 나올 때는 분류 체계가 틀렸다는 신호인 경우가 생각보다 많습니다.
여기서 끝낼 수도 있었습니다. 잘못 붙은 품목을 제 중분류로 옮기면 비교 화면 숫자는 그날 맞습니다. 그래서 한 칸 더 보기로 한 질문이 '왜 그렇게 붙었나' 였습니다.
선택지가 데이터 구조보다 좁은 입력 칸
같은 데이터를 다루는 화면이 둘 있었습니다. 품목의 규격과 단가를 보는 목록 화면, 그리고 품목을 분류에 배정하는 전용 화면입니다. 그런데 규격을 보는 쪽에도 분류를 고칠 수 있는 입력 칸이 같이 달려 있었습니다.
그 칸이 선택지로 가져오는 값이 대분류뿐이었습니다. 중분류는 목록에 없었습니다. 그래서 중분류에 올바르게 붙어 있던 품목은 그 화면에서 '미분류' 로 보였습니다. 실제로는 분류가 있는데, 그 칸의 선택지에 없으니 없는 것으로 그려진 것입니다.
'미분류' 로 보이면 사람은 고칩니다. 칸을 누르고 보이는 값 중 하나를 고르는데, 보이는 값은 대분류뿐이므로 저장되는 값도 대분류입니다. 중분류에 잘 붙어 있던 품목을 대분류로 올려 버리는 경로가 화면에 열려 있었습니다.
그 순간 이 건의 성격이 바뀌었습니다. 데이터 오류가 아니라 **쓰기 경로의 결함**입니다. 데이터만 고치면 다음 주에 같은 모양으로 돌아옵니다.

고를 수 있었던 길은 셋이었습니다
선택지가 좁다는 것을 알고 나면 손이 먼저 가는 쪽은 칸을 채우는 일입니다. 그런데 이 시스템에는 배정 전용 화면이 이미 있었고, 그 사실이 판단을 바꿨습니다.
길 | 얻는 것 | 남는 것 |
|---|---|---|
A. 선택 칸에 중분류까지 채운다 | 가장 자연스럽고 사용자 동선이 그대로다 | 같은 데이터에 쓰기 창구가 둘로 남는다. 두 화면의 선택지와 검증이 다시 갈릴 자리다 |
B. 그 화면에서 분류 수정을 뺀다 | 쓰기 창구가 하나가 된다. 검증을 한 곳에만 쓴다 | 분류를 고치려는 사람은 화면을 한 번 옮겨야 한다 |
C. 분류 변경 이력 테이블을 먼저 만든다 | 다음부터는 누가 언제 옮겼는지 알 수 있다 | 지금 열려 있는 경로를 닫지는 못한다 |
B 를 골랐습니다. 읽기는 여러 화면에서 해도 되지만 쓰기는 한 곳으로 모으는 쪽이, 검증을 한 번만 쓰게 합니다. C 는 이번에 하지 않았습니다. 열린 경로를 먼저 닫는 쪽이 급했고, 이력은 창구가 하나로 모인 뒤에 붙이는 것이 더 쌉니다.
반나절 동안 관리자 웹만 고쳤습니다.
규격 화면에서 분류를 아예 수정할 수 없게 했습니다. 목록의 인라인 선택 칸과 수정 창에서 분류 항목을 뺐습니다
배정은 전용 화면 한 곳으로 모았습니다
분류 칸과 필터를 '대분류 > 중분류' 경로로 보여주고, 분류 정렬도 대분류 다음에 그 하위 중분류가 오도록 맞췄습니다
대분류로 거를 때 하위 중분류에 붙은 품목이 함께 나오게 했습니다
메뉴와 화면 제목을 '품목관리' 에서 '품목규격 관리' 로 바꿨습니다
이름을 바꾼 것이 곁가지로 보이는데, 이번 건에서는 같은 무게였습니다. 분류를 못 고치는 화면을 계속 '품목관리' 라고 부르면 사람은 또 거기서 분류를 찾습니다. 읽기 전용으로 바꾸는 순간 전에 한 화면에서 끝낸 일이 두 화면이 되므로, 어디로 가야 하는지가 화면에 보이지 않으면 잠근 것이 그냥 기능 삭제로 느껴집니다.

막을 층을 틀리면 남의 화면이 멈춥니다
잠그는 작업에서 건드리지 않고 둔 것이 둘 있습니다. 둘 다 '여기까지만 잠근다' 는 선을 그은 자리입니다.
첫째, 내부 메뉴 키는 그대로 뒀습니다. 권한이 그 키를 기준으로 행으로 저장돼 있어서, 키를 바꾸면 이미 준 권한이 끊깁니다. 바꾼 것은 사람이 읽는 이름뿐입니다. 화면 이름과 권한의 이름을 같은 값으로 묶어 두면, 라벨 하나 고치는 일이 권한 사고가 됩니다.
둘째, 분류 지정 API 는 열어 뒀습니다. 배정 전용 화면이 같은 API 를 씁니다. 잠근 것은 화면이고 서버는 그대로입니다. 한 화면의 쓰기를 막으려고 API 를 닫으면, 같은 API 를 쓰는 다른 화면이 같이 멈춥니다.
같은 데이터를 두 화면이 서로 다르게 다루다가 생긴 문제는 이번이 처음이 아닙니다. 분류 기준이 화면마다 갈리면, 어느 쪽이 맞는지를 사람이 숫자로 먼저 알게 됩니다.
한쪽 화면이 맞고 다른 쪽이 틀린 것이 아니라 둘의 기준이 달랐던 사례도 같은 모양이었습니다.
확정하지 못한 자리와 남겨 둔 일
그 품목들이 그날 그 경로로 옮겨졌을 가능성이 있습니다. 가능성까지만 적는 이유는 분류 변경 이력을 남기지 않았기 때문입니다. 누가 언제 어느 값에서 어느 값으로 옮겼는지가 아무 데도 없어서, '이 화면이 그랬다' 가 아니라 '이 화면이 그럴 수 있었다' 까지만 쓸 수 있었습니다. 기록이 없으면 원인을 세우지 못한다는 것이 이번 건의 부수 소득입니다.
그래서 다른 경로가 또 있을 가능성도 남아 있습니다. 일괄 업로드나 외부 연동 쪽에 같은 모양의 쓰기가 있는지는 이번에 확인하지 않았습니다. 확정된 원인 하나를 못 세운 상태에서 경로 하나만 닫은 것입니다.
이미 잘못 붙은 품목도 그대로 남았습니다. 우리가 닫은 것은 경로이고, 값은 데이터 주인이 고칩니다. 고객사 데이터를 우리가 옮기지 않는다는 선을 지킨 것인데, 순서가 중요합니다. 먼저 경로를 닫는 것이 우리 몫이고, 그다음에 전용 화면에서 제 중분류로 옮기는 작업이 남습니다. 그 작업이 끝나야 비교 화면 숫자가 맞습니다.
집계가 맞지 않을 때 원인이 계산이 아니라 원천 데이터에 있는 경우를 전에도 한 번 겪었습니다.
입력 칸을 열기 전에 보는 것
의사결정 자리에서 같은 모양을 피하려면 볼 자리가 몇 개 있습니다. 전부 코드를 열지 않고 화면만 보고 확인할 수 있는 것들입니다.
입력 칸의 선택지가 그 데이터가 실제로 가질 수 있는 값을 전부 담고 있는지 — 한 층이라도 빠지면 그 칸은 고치는 도구가 아니라 덮어쓰는 도구입니다
'미분류' 나 '없음' 라벨이 진짜 빈 값인지, 내 선택지에 없다는 뜻인지 — 둘을 같은 말로 쓰면 라벨이 사용자를 잘못된 행동으로 이끕니다
같은 데이터에 쓰기 화면이 몇 개인지 — 둘이면 선택지와 검증이 갈라지는 것은 시간문제입니다
분류·소속 같은 구조 값에 변경 이력이 있는지 — 없으면 사고가 났을 때 가능성까지만 적게 됩니다
화면 이름이 지금 그 화면이 하는 일과 맞는지, 그리고 그 이름이 권한 키와 같은 값으로 묶여 있지 않은지
쓰기 창구를 모으는 것이 늘 정답은 아닙니다
이번에 창구를 하나로 모을 수 있었던 이유는 분류 배정이 드물게 일어나는 작업이기 때문입니다. 하루에 수십 번 고치는 값이라면, 화면을 한 번 옮기는 비용이 사용자에게 그대로 갑니다. 그럴 때는 선택지를 제대로 채우는 A 가 맞습니다.
화면이 하나뿐이거나 그 화면이 유일한 배정 창구일 때도 A 입니다. 잠글 수 있었던 것은 전용 화면이 이미 있었기 때문이고, 없으면 잠그는 선택지 자체가 없습니다. 계층이 대분류와 중분류 두 층이라 '대분류 > 중분류' 경로 표시로 충분했던 점도 조건입니다. 층이 더 깊으면 경로를 보여주는 것만으로는 부족하고 고르는 방식 자체를 다시 설계해야 합니다.
남는 원리는 한 줄입니다. 입력 칸이 보여주는 선택지가 데이터의 실제 구조보다 좁으면, 그 칸은 고치는 도구가 아니라 망가뜨리는 도구입니다. 사용자는 칸에 없는 값을 모릅니다. 칸에 없으니 지금 값이 없는 것으로 보이고, 없어 보이니 채우려 하고, 채우면 좁은 값으로 덮입니다. 악의도 실수도 아니고 화면이 그렇게 시킨 것입니다.

비교 화면의 0 을 보고 처음 열어 본 것은 집계 쿼리였습니다. 쿼리에서 데이터로, 데이터에서 그 데이터를 쓴 화면으로 한 칸씩 올라가면서 고칠 대상이 바뀌었습니다. 집계가 이상할 때 한 칸 위를 보는 습관은, 같은 숫자를 다음 달에 다시 보지 않게 해줍니다.