AI가 짠 SQL을 받는 순간,
제일 먼저 의심할 5가지
드디어 섹션 6 — 강의 소개에서 "화룡점정"이라고 했던 그 섹션입니다. 모델링도 알았고, AI에게 SQL을 잘 시키는 법도 배웠죠. 이게 다 끝일까요? 아닙니다. 진짜 AI 시대의 실무 역량은 따로 있습니다 — AI가 짠 SQL을 의심할 줄 아는 것. 오늘은 AI가 가장 자주 빠지는 함정 5가지를 한 장의 지도로 머릿속에 넣습니다.
이번 섹션의 여정
섹션 4에서 구조를 읽는 눈을, 섹션 5에서 AI에게 잘 시키는 법을 챙겼습니다. 마지막 한 기둥이 남았어요 — AI의 결과를 의심하고 검증하는 능력. 섹션 6은 5개 강의로 이 능력을 만듭니다. 오늘 5가지 패턴을 전부 훑고, 다음 강의들에서 하나씩 깊게 잡고, 마지막엔 일부러 틀린 SQL을 함께 디버깅하는 실습까지 갑니다.
완료 ✓
지금 여기
빈도 1위 ⭐
NULL · GROUP BY
+ 디버깅 실습
실무 가이드
진짜 AI 시대의 실무 역량은 — “AI가 짠 SQL을 의심할 줄 아는 것.” 여기서 실력이 갈립니다.
다섯 가지 함정, 한눈에
좋은 소식부터 — AI가 SQL을 만들 때 빠지는 함정은 무한하지 않습니다. 실무에서 마주치는 건 사실상 이 5가지의 변주예요. 받자마자 이 지도를 머릿속에 띄우는 게 오늘의 목표입니다.
①②③은 SQL 자체의 함정이라 강의 한 편씩 잡고 깊게 들어가고, ④⑤는 오늘 이 자리에서 끝을 봅니다.
에러가 나면 차라리 다행입니다. 5가지 중 ④를 뺀 나머지는 에러 없이 그럴듯한 결과를 돌려줘요 — 검증 습관이 없으면 틀린 숫자가 그대로 보고서에 올라갑니다.
패턴 1 — JOIN 으로 행 뻥튀기
3-7에서 우리가 직접 빠져봤던 그 함정 — AI도 똑같이 빠집니다. “회원별 주문 건수를 구해줘” 라고 했더니 AI가 이런 SQL을 줬다고 해볼게요.
-- ❌ AI 가 만든 SQL — 어디가 문제일까요?
SELECT
m.member_name,
COUNT(*) AS 주문수
FROM
members m
JOIN orders o
ON m.member_id = o.member_id
JOIN order_items oi
ON o.order_id = oi.order_id
GROUP BY
m.member_id;주문 건수만 필요한데 order_items까지 JOIN했습니다. 한 주문이 주문상세 개수만큼 여러 행으로 불어나고, COUNT(*)는 그 불어난 행을 셉니다.
| member_name | 주문수 |
|---|---|
| 김철수 | 10 ← 실제 주문은 6건 |
| 이영희 | 6 ← 실제 주문은 4건 |
| ⋮ | |
잡는 법 — 결과를 받자마자 COUNT(*) 검산. JOIN 결과의 행 수가 “가장 자식 테이블” 의 행 수와 같은지부터 확인합니다. (우리 DB라면 order_items 64행.)
가장 빈도가 높은 패턴이라 다음 강의 한 편을 통째로 씁니다 — 뻥튀기가 일어나는 원리부터 검산하고 고치는 법까지.
패턴 2 — NULL 처리 빠뜨림
“회원별 매출 합계를 구해줘. 주문 없는 회원도 포함해서.” — AI가 LEFT JOIN까지는 잘 씁니다. 문제는 그다음이에요.
-- ❌ AI 가 만든 SQL — LEFT JOIN 까지는 좋았는데
SELECT
m.member_name,
SUM(p.price * oi.quantity) AS 매출
FROM
members m
LEFT JOIN orders o
ON m.member_id = o.member_id
LEFT JOIN order_items oi
ON o.order_id = oi.order_id
LEFT JOIN products p
ON oi.product_id = p.product_id
GROUP BY
m.member_id,
m.member_name;| member_name | 매출 |
|---|---|
| 김철수 | 498000 |
| ⋮ | |
| 황태웅 | NULL |
| 안소영 | NULL |
| 류재민 | NULL |
| 백서윤 | NULL |
문제 — 주문 안 한 회원의 매출이 NULL입니다. “0원” 이 아니에요. NULL은 “값이 없음” 인데, 보고서를 받는 사람이 0으로 착각하는 순간 사고가 됩니다.
잡는 법 — COALESCE(SUM(...), 0) 으로 NULL을 0으로 명시합니다. 그리고 결과에 NULL이 보이면 “이게 0이라는 뜻인지, 모른다는 뜻인지” 를 꼭 확인하세요.
NULL vs 0 vs ‘없음’ — 셋의 차이와 COALESCE, IS NULL 사용법을 6-3에서 깊게
다룹니다.
패턴 3 — GROUP BY 누락 · 잘못된 집계 단위
-- ❌ AI 가 만든 SQL — SELECT 와 GROUP BY 가 안 맞음
SELECT
p.category_id,
o.member_id,
COUNT(*) AS 주문수
FROM
orders o
JOIN order_items oi
ON o.order_id = oi.order_id
JOIN products p
ON oi.product_id = p.product_id
GROUP BY
p.category_id; -- ⚠️ member_id 가 빠졌다문제 — SELECT에는 member_id가 있는데 GROUP BY에는 category_id뿐. 한 카테고리에는 여러 회원의 주문이 섞여 있을 텐데, 그중 누구의 member_id가 나올지 아무도 모릅니다. 설정에 따라 MySQL이 이걸 허용하기도 하는데 — 그러면 아무 값이나 조용히 나옵니다. 사고죠.
잡는 법 — 공식 하나면 됩니다. GROUP BY 컬럼 = SELECT의 모든 비집계 컬럼. 받은 SQL에서 둘을 대조만 해도 잡힙니다.
MySQL 8의 기본 설정(ONLY_FULL_GROUP_BY)은 이런 SQL을 에러로 막아주지만, 이
설정이 꺼진 서버가 실무에 아직 많습니다. 집계 단위 감각은 6-4에서 제대로
만듭니다.
패턴 4 — 컬럼·테이블명 환각 (hallucination)
이번엔 SQL 문법이 아니라 프롬프트의 함정입니다. AI가 우리 스키마에 없는 컬럼을 “있을 것 같으니까” 그냥 지어내는 현상 — 환각(hallucination) 이에요.
-- ❌ AI 가 만든 SQL — 그럴듯하지만, 존재하지 않는 컬럼들
SELECT
m.member_name,
m.email,
m.phone -- ⚠️ members 에 email, phone 없음!
FROM
members m
WHERE
m.last_login_at > '2025-01-01'; -- ⚠️ last_login_at 도 없음!세상의 ‘보통 쇼핑몰’ 회원 테이블에는 이메일이 있으니까, 우리 members에도 있을 거라고 추측한 겁니다. 하지만 우리 members의 컬럼은 member_id, member_name, gender, age, joined_at — 다섯 개가 전부예요.
잡는 법은 3단계 — 사실 1단계에서 거의 끝납니다.
받은 SQL에 등장하는 컬럼·테이블이 실제 스키마에 다 있는지 눈으로 점검합니다.
실행해서 컬럼 에러가 나면 환각 확정 — 다섯 패턴 중 유일하게 에러로 즉시 잡히는 고마운 패턴입니다.
대화가 길어져 AI의 파일 기억이 흐려진 신호입니다. 2-4의 규칙 그대로 — 새 대화 + init.sql 재첨부.
패턴 5 — 비즈니스 의미 해석 오류 ⭐
마지막 패턴은 결이 다릅니다. 이건 AI가 절대 못 잡는 영역 — 사람의 일이에요.
마케팅팀에서 “활성 사용자 분석해줘” 라는 요청이 왔다고 해볼게요. 그대로 AI에게 전달하면 —
”활성 사용자 분석해줘”
AI의 해석은 “지난 7일 안에 로그인한 사용자”. 그런데 우리 회사의 정의는 “지난 30일 안에 주문한 사용자”. 같은 단어, 다른 의미 — SQL은 정확하게 돌아가는데 결과는 비즈니스 의도와 어긋납니다.
”활성 = 지난 30일 안에 주문한 사용자”
정의를 먼저 못박고, 조건까지 같이 전달합니다 — orders.ordered_at >= NOW() - INTERVAL 30 DAY. AI가 해석할 여지를 없애는 것이 핵심입니다.
잡는 법 — 요청 문장에 모호한 단어가 보이면 멈추세요. “활성”, “인기”, “최근”, “VIP”, “잘 팔리는” — 전부 본인 회사의 정의로 먼저 구체화한 뒤 AI에게 전달합니다. 5-3의 “모호한 단어 → 구체화” 원칙과 정확히 같은 메시지예요.
SQL 함정은 검증으로, 프롬프트 함정은 컨텍스트로 잡지만 — 비즈니스 의미는 그 회사를 아는 사람만 잡을 수 있습니다.
한 표 요약 — 캡처해 두세요
| # | 패턴 | 1줄 정체 | 잡는 법 | 심화 |
|---|---|---|---|---|
| ① | JOIN 뻥튀기 | N:M JOIN 으로 행이 곱해짐 | COUNT(*) 검산 | 6-2 ⭐ |
| ② | NULL | NULL vs 0 vs 없음 혼동 | COALESCE, IS NULL | 6-3 |
| ③ | GROUP BY | 집계 단위 불일치 | GROUP BY = SELECT 비집계 컬럼 | 6-4 |
| ④ | 환각 | 없는 컬럼을 지어냄 | 스키마 항상 명시 | 오늘 완료 ✓ |
| ⑤ | 의미 오류 | 모호한 단어를 멋대로 해석 | 본인 정의로 구체화 | 오늘 완료 ✓ |
①②③은 SQL의 함정 — 검증으로 잡고, ④는 프롬프트의 함정 — 컨텍스트로 막고, ⑤는 비즈니스의 함정 — 사람만 잡습니다.
①②③의 검산법은 6-2 ~ 6-4에서 손으로 직접 잡아보고, 6-5에서 체크리스트 한 장으로 정리해 드립니다. 오늘은 이 지도만 머리에 넣으면 충분해요.
다음 강의는 빈도 1위 — 6-2 JOIN 뻥튀기 심화입니다. 그 전에, 방금 본 5가지 패턴을 보는 눈이 생겼는지 연습 문제로 점검하고 가세요.