SQL 실습
식품 제조 AI가 막힐 때, 모델보다 데이터 연결을 먼저 봅니다
AI가 같은 업체의 이슈 건수를 질문할 때마다 다르게 설명한다고 해볼게요. 프롬프트를 고치고 모델을 바꿔도 숫자가 맞지 않습니다. 이때 답변을 만드는 단계보다 먼저 볼 곳이 있어요. 서로 다른 데이터를 어떤 관계로 연결했는지입니다.
이 글의 데이터와 업무 장면은 오류 유형을 설명하기 위해 만든 예시입니다. 특정 프로젝트에서 실제로 발생한 일을 단정하는 회고는 아니에요.
이름이 같다는 이유로 연결하면 대상이 섞입니다
계약 상대와 실제 생산처가 별도로 관리되는 상황을 생각해보죠. 계약 상대의 이름을 기준으로 모든 기록을 합치면 여러 생산처의 이슈가 한곳의 문제처럼 보일 수 있습니다.
반대로 같은 생산처가 약칭과 정식 명칭으로 나뉘어 있으면 기록이 분산돼요. 그래서 이름 비교보다 업무에서 같은 대상으로 인정할 식별 기준을 먼저 정해야 합니다. 연결 여부가 불확실하면 후보를 제시하되 자동 확정하지 않는 것이 출발점이에요.
데이터를 붙였더니 건수가 늘어날 수 있습니다
가상의 점검 사건 하나에 첨부 파일이 세 개 있다고 해볼게요. 사건과 첨부를 연결한 표는 세 행이 됩니다. 이 결과에서 행 개수를 세면 점검 세 건으로 오해할 수 있죠.
다른 이력까지 함께 붙이면 중복은 더 커질 수 있습니다. 그래서 무엇을 한 건으로 셀 것인지 정하고, 세부 이력을 연결하기 전후에 사건 수가 유지되는지 확인해야 해요. 행이 늘어나는 것 자체가 오류는 아니지만 그 행 수를 사건 수로 사용하는 것은 다른 문제입니다.
이 단계에서는 복잡한 AI 평가보다 원본 사건 몇 개를 손으로 따라가는 검증이 더 직접적일 수 있습니다. 어떤 연결에서 같은 사건이 여러 행으로 확장되는지 확인하는 거예요.
SQL로 결합 후 행 수와 사건 수를 비교해봅니다
이번에는 가상 기록으로 중복 집계가 생기는 지점을 직접 확인해볼게요. 아래 SQL은 실제 업무 테이블을 읽거나 수정하지 않습니다. PostgreSQL의 VALUES 구문으로 사건 3건과 첨부 관계 3건을 만들어 조회하는 예시입니다.
사건 I-1에는 첨부 A-1과 A-2가 있고, I-2에는 A-3이 있습니다. I-3에는 첨부가 없어요. 먼저 결합 결과의 행 수, 고유 사건 수, 연결된 첨부 수를 각각 구해봅시다.
WITH events(event_id, site_id) AS (
VALUES ('I-1', 'F-1'), ('I-2', 'F-1'), ('I-3', 'F-2')
), attachments(event_id, file_id) AS (
VALUES ('I-1', 'A-1'), ('I-1', 'A-2'), ('I-2', 'A-3')
)
SELECT
COUNT(*) AS joined_rows,
COUNT(DISTINCT e.event_id) AS event_count,
COUNT(a.file_id) AS attachment_count
FROM events e
LEFT JOIN attachments a ON a.event_id = e.event_id;
기대 결과는 joined_rows=4, event_count=3, attachment_count=3입니다. I-1은 첨부가 두 개라 두 행으로 늘어나고, I-2는 한 행입니다. I-3은 첨부 값이 NULL인 한 행으로 남으므로 전체 결합 결과는 네 행이 됩니다.
COUNT(*)는 그 네 행을 세고, COUNT(DISTINCT e.event_id)는 고유 사건 세 개를 셉니다. COUNT(a.file_id)는 NULL이 아닌 첨부 값 세 개를 세는 거예요. 이 네 행을 곧바로 점검 네 건이라고 보고하면 원본 사건 수와 달라집니다. 첨부가 없는 사건을 빼려던 것이 아니라면 INNER JOIN으로 바꾸는 것도 답이 아닙니다. 이 예시에서는 I-3이 집계에서 사라지기 때문이에요.
LEFT JOIN이 일치하는 오른쪽 행을 연결하고, 일치 항목이 없는 왼쪽 행에는 NULL을 채워 남기는 동작은 PostgreSQL 문서에서 확인할 수 있습니다. 위 자료와 기대 숫자는 그 동작을 확인하기 위해 직접 만든 예시입니다.
참고: PostgreSQL 18 · Table Expressions
https://www.postgresql.org/docs/18/queries-table-expressions.html
다만 DISTINCT를 붙였다고 연결 문제가 모두 해결되는 것은 아닙니다. 서로 다른 조직이 같은 사건 번호를 쓰는데 조직 조건을 빼고 결합했다면, 이미 다른 대상의 자료가 섞인 거예요. 사건 번호가 전체에서 유일한지 확인하고 실제 식별 범위에 맞는 키를 사용해야 합니다. 첨부 금액이나 측정값을 합산하는 경우에도 고유 사건 수 하나를 맞췄다고 다른 합계가 맞아지는 것은 아닙니다.
한 사건에 첨부 여러 개와 조치 여러 개를 동시에 붙이는 경우에는 각 세부 자료를 사건 단위로 먼저 집계한 뒤 연결하거나, 상세 목록을 별도로 조회하는 방식을 검토해보세요. 결합을 없애는 것이 아니라 최종 결과에서 무엇을 한 행으로 표현할지 정하는 작업입니다.
회귀 시험도 원본 단위로 작성합니다. 사건에 첨부를 추가해도 고유 사건 수는 유지돼야 해요. 첨부를 모두 제거해도 사건 세 건은 남아야 합니다. 같은 첨부 관계를 중복 전송했을 때는 관계 식별 기준에 따라 중복을 처리해야 하고, 다른 조직의 같은 사건 번호는 들어오지 않아야 합니다. 각각의 기대 결과를 적어두면 모델을 바꾸기 전에 데이터 조회부터 확인할 수 있어요.
현재 상태와 당시 상태를 섞지 않습니다
업체 분류가 바뀌거나 시정조치가 나중에 완료되면 현재 데이터만으로 과거 판단을 다시 만들기 어려워집니다. “그때 먼저 확인했어야 할 대상”을 분석하면서 나중에 알게 된 조치 결과를 넣으면 시간의 순서가 뒤집혀요.
발생 시점, 시스템에 들어온 시점, 수정 시점을 구분하고 분석 기준일을 정해보세요. 과거 비교에는 당시 알 수 있었던 자료만 쓰는 방식이 필요합니다. 최신 정보를 쓰는 운영 조회와 과거 시점을 재현하는 검증을 같은 조건으로 처리하지 않는 겁니다.
연결 품질을 결과 화면과 함께 봅니다
전체 기록 중 연결된 비율만으로 충분하지 않을 수 있습니다. 중요한 대상의 기록이 빠졌는지, 하나의 기록이 여러 대상에 연결됐는지, 단위가 맞지 않는 값이 섞였는지도 확인해야 해요.
미연결 자료가 남아 있는데 결과를 전체 현황처럼 표시하지 않도록 화면에도 분석 범위를 보여줍니다. 사용자에게 “모델이 답을 못 했다”와 “필요한 자료가 아직 연결되지 않았다”는 상태가 구분되어야 하죠.
Google의 머신러닝 가이드에는 학습과 실제 서비스에서 쓰이는 데이터가 어긋나는 문제를 점검하는 원칙이 나옵니다.[1] 이 글에서는 그 관점을 모델 앞단의 식별자·집계 단위·시간 조건까지 확장해 살펴본 거예요.
모델 개선을 시작하기 전, 한 질문에 사용된 원본과 연결 결과를 나란히 놓아보세요. 데이터스케쳐스와 그 흐름을 검토하면 모델 문제와 데이터 연결 문제를 구분하는 데 필요한 검증 범위를 정할 수 있습니다.
참고자료
[1] Google for Developers · Rules of Machine Learning
https://developers.google.com/machine-learning/guides/rules-of-ml
모델보다 먼저 지표와 기반 구조를 정비하고 학습·서비스 데이터 차이를 점검하는 원칙
자료 확인: 2026-10-08. 본문의 가상 상황·화면 구성·점검 절차는 출처의 실제 사례가 아니라 이 글에서 제안한 적용 예시입니다.