본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 26. 14:54

복잡한 다중 테이블 SQL을 위한 AI 프롬프팅: 실무 가이드

요약

다중 테이블 SQL 생성 시 발생하는 AI의 오류 원인을 분석하고, 정확한 쿼리 생성을 위한 프롬프팅 기술을 가이드합니다. 스키마 컨텍스트 주입을 통해 모델이 테이블 관계와 비즈니스 로직을 정확히 이해하도록 만드는 방법을 다룹니다.

핵심 포인트

  • 단순 쿼리와 달리 다중 테이블 쿼리는 정확한 스키마 정보가 필수적임
  • 모델이 스키마를 모를 경우 잘못된 조인 키나 컬럼명을 생성할 위험이 있음
  • 가장 효과적인 방법은 실제 DDL(CREATE TABLE 문)을 프롬프트에 직접 포함하는 것
  • 외래 키 관계와 컬럼 주석을 포함하여 모델의 컨텍스트를 강화해야 함

AI 도구에 질문을 붙여넣습니다: "지난 분기에 이탈한 고객들의 플랜별 매출을 보여줘." 모델이 쿼리를 반환합니다. 그럴싸해 보입니다. 실행합니다. 카테시안 곱 (Cartesian product) 발생. 4천만 개의 행. 데이터베이스가 멈춰버립니다.

문제는 대개 AI가 아니라 프롬프트 (Prompt)에 있습니다. 다중 테이블 SQL 생성은 일반적인 프롬프팅이 무너지는 지점입니다. 단일 테이블 SELECT는 관대합니다. 모델이 추측할 수 있기 때문입니다. 하지만 세 개의 테이블, 두 개의 JOIN, 날짜 필터, 그리고 GROUP BY가 필요한 순간, 모델은 당신의 스키마 (Schema)를 정확히 알아야 합니다. 그렇지 않으면 모델은 컬럼 (Column) 이름을 지어내거나, 잘못된 조인 키 (Join key)를 선택하거나, 관계를 완전히 놓쳐버립니다.

이 가이드는 AI가 정확하고 실행 가능한 다중 테이블 SQL을 생성하도록 만드는 구체적인 기술들을 다룹니다. 이는 Draxlr와 같은 목적 기반 도구에서 기대할 수 있는 것과 동일한 결과이지만, 현재 AI를 프롬프팅하는 곳이라면 어디든 적용 가능합니다.

왜 다중 테이블 쿼리가 AI 프롬프트를 망가뜨리는가

SQL을 생성하는 대규모 언어 모델 (LLM)은 핵심적인 과제에 직면합니다. 당신이 알려주지 않으면 모델은 당신의 스키마를 알지 못한다는 것입니다. 단순한 쿼리의 경우 모델은 추측을 할 수 있습니다. SELECT * FROM users WHERE id = 1은 틀리기가 어렵습니다. 하지만 다중 테이블 쿼리의 경우, 모델은 다음 사항을 알아야 합니다:

  • 어떤 테이블이 존재하며 각 컬럼이 무엇을 의미하는지
  • 어떤 컬럼이 외래 키 (Foreign keys)이며 무엇을 참조하는지
  • 당신의 시스템에서 "매출 (Revenue)"이 무엇을 의미하는지 (orders.total인지, invoices.amount_due인지, 아니면 subscription_events.mrr인지?)
  • 조인 (Join)이 INNER여야 하는지 아니면 LEFT여야 하는지 (이는 이탈한 고객이 결과에 나타날지 여부에 영향을 미칩니다)

이러한 컨텍스트 (Context)가 누락되면, 모델은 그 빈틈을 그럴싸하게 들리는 추측으로 채웁니다. 실제 외래 키가 orders.account_uuid인데도 JOIN customers ON customers.id = orders.customer_id와 같은 결과를 얻게 되는 방식이 바로 이렇습니다.

기술 1: 관련 테이블에 대한 전체 스키마 주입하기

가장 큰 개선을 이룰 수 있는 단 한 가지 방법은 CREATE TABLE 문(또는 그에 상응하는 스키마 정의)을 프롬프트에 직접 포함하는 것입니다. 요약하지 마세요. 실제 DDL을 그대로 붙여넣으세요.

취약한 프롬프트 (Weak prompt):

users, orders, subscriptions 테이블이 있습니다.
이탈한 고객의 플랜별 매출을 보여주는 쿼리를 작성해 주세요.

강력한 프롬프트 (Strong prompt):

-- 스키마 컨텍스트 (Schema context):
CREATE TABLE users (
  id UUID PRIMARY KEY,
...

이 프롬프트를 사용하면 모델은 조인 키 (join keys), 상태 값 (status values), 매출 데이터의 위치, 그리고 "이탈 (churned)"이 무엇을 의미하는지 알게 됩니다. 결과물은 훨씬 더 정확해질 것입니다.

스키마 주입 (Schema injection)을 위한 핵심 규칙:

  • 쿼리에 간접적으로라도 관련된 모든 테이블을 포함하세요.
  • 컬럼 주석 (column comments)을 유지하세요. 비즈니스 용어 파악에 매우 유용합니다.
  • REFERENCES 절을 포함하여 모델이 외래 키 (foreign key) 관계를 명시적으로 볼 수 있게 하세요.
  • ENUM 또는 제한된 값의 집합이 있다면 목록을 나열하세요.

기술 2: 조인 경로를 명시적으로 지정하기

전체 스키마 컨텍스트가 있더라도, 테이블 사이에 여러 경로가 존재할 경우 모델이 잘못된 조인 경로 (join path)를 선택할 수 있습니다. 만약 invoices 테이블을 invoices.user_id를 통해 users와 직접 조인할 수도 있고, subscriptions를 통해 간접적으로 조인할 수도 있다면, 모델에게 어떤 경로를 사용할지 알려주세요.

invoices.subscription_id = subscriptions.id를 사용하여 invoices를 subscriptions와 조인하고,
그 다음 subscriptions.user_id = users.id를 사용하여 subscriptions를 users와 조인하세요.
invoices를 users와 직접 조인하지 마세요.

이렇게 하면 팬아웃 버그 (fan-out bugs)를 유발하는 모호함을 제거할 수 있습니다. 팬아웃 버그란 두 경로를 동시에 따라 조인할 때 행 수 (row counts)가 예상치 못하게 배수로 늘어나는 현상을 말합니다.

기술 3: 복잡한 로직을 위해 사고 사슬 (Chain-of-Thought) 사용하기

서브쿼리 (subqueries), 윈도우 함수 (window functions), 또는 다단계 집계 (multi-step aggregation)가 포함된 쿼리의 경우, 모델에게 SQL을 작성하기 전에 먼저 추론하도록 요청하세요. 사고 사슬 (Chain-of-thought) 프롬프팅은 어려운 쿼리에서 논리적 오류를 극적으로 줄여줍니다.

프롬프트에 다음 내용을 추가하세요:

쿼리를 작성하기 전에 다음 사항을 충분히 생각하세요:
1. 어떤 테이블이 필요하며 어떻게 조인 (join)되는가
2. 집계 (aggregation) 전과 후에 어떤 필터링 (filtering)이 수행되어야 하는가
...

모델의 추론 (reasoning) 출력은 그렇지 않으면 버그로 이어졌을 문제들을 종종 잡아냅니다. 예를 들어, 모델은 다음과 같이 메모할 수 있습니다: "WHERE 절에서 canceled_at을 필터링하면 활성 구독 (active subscriptions)의 null 값이 제외될 것입니다 — 대신 HAVING 절이나 서브쿼리 (subquery)를 사용해야 합니다." 이것이 바로 잘못된 결과를 방지하는 통찰력입니다.

다음은 요금제별 매출 (revenue-by-plan) 쿼리에 대해 잘 구조화된 CTE 기반 출력의 예시입니다:

-- 1단계: 2026년 1분기에 취소된 구독 식별
WITH churned_subs AS (
  SELECT
...

CTE 구조는 사고 사슬 (chain-of-thought) 추론의 직접적인 결과입니다. 모델이 SQL을 한 줄도 쓰기 전에 문제를 단계별로 분해한 것입니다.

기법 4: 퓨샷 (Few-Shot) 예시 제공하기

동일한 데이터베이스에 반복적으로 쿼리를 수행한다면, 프롬프트에 작동하는 예시를 하나 포함하는 것만으로도 정확도를 극적으로 향상시킬 수 있습니다. 모델에게 이미 정답을 맞혔던 질문과 그 정답을 만들어낸 SQL을 함께 보여주세요.

예시:
질문: "각 사용자별 활성 구독 (active subscriptions) 수는 얼마인가요?"
SQL:
...

이 예시는 사용자가 다시 설명할 필요 없이 모델에게 명명 규칙 (naming conventions), 선호하는 조인 (JOIN) 스타일, 그리고 어떤 테이블이 "기본" 진실의 원천 (primary source of truth)인지 가르쳐 줍니다.

기법 5: 출력 형식 제약하기

AI 도구는 종종 여러 가지 변형, 장황한 설명, 또는 플레이스홀더 (placeholder) 값이 포함된 쿼리를 생성합니다. 출력을 애플리케이션이나 테스트 파이프라인에 직접 입력하는 경우, 명시적인 출력 제약 조건을 추가하세요:

SQL 쿼리만 반환하세요. 설명은 생략하세요. 마크다운 펜스 (markdown fences)도 사용하지 마세요.
모든 컬럼 이름에 별칭 (alias)을 사용하세요. 중첩된 서브쿼리 (nested subqueries) 대신 CTE를 사용하세요.
SELECT *를 사용하지 마세요.

이렇게 하면 출력이 기계 판독 가능 (machine-readable)해지며, 팀에서 표준화한 스타일을 강제할 수 있습니다.

흔한 실수와 이를 피하는 방법

LEFT JOIN에서의 NULL 의미론(semantics) 망각. 일치하는 항목이 없는 행(예: 송장(invoice)이 0개인 사용자)을 포함하고 싶을 때는 모델에게 LEFT JOIN을 사용하도록 요청하세요. 만약 INNER JOIN을 사용하면 해당 행들은 소리 없이 사라집니다.

테이블 간 모호한 컬럼 이름. 만약 userssubscriptions 테이블 모두에 created_at 컬럼이 있다면, 모델은 잘못된 컬럼을 참조할 수 있습니다. 모든 컬럼에 테이블명을 명시(Qualify)하세요: users.created_at, subscriptions.created_at.

정의가 없는 비즈니스 용어. "활성 고객(Active customers)"은 시스템마다 의미가 다릅니다. status = 'active'를 의미하나요? 최근 30일 이내에 로그인했음을 의미하나요? 유료 송장이 있음을 의미하나요? 프롬프트에 명확히 정의하세요: "활성(Active)이란 구독의 status = 'active'이고 canceled_at IS NULL인 상태를 의미합니다."

모델이 이전 문맥을 기억할 것이라는 가정. 각 새로운 프롬프트는 (보통) 상태를 유지하지 않는 (stateless) 방식입니다. 매번 스키마(schema)와 이전에 설정한 정의들을 다시 주입하세요.

핵심 요약 (Key Takeaways)

AI로부터 정확한 다중 테이블 SQL을 얻어내는 것은 적절한 도구를 선택하는 문제가 아닙니다. 어떤 도구라도 올바르게 추론할 수 있도록 충분한 문맥(context)을 제공하는 것이 핵심입니다. 가장 중요한 기술들은 다음과 같습니다:

  • 외래 키(foreign keys)와 컬럼 주석(column comments)을 포함한 CREATE TABLE 문을 붙여넣으세요.
  • 여러 경로가 존재하는 경우 조인 경로(join path)를 명시적으로 지정하세요.
  • 서브쿼리(subqueries)나 복잡한 집계(aggregation)가 포함된 쿼리에는 생각의 사슬 (Chain-of-thought) 프롬프팅을 사용하세요.
  • 명명 규칙(naming conventions)의 기준이 될 수 있도록 작동하는 예시 쿼리를 하나 포함하세요.
  • 일관되고 기계 판독 가능한 (machine-readable) 결과를 위해 출력 형식을 제한하세요.

문맥을 더 많이 제공할수록 모델이 추측해야 하는 부분은 줄어듭니다. 그리고 추측이야말로 다중 테이블 SQL 생성에서 오류가 발생하는 지점입니다.

AI를 통해 성공적으로 생성한 가장 복잡한 다중 테이블 쿼리는 무엇인가요? 큰 차이를 만들어낸 프롬프팅 기술을 발견하셨나요? 댓글로 공유해 주세요. 실제 운영 환경(production)에서 무엇이 효과적인지 정말 궁금합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0