+ 품목·리뷰의 이중 증폭과 NULL 영향을 빠짐없이 짚었다.
- 정확하지만 일부 데이터 가정 설명은 다소 장황하다.
중첩된 서브쿼리를 안쪽부터 갈라 설명합니다. 조용히 틀릴 수 있는 지점을 표시합니다.
| 분류 | 개발 › 데이터·DB |
|---|---|
| 태그 | 분석코드개발자 |
Explain this query. Produce: 1. What it returns, in one sentence, at the grain of one output row. 2. A breakdown from the innermost subquery or CTE outward. For each: what it produces and at what grain. 3. **Grain changes** — every point where the number of rows can multiply or collapse. Joins that fan out are the most common source of wrong totals and the hardest to see. 4. NULL behavior — where NULLs change the result: outer joins feeding `WHERE`, `NOT IN` against a nullable column, aggregates skipping NULLs, `!=` excluding them. 5. Assumptions about the data the query relies on without enforcing: uniqueness, sorted order, no duplicates, complete reference data. 6. Compare to what I said it should return, and flag any mismatch. Rules: - Explain what it *does*, not what it was probably meant to do. If they differ, state both — that gap is usually the bug. - *Flag anything you cannot verify without seeing the data*, especially duplicate keys. - Do not rewrite it. Understanding first.
인수인계받은 쿼리는 고치기가 무섭다. 이 프롬프트는 각 조각이 무엇을 반환하는지 설명하고, 조인 방식이나 NULL 처리 때문에 값이 조용히 달라지는 자리를 짚는다.
ChatGPT가 가장 완전하고 정확하다. Gemini도 핵심 fan-out을 잘 설명하지만 일부 예외가 빠졌으며, [C]는 답변이 없다.
+ 품목·리뷰의 이중 증폭과 NULL 영향을 빠짐없이 짚었다.
- 정확하지만 일부 데이터 가정 설명은 다소 장황하다.
+ 단계별 grain과 행 수 공식을 명확하게 시각화했다.
- 리뷰 0건일 때 총액 공식과 일부 가정 설명이 불완전하다.
| 기준 | ChatGPT | Gemini | 앞선 모델 |
|---|---|---|---|
| 지시 준수 | 10 | 9 | ChatGPT +11% |
| 정확성 | 10 | 9 | ChatGPT +11% |
| 구체성 | 10 | 10 | 동점 |
| 구성·가독성 | 9 | 9 | 동점 |
| 분량 적정성 | 8 | 8 | 동점 |
gpt-5.6-sol가 모델 이름을 가린 채 1~10점으로 채점했습니다 (2026-09-25). 측정값이 아니라 AI 심사 결과입니다.
같은 입력을 세 모델에 넣어 받은 답을 그대로 옮겼습니다. 각 CLI(에이전트 하네스)에서 실행했습니다. ChatGPT·Claude 앱이나 웹에서는 답이 다를 수 있습니다.
쿼리: SELECT u.id, u.name, COUNT(o.id) AS order_count, SUM(oi.quantity * oi.price) AS total_spent FROM users u JOIN orders o ON o.user_id = u.id JOIN order_items oi ON oi.order_id = o.id LEFT JOIN reviews r ON r.user_id = u.id WHERE o.created_at >= '2026-01-01' GROUP BY u.id, u.name; 테이블 정보: users(id, name) orders(id, user_id, created_at, status) order_items(id, order_id, product_id, quantity, price) reviews(id, user_id, product_id, rating) 기대하는 결과: 고객별 순수 주문 건수(order_count)와 총 구매 금액(total_spent)만 나와야 하는데, 리뷰를 여러 개 남긴 고객일수록 order_count와 total_spent가 실제보다 훨씬 크게 부풀어서 나옵니다.
이 쿼리는 2026-01-01 이후 주문이 있는 고객별로, 주문·주문상품·리뷰 조인으로 만들어진 행들을 기준으로 COUNT(o.id)와 상품 금액 합계를 계산하므로, 고객의 리뷰 수와 주문상품 수에 따라 주문 건수와 총액이 부풀 수 있습니다.
명시적인 CTE나 서브쿼리는 없습니다. FROM과 JOIN이 만드는 결과를 순서대로 보면 다음과 같습니다.
users u JOIN orders oJOIN orders o ON o.user_id = u.id
orders에 연결된 사용자가 없으면 제외됩니다.WHERE o.created_at >= '2026-01-01' 때문에 2026년 이전 주문은 제외됩니다.JOIN order_items oiJOIN order_items oi ON oi.order_id = o.id
LEFT JOIN reviews rLEFT JOIN reviews r ON r.user_id = u.id
r 컬럼이 NULL인 한 행은 유지됩니다.product_id가 없으므로, 고객의 모든 리뷰가 모든 주문상품에 붙습니다.GROUP BYGROUP BY u.id, u.name
주요 변화는 다음과 같습니다.
users → orders
orders → order_items
order_items → reviews
n개이고 리뷰가 m개인 고객은 대략 n × m개의 행을 만들 수 있습니다.LEFT JOIN 때문에 리뷰 NULL인 한 행만 유지됩니다.GROUP BY
order_count의 추가 문제COUNT(o.id)
이 값은 주문의 개수가 아니라 최종 조인 결과에서 o.id가 NULL이 아닌 행의 개수입니다.
따라서 리뷰가 전혀 없더라도:
이면 COUNT(o.id)는 3입니다.
리뷰가 2개 있으면 같은 주문상품 행이 리뷰별로 복제되어 6이 될 수 있습니다.
즉, 현재 쿼리의 order_count는 실제 주문 건수가 아니라 대략 다음의 영향을 받습니다.
주문상품 행 수 × 고객 리뷰 행 수
리뷰가 없는 경우에도 주문상품 행 수만큼 셉니다.
total_spent의 추가 문제SUM(oi.quantity * oi.price)
주문상품별 금액은 정상적으로 합산되기 전에 리뷰 조인으로 복제됩니다.
예를 들어 한 주문의 상품 금액 합계가 100이고 고객 리뷰가 3개면, 리뷰 조인 이후에는 100이 3번 합산되어 300이 될 수 있습니다.
users, orders, order_items는 모두 일반 JOIN이므로 연결되지 않는 행은 결과에서 제거됩니다.reviews는 LEFT JOIN이므로 리뷰가 없어도 고객의 주문상품 행은 유지됩니다. 이 경우 r의 컬럼만 NULL입니다.WHERE 조건은 o.created_at에 적용되지만 orders가 이미 inner join이므로, 여기서는 outer join 행을 다시 제거하는 문제는 없습니다.COUNT(o.id)는 NULL을 세지 않습니다. 다만 이 쿼리에서는 inner join 때문에 정상적인 경우 o.id가 NULL이 아니므로 주문상품/리뷰 중복 행을 모두 셉니다.SUM은 NULL 값을 건너뜁니다.oi.quantity 또는 oi.price 중 하나가 NULL이면 oi.quantity * oi.price도 NULL이고, 해당 금액은 SUM에 기여하지 않습니다.SUM(...) 결과는 0이 아니라 NULL입니다.NOT IN이나 != 조건은 이 쿼리에 없습니다.u.name이 NULL이어도 GROUP BY u.id, u.name에는 영향을 주지만, 일반적으로 u.id가 유일하면 고객별 그룹 구분은 유지됩니다.다음은 스키마나 쿼리만으로 확인할 수 없습니다.
users.id가 유일해야 고객별 집계가 의도대로 됩니다.orders.id가 유일해야 주문 식별이 정상적입니다.order_items.id가 유일해야 주문상품 중복을 구분할 수 있습니다.reviews.id가 유일해야 리뷰 행이 중복 저장되지 않았다고 볼 수 있습니다.orders.user_id가 실제 존재하는 사용자만 가리킨다는 참조 무결성을 가정합니다.order_items.order_id가 실제 존재하는 주문만 가리킨다는 참조 무결성을 가정합니다.reviews.product_id와 order_items.product_id가 어떤 관계인지 확인하지 않습니다. 상품별 리뷰를 붙이려는 목적이었다면 현재 조인은 상품을 기준으로 제한하지 않습니다.quantity와 price가 NULL이 아니고 유효한 금액이라는 가정이 필요합니다.price가 주문 당시 가격인지, 현재 상품 가격인지도 쿼리만으로는 알 수 없습니다.status는 전혀 사용되지 않으므로 취소·환불 주문도 날짜 조건만 통과하면 포함됩니다.기대 결과가 “고객별 순수 주문 건수와 총 구매 금액”이라면 현재 쿼리는 일치하지 않습니다.
구체적으로:
reviews 조인 때문에 주문상품 금액이 리뷰 수만큼 반복되어 total_spent가 부풀 수 있습니다.order_count는 리뷰 때문에 부풀 뿐 아니라, 리뷰가 없어도 주문상품 수를 세므로 실제 주문 건수가 아닙니다.status를 필터링하지 않으므로 취소나 환불 주문을 제외해야 한다는 요구가 있다면 그것도 불일치입니다.따라서 사용자가 관찰한 리뷰가 많은 고객의 과대 집계 현상은 쿼리의 LEFT JOIN reviews r ON r.user_id = u.id가 직접적인 원인입니다. 다만 그 문제를 제거해도 COUNT(o.id)는 여전히 주문 건수가 아니라 주문상품 행 수를 셉니다.
| 대용량 텍스트 변환기 프롬프트 | |
| AI 데이터 추출 도구 기획 프롬프트 | |
| AI2sql 자연어 SQL 생성 프롬프트 | |
| 백엔드 아키텍트 역할을 맡기는 프롬프트 | |
| Base R 프로그래밍 프롬프트 |