경영지원팀이 데이터 에이전트에 "지난달 매출 얼마예요?"라고 물었어요. 2억 7천만 원이 나왔어요. 같은 날 오후, 재무팀이 같은 챗봇에 같은 질문을 던졌더니 2억 2천만 원이었어요. 둘 다 SQL은 멀쩡히 돌았고, 에러도 없었어요.
어느 쪽이 맞는 걸까요. 이 장면에서 틀린 건 모델이 아니라 질문이었어요. "매출"이 무엇인지 아무도 정해 두지 않았거든요.
Text-to-SQL은 SQL을 못 써서 틀리는 게 아니에요
자연어를 SQL로 바꾸는 일 자체는 이제 꽤 잘 돼요. 기업용 벤치마크 Spider 2.0은 공개 당시(ICLR 2025) GPT-4o가 10.1%밖에 못 풀었는데, 지금 리더보드 상위는 90%를 넘어요. 구글 클라우드의 2026 AI 에이전트 트렌드 보고서에는 펄프 제조사 수자노가 Gemini 기반 에이전트로 질의 소요 시간을 95% 줄인 사례도 실렸고요.
그런데 dbt Labs가 2026년 4월에 낸 벤치마크 갱신에서 눈에 띄는 대목은 정확도 숫자가 아니라 실패의 모양이었어요. 모델이 테이블을 잘못 조인하거나 컬럼 의미를 오해해도 쿼리는 성공적으로 실행되고 그럴듯한 값을 돌려준다는 거예요. 에러가 나면 사람이 보는데, 그럴듯한 오답은 보고서에 그대로 실려요.
같은 질문을 72가지로 읽을 수 있었어요
저희는 이걸 모델 없이 재봤어요. 주문 4,000건짜리 가상 데이터를 만들고, "지난달 매출"을 사람이 봐도 전부 말이 되는 해석으로 쪼갰어요.
| 축 | 선택지 |
|---|---|
| 날짜 기준 | 주문일 · 결제일 · 출고일 |
| 기간 | 달력상 지난달 · 최근 30일 |
| 취소 주문 | 포함 · 제외 |
| 금액 | 정가 · 할인 차감 · 할인과 환불 차감 |
| 부가세 | 포함 · 제외 |
조합하면 72개의 SQL이 나와요. 전부 실행에 성공했고, 서로 다른 값이 48개였어요. 최솟값 2억 1,600만 원, 최댓값 3억 5,900만 원. 같은 데이터, 같은 질문에 답이 1.66배 벌어졌어요.
결제일 · 달력월 · 취소 제외 · 환불 차감 · 부가세 제외를 기준으로 두고, 축 하나만 바꿔 봤어요.
| 바꾼 축 | 값의 변화 |
|---|---|
| 결제일 → 주문일 | +23.0% |
| 부가세 제외 → 포함 | +10.0% |
| 할인·환불 차감 → 정가 | +10.9% |
| 달력월 → 최근 30일 | +7.4% |
| 취소 제외 → 포함 | 0.0% |
마지막 줄이 재미있어요. 취소 주문은 결제일이 비어 있어서 결제일 기준에서는 걸러도 안 걸러도 같아요. 그런데 주문일 기준으로 바꾸면 같은 조건이 값을 7.6% 움직여요. 조건끼리 얽혀 있어서 사람이 SQL을 읽어도 "이게 맞나"를 판단하기 어려운 구조예요. 도입부의 두 숫자도 이 표 안에 있어요. 2억 7천은 주문일 기준, 2억 2천은 결제일 기준이었어요.
그래서 SQL을 쓰기 전에 정의부터 적었어요
저희가 내린 결론은 단순해요. 모델이 SQL을 쓰는 게 아니라 지표를 고르게 해요. "매출"이 무엇인지는 사람이 한 번 정하고, 모델은 그 정의를 부르기만 해요. 이게 시맨틱 레이어예요. 처음엔 YAML 한 장이면 충분해요.
metrics:
net_revenue:
description: 결제 완료 기준 순매출. 할인·환불 차감, 부가세 제외. 취소 제외.
time_dimension: orders.paid_at
filters: [orders.status != 'cancelled']
expr: sum(orders.gross_amount - orders.discount) - sum(refunds.amount)
vat: excluded
이렇게 두고 달라진 건 72개였던 선택이 0개가 됐다는 거예요. 같은 정의를 query_metric("net_revenue", "last_month") 도구로 감싸니 누가 몇 번을 물어도 2억 2,207만 원 하나만 나왔고, 응답에 정의 문장이 같이 붙어요. "이건 부가세 제외예요?"라고 다시 물을 일이 없어져요.
dbt Labs 벤치마크에서도 같은 구조가 보여요. Text-to-SQL이 8490%였던 조건에서 시맨틱 레이어를 거치자 98100%로 올랐고, 정의 밖 질문에는 틀린 값 대신 에러를 돌려줬어요. 에러는 고칠 수 있지만 그럴듯한 오답은 발견조차 안 되거든요.
정의 밖 질문은 막고, 골든셋으로 지켜요
시맨틱 레이어의 약점은 커버리지예요. 그래서 저희는 두 갈래로 나눠요. KPI · 보고서처럼 틀리면 안 되는 질문은 지표 도구로만 답하고, 탐색용 질문은 읽기 전용 계정에 테이블 허용 목록과 LIMIT을 강제한 채 SQL을 쓰게 둬요. 후자의 답에는 "임시 집계" 표시를 붙여요.
| AS-IS | TO-BE | |
|---|---|---|
| 모델이 받는 것 | 테이블 · 컬럼명 | 지표 이름 · 정의 문장 · 허용 기간 |
| 같은 질문의 답 | 물을 때마다 다를 수 있음 | 항상 같음 |
| 정의 밖 질문 | 그럴듯한 값 | "정의된 지표가 없어요" |
| 틀린 답의 모습 | 보고서에 실린 뒤 발견 | 골든셋 실패로 배포 전 발견 |
골든셋은 RAG 품질 평가 글과 같은 방식이에요. 질문 20개와 기대하는 지표 호출을 적어 두고, 정의나 프롬프트가 바뀌는 커밋마다 돌려요. 파일럿에서 운영으로 넘어갈 때 무너지는 지점으로 비결정성을 첫째로 꼽았는데, 데이터 에이전트에서 그 실체가 "매출의 정의"예요.
다시 처음 질문으로 돌아가면
경영지원팀과 재무팀 중 누가 맞았을까요. 둘 다 맞았고, 그래서 둘 다 틀렸어요. 정의가 없으면 어떤 SQL도 정답이 될 수 있고, 그건 정답이 없다는 뜻이에요. Text-to-SQL 도입에서 저희가 제일 먼저 하는 일은 모델 고르기가 아니라 "이 회사에서 매출은 무엇인가"를 한 줄로 적는 일이에요. 데이터 엔지니어링에서 하는 일의 상당 부분이 이 한 줄들을 모으는 일이고요. 완성된 정의가 아니라, 질문이 올 때마다 한 줄씩 늘어나는 정의예요.
자주 묻는 질문
Q. 시맨틱 레이어를 두면 모델이 자유롭게 질문을 못 받는 것 아닌가요?
A. 정의 밖 질문은 못 받아요. 그게 의도예요. 탐색용 질문은 읽기 전용 · 허용 목록 · LIMIT을 건 별도 경로로 받아요.
Q. dbt나 Cube 같은 도구를 꼭 써야 하나요? A. 처음엔 YAML 하나와 그걸 SQL로 바꾸는 함수 하나면 돼요. 지표가 수십 개를 넘고 부서마다 정의가 갈리면 그때 dbt · Cube 같은 도구를 검토해도 늦지 않아요.
Q. 모델을 더 좋은 걸로 바꾸면 해결되지 않나요? A. 저희 실험은 모델 없이 SQL 해석만 열거한 거라, 어떤 모델이 와도 72개 중 하나를 고르는 구조는 같아요. dbt Labs 벤치마크에서도 시맨틱 레이어를 거친 조건에서는 모델 차이가 거의 없었어요.
Sources
- dbt Labs, "Semantic Layer vs. Text-to-SQL: 2026 Benchmark Update" (2026-04-07): https://docs.getdbt.com/blog/semantic-layer-vs-text-to-sql-2026
- Spider 2.0 — Enterprise Text-to-SQL 벤치마크 (ICLR 2025): https://spider2-sql.github.io/
- Google Cloud, "AI Agent Trends 2026": https://cloud.google.com/resources/content/ai-agent-trends-2026
- Process Excellence Network, "Google Cloud predicts 5 ways AI agents will transform businesses in 2026" (2026-01-07, 수자노 사례 인용): https://www.processexcellencenetwork.com/ai/news/google-cloud-predicts-5-ways-ai-agents-will-transform-businesses-in-2026
- 인공지능신문, "AI 에이전트, 2026년 업무 방식 근본적으로 바꾼다" (2025-12-21): https://www.aitimes.kr/news/articleView.html?idxno=37812

