11 AI와 함께하는 데이터 탐구
1927년 2월 8일, 남극 대륙의 DR-1 지점. 탐사대원 William Dyer가 방사능 측정기를 들고 빙하 위를 걷고 있다. 그가 측정한 9.82의 방사능 수치는 데이터베이스에 기록되어, 거의 100년이 지난 지금까지 우리에게 과학적 통찰을 제공하고 있다.
이 장에서는 바로 그 남극 탐사 데이터(survey.db
)를 통해 AI와 함께하는 데이터 탐구의 진화를 경험해보겠다. 단순한 질문에서 시작해서 복잡한 분석까지, AI가 어떻게 우리의 호기심을 정확하고 효율적인 SQL 쿼리로 변환해주는지 살펴보자.
11.1 남극 탐사 데이터베이스
정적으로 작성된 스키마를 사용하다 보면 시간이 지나면서 여러 문제가 발생한다. 데이터베이스 구조가 변경될 때마다 문서를 업데이트해야 하는데, 이를 놓치면 AI가 존재하지 않는 테이블이나 컬럼을 참조하는 잘못된 쿼리를 생성하게 된다. 더 큰 문제는 실제 데이터의 패턴을 모르면 JOIN 조건을 잘못 추측하거나 데이터 타입 불일치로 인한 오류가 발생한다는 점이다. 따라서 실제 데이터베이스에서 동적으로 정보를 추출하는 방법이 필수적이다.
스키마 정보는 AI에게 있어 지도와 같은 역할을 한다. “1930년에 측정한 사람들을 보여줘”라는 질문을 받으면, 스키마 정보가 없는 AI는 SELECT * FROM measurements WHERE year = 1930
같은 추측성 쿼리를 생성한다. 하지만 실제로는 Survey 테이블에 측정 데이터가 있고, 날짜 정보는 Visited 테이블의 dated 컬럼에 저장되어 있으며, 사람 정보와 연결하려면 복잡한 JOIN이 필요하다. 반면 스키마 정보를 제공받은 AI는 Person
, Survey
, Visited
테이블의 관계를 정확히 파악하고, substr(v.dated,1,4) = '1930'
처럼 날짜 추출까지 정확하게 수행하는 완벽한 쿼리를 생성한다.
샘플 데이터는 AI의 패턴 인식을 돕는 핵심 요소다. “Dyer씨가 측정한 방사능 수치를 보여줘”라는 질문에서 AI는 person = 'Dyer'
로 검색하려 하지만, 실제 데이터를 보면 ID는 ‘dyer’처럼 소문자로 저장되어 있고, 방사능은 ’radiation’이 아닌 ’rad’ 약어를 사용한다. 샘플 데이터 dyer | William | Dyer
와 619 | dyer | rad | 9.82
를 보고 나서야 AI는 Person 테이블의 id 컬럼이 Survey 테이블의 person 컬럼과 연결되며, 실제 데이터 형식과 약어 패턴을 정확히 이해하게 된다. 이제 데이터베이스가 변경되어도 항상 최신 정보를 AI에게 제공할 수 있는 구체적인 방법들을 살펴보자.
가장 빠른 방법은 SQLite 명령어를 직접 사용하는 것이다. 터미널에서 바로 실행하여 결과를 AI 프롬프트에 복사-붙여넣기할 수 있으며, 별도의 프로그래밍 지식 없이도 즉시 활용할 수 있다는 장점이 있다:
# 스키마 정보 추출
sqlite3 data/survey.db ".schema"
# CREATE TABLE Person (id text, personal text, family text);
# CREATE TABLE Site (name text, lat real, long real);
# CREATE TABLE Survey (taken integer, person text, quant text, reading real);
# CREATE TABLE Visited (id integer, site text, dated text);
# 각 테이블의 샘플 데이터 추출 (3개씩)
sqlite3 data/survey.db "SELECT * FROM Person LIMIT 3"
# dyer|William|Dyer
# pb|Frank|Pabodie
# lake|Anderson|Lake
sqlite3 data/survey.db "SELECT * FROM Site LIMIT 3"
sqlite3 data/survey.db "SELECT * FROM Survey LIMIT 3"
sqlite3 data/survey.db "SELECT * FROM Visited LIMIT 3"
더 체계적인 접근을 원한다면 파이썬으로 자동화 함수를 만들 수 있다. 프로그래밍 방식은 한 번 작성하면 여러 데이터베이스에 재사용할 수 있고, AI 프롬프트 형태로 바로 출력할 수 있다는 장점이 있다:
import sqlite3
def get_context(db_path):
"""스키마 + 샘플 데이터를 한 번에 추출"""
= sqlite3.connect(db_path)
conn = conn.cursor()
cursor
# 스키마 추출
"SELECT sql FROM sqlite_master WHERE type='table'")
cursor.execute(= [row[0] for row in cursor.fetchall()]
schemas
# 샘플 데이터 추출 (각 테이블별 3개씩)
= ['Person', 'Site', 'Survey', 'Visited']
tables = {}
samples for table in tables:
f"SELECT * FROM {table} LIMIT 3")
cursor.execute(= cursor.fetchall()
samples[table]
conn.close()
# 포매팅
= "## 스키마:\n" + "\n".join(schemas)
context += "\n\n## 샘플 데이터:\n"
context for table, rows in samples.items():
+= f"\n{table}:\n"
context for row in rows:
+= f" {' | '.join(str(x) for x in row)}\n"
context
return context
# 사용법
= get_context("data/survey.db")
context print(context)
이 함수는 매번 동일한 형태의 컨텍스트를 생성하여 일관성을 보장하고, 한 번의 호출로 모든 정보를 수집하는 효율성을 제공한다. 더욱 중요한 것은 다른 SQLite 데이터베이스에도 동일하게 적용할 수 있어 확장성이 뛰어나며, 데이터 파이프라인에 통합하여 배치 처리도 가능하다는 점이다.
## 스키마:
CREATE TABLE Person (id text, personal text, family text)
CREATE TABLE Site (name text, lat real, long real)
CREATE TABLE Survey (taken integer, person text, quant text, reading real)
CREATE TABLE Visited (id integer, site text, dated text)
## 샘플 데이터:
Person:
dyer | William | Dyer
pb | Frank | Pabodie
lake | Anderson | Lake
Survey:
619 | dyer | rad | 9.82
619 | dyer | sal | 0.13
622 | dyer | rad | 7.8
11.2 Text-to-SQL 단계별 접근법
AI Text-to-SQL의 완전한 여정은 데이터베이스에 대한 깊은 이해에서 시작된다. 먼저 스키마와 샘플 데이터를 추출하여 AI에게 맥락을 제공하는 컨텍스트 준비 단계를 거친다. 이 정보는 중요도에 따라 계층화되어 스키마 정보가 90%, 샘플 데이터가 70%, 비즈니스 규칙이 50%, 성능 고려사항이 30%의 우선순위를 갖는다. 이어서 프롬프트 엔지니어링 단계로 넘어가면, 처음에는 단순한 Zero-shot 질문에서 시작하지만, 점차 Few-shot 예시를 통한 패턴 학습을 거쳐, 최종적으로는 Chain-of-Thought 방식의 단계별 사고 과정까지 발전한다.
AI 처리 엔진은 이 정보들을 바탕으로 컨텍스트 분석, 테이블 관계 파악, JOIN 조건 결정, WHERE 절 구성, SQL 문법 최적화라는 5단계 과정을 거쳐 정확한 쿼리를 생성한다. 하지만 여기서 끝이 아니다. 실행 결과를 확인하고 NULL 처리 문제를 해결하며, 성능을 최적화하고, 데이터 품질을 검증하는 반복적 개선 과정을 통해 완성도를 높인다. 마지막으로 Python 자동화 도구를 통해 전 과정을 시스템화함으로써 일관된 품질의 SQL 생성 파이프라인을 구축하게 된다.
AI와 함께 하는 데이터 탐구는 단순한 질문에서 시작해서 점진적으로 발전하는 과정을 거친다. 이 여정은 크게 세 단계로 나눌 수 있으며, 각 단계마다 뚜렷한 특징과 한계, 극복 방법이 있다.
첫 번째 단계: 기초 대화 (Foundation)는 가장 자연스러운 시작점이다. “어떤 탐사대원이 가장 많은 측정을 했나요?”같은 직관적인 질문으로 시작하지만, AI는 데이터베이스 구조를 모르기 때문에 추측에 의존할 수밖에 없다. 결과적으로 틀린 테이블명, 잘못된 JOIN, 부정확한 필터 조건 등으로 인해 오류와 수정을 반복하게 된다. 하지만 이 과정에서 데이터의 특성과 문제점을 발견하게 되며, 더 정교한 접근의 필요성을 깨닫는다.
두 번째 단계: 정보 제공 (Information)에서는 체계적인 컨텍스트 제공이 핵심이다. 스키마 정보와 샘플 데이터를 구조화된 프롬프트 템플릿으로 제공하면, AI는 추측 대신 정확한 정보를 바탕으로 SQL을 생성한다. 테이블 관계를 정확히 파악하고, 실제 데이터 패턴을 이해하며, 적절한 JOIN 조건과 필터링을 수행한다. 정확도가 현저히 향상되지만, 여전히 복잡한 비즈니스 로직이나 다단계 분석에서는 한계를 보인다.
세 번째 단계: 고급 기법 (Advanced)에서는 AI의 학습 능력을 최대한 활용한다. Few-shot learning으로 여러 예시를 보여주어 패턴을 학습시키고, Chain-of-Thought 방식으로 복잡한 문제를 단계별로 분해하여 해결한다. 이상치 제거, 시계열 분석, 데이터 품질 검증 같은 고급 분석도 가능해지며, 반복적 개선을 통해 성능 최적화까지 달성한다. 더 나아가 Python 자동화로 전 과정을 시스템화하여 일관된 품질의 결과를 대량으로 생산할 수 있게 된다.
11.3 단순한 질문에서 시작하기
연구실의 월요일 아침, 김박사는 평소보다 일찍 출근해서 책상 앞에 앉았다. 주말 동안 받아본 남극 탐사 데이터베이스 파일이 컴퓨터 화면에 떠 있었다. 1927년부터 1932년까지의 방대한 측정 기록들이 SQLite 파일 하나에 담겨 있었지만, 어디서부터 시작해야 할지 막막했다. 데이터 분석 프로젝트를 맡은 지 얼마 되지 않아 아직 복잡한 SQL을 손으로 작성하는 것이 부담스러웠던 그는, 최근 화제가 되고 있는 AI 도구를 활용해보기로 결심했다.
김박사는 따뜻한 커피를 한 모금 마시며 생각했다. ‘일단 간단한 것부터 시작해보자.’ 연구 보고서를 위해 가장 기본적이면서도 흥미로운 정보를 얻고 싶었다. 누가 가장 많은 데이터를 수집했는지, 어떤 지역에서 주로 측정이 이루어졌는지, 언제 가장 활발한 탐사 활동이 있었는지 같은 기초적인 통계가 필요했다. 그는 AI 채팅 인터페이스에 가장 단순하면서도 자연스러운 질문을 입력했다:
“어떤 탐사대원이 가장 많은 측정을 했나요?”
AI는 몇 초 만에 응답했다. 스키마 정보나 데이터베이스 구조에 대한 추가 설명 없이 순수하게 질문 내용만으로 쿼리를 생성하는 Zero-shot 방식이었다. AI는 ‘Survey’라는 테이블에서 ’person’ 컬럼을 기준으로 그룹화하고 개수를 세는, 직관적으로 합리적인 접근을 택했다:
SELECT person, COUNT(*) as measurement_count
FROM Survey
GROUP BY person
ORDER BY measurement_count DESC;
김박사는 쿼리를 실행해보았다. 문법적으로는 완벽했고, 논리적으로도 타당해 보였다. 결과가 화면에 나타났다:
person|measurement_count
lake|7
pb|5
dyer|4
roe|3
Kim Jin-Ho|2
|2
Park So-Young|1
하지만 결과를 보는 순간 김박사는 당황했다. ‘lake’가 7번의 측정을 했다고? ’pb’는 또 누구인가? ’dyer’는 사람 이름처럼 보이긴 하지만 확신할 수 없었다. 더 이상한 것은 빈 값(NULL)도 나타났고, ’Kim Jin-Ho’와 ’Park So-Young’ 같은 이름은 1927년 남극 탐사 맥락에서 전혀 어울리지 않았다. 이 데이터가 정말 남극 탐사 기록이 맞는지 의심스러웠다.
김박사는 잠시 생각에 잠겼다. AI가 생성한 쿼리 자체는 틀리지 않았지만, 결과가 해석하기 어려웠다. ‘lake’, ‘pb’, ‘dyer’ 같은 값들이 사람을 나타내는 것 같긴 하지만 실제 이름인지 코드인지 판단할 수 없었다. 데이터베이스 구조를 좀 더 자세히 살펴보니, 아마도 Person이라는 별도 테이블이 있어서 실제 이름 정보를 담고 있을 것 같았다. 그는 AI에게 더 구체적으로 요청했다:
“잠깐, ’dyer’가 뭔가요? 실명으로 보여주세요.”
이번에는 AI가 훨씬 더 정교한 접근을 시도했다. Survey 테이블과 Person 테이블을 조인하여 실제 이름을 가져오는 쿼리를 제안했다:
SELECT p.personal || ' ' || p.family as full_name,
COUNT(*) as measurement_count
FROM Survey s
JOIN Person p ON s.person = p.id
GROUP BY s.person, p.personal, p.family
ORDER BY measurement_count DESC;
김박사가 이 쿼리를 실행하자, 비로소 의미 있는 결과가 나타났다:
full_name|measurement_count
Anderson Lake|7
Frank Pabodie|5
William Dyer|4
Valentina Roerich|3
’lake’는 Anderson Lake이라는 탐사대원이었고, ’pb’는 Frank Pabodie, ’dyer’는 William Dyer였다. 이제야 결과가 합리적으로 해석되었다. Anderson Lake가 7번의 측정으로 가장 활발했고, Frank Pabodie와 William Dyer가 그 뒤를 이었다. 한국 이름들과 NULL 값들은 사라졌는데, 아마도 실제 Person 테이블에 등록되지 않은 잘못된 데이터였던 것 같았다.
김박사는 이 경험을 통해 AI와의 효과적인 소통 방법을 깨달았다. 첫 번째 시도에서는 자연스러운 질문을 던졌지만, AI는 데이터베이스의 구조를 정확히 모르는 상황에서 가장 직관적인 접근을 시도했다. 결과적으로 기술적으로는 올바른 쿼리였지만 실용적으로는 해석하기 어려운 결과를 만들었다.
두 번째 시도에서 구체적인 요구사항을 추가하자 AI는 즉시 테이블 간의 관계를 파악하고 적절한 JOIN을 수행했다. 이는 단순히 기술적 정확성을 넘어서서 사용자의 실제 의도를 이해하고 의미 있는 정보를 제공하는 것의 중요성을 보여준다. “측정 많이 한 사람 누구?”같은 모호한 질문보다는 “남극 탐사 데이터에서 어떤 탐사대원이 실명으로 가장 많은 측정을 했는지 보여주세요. Survey와 Person 테이블을 조인해서요”처럼 구체적인 맥락과 기술적 요구사항을 함께 제공하는 것이 훨씬 효과적이라는 것을 배웠다.
더 중요한 것은 이 과정이 일회성이 아니라는 점이다. AI와의 대화는 점진적 개선의 과정이며, 초기 결과를 바탕으로 문제점을 파악하고 더 정확한 질문을 던지는 반복적인 과정을 통해 원하는 답에 도달할 수 있다. 이는 전통적인 SQL 작성 방식과는 완전히 다른 접근법으로, 대화를 통한 협력적 문제 해결의 새로운 패러다임을 보여준다.
11.4 SQL 쿼리 프롬프트 엔지니어링
AI와의 대화에서 단순한 질문보다는 체계적인 정보 제공이 훨씬 정확한 SQL을 만들어낸다. 가장 기본적인 방법은 데이터베이스 스키마 정보와 함께 질문하는 것이다.
프롬프트:
역할: 당신은 남극 탐사 데이터 분석 전문가입니다.
데이터베이스 스키마:
- Person (id text, personal text, family text)
- Site (name text, lat real, long real)
- Survey (taken integer, person text, quant text, reading real)
- Visited (id integer, site text, dated text)
질문: 1927년에 측정된 방사능(rad) 데이터의 평균값을 구해주세요.
출력 형식: SQL 쿼리만 반환하고, 가독성을 위해 적절히 포매팅해주세요.
AI가 생성한 SQL:
SELECT AVG(s.reading) as avg_radiation_1927
FROM Survey s
JOIN Visited v ON s.taken = v.id
WHERE substr(v.dated, 1, 4) = '1927'
AND s.quant = 'rad'
AND s.reading IS NOT NULL;
8.81 #
스키마 정보가 없다면 AI는 테이블 구조를 추측해야 하지만, 명확한 스키마가 제공되면 즉시 정확한 JOIN 구조와 날짜 필터링 방법을 적용한 완벽한 쿼리를 생성한다.
더 나아가 AI에게 몇 가지 예시를 보여주는 Few-Shot Learning을 활용하면 패턴을 학습해서 더 일관된 결과를 만든다.
프롬프트:
예시:
질문: "어떤 지점에서 가장 많은 측정이 이루어졌나요?"
SQL: SELECT v.site, COUNT(*) as count FROM Survey s JOIN Visited v
ON s.taken = v.id GROUP BY v.site ORDER BY count DESC LIMIT 1;
이제 다음 질문에 답해주세요:
질문: "1930년도에 DR-3 지점에서 측정된 모든 데이터를 탐사대원 실명과 함께 보여주세요."
AI가 생성한 SQL:
SELECT p.personal || ' ' || p.family as explorer_name,
as measurement_type,
s.quant as value
s.reading FROM Survey s
JOIN Person p ON s.person = p.id
JOIN Visited v ON s.taken = v.id
WHERE v.site = 'DR-3'
AND substr(v.dated, 1, 4) = '1930'
ORDER BY explorer_name;
0.05
# Anderson Lake|sal|0.1
# Anderson Lake|sal|8.41
# Frank Pabodie|rad|-21.5
# Frank Pabodie|temp|7.22
# Frank Pabodie|rad|4.35
# Frank Pabodie|rad|-18.5 # Frank Pabodie|temp|
AI는 예시에서 보여준 패턴을 학습하여 새로운 질문에서도 동일한 형태의 JOIN 구조와 p.personal || ' ' || p.family
를 사용한 실명 표시 방법을 자동으로 적용했다.
가장 복잡한 질문에는 Chain-of-Thought 프롬프팅이 효과적이다.
프롬프트:
1927년과 1930년 사이에 각 탐사대원이 측정한 방사능 데이터의 평균을 구하되,
2번 이상 측정한 대원들만 포함해주세요. 단계별로 생각해서 SQL을 작성해주세요.
AI가 생성한 SQL:
-- 단계별 접근:
-- 1) 1927-1930년 사이의 방사능 측정 데이터 필터링
-- 2) 각 탐사대원별로 그룹화
-- 3) 측정 횟수가 2번 이상인 대원만 필터링
-- 4) 각 대원의 평균 방사능 수치 계산
SELECT p.personal || ' ' || p.family as explorer_name,
COUNT(*) as measurement_count,
ROUND(AVG(s.reading), 2) as avg_radiation
FROM Survey s
JOIN Person p ON s.person = p.id
JOIN Visited v ON s.taken = v.id
WHERE s.quant = 'rad'
AND substr(v.dated, 1, 4) BETWEEN '1927' AND '1930'
AND s.reading IS NOT NULL
GROUP BY s.person, p.personal, p.family
HAVING COUNT(*) >= 2
ORDER BY avg_radiation DESC;
2|8.81
# William Dyer|3|6.66 # Frank Pabodie|
AI는 복잡한 요구사항을 단계별로 분해하여 각 조건을 정확히 반영하는 완성도 높은 쿼리를 생성했다.
11.5 대화를 통한 반복적 개선
실제 데이터 분석에서는 한 번에 완벽한 쿼리가 나오지 않는다. 오히려 초기 결과를 보고 문제를 발견한 다음, AI와의 지속적인 대화를 통해 점진적으로 개선해 나가는 과정이 핵심이다.
연구원이 “각 지점별 평균 방사능 수치를 보여주세요”라고 질문하면 AI는 기본적인 집계 쿼리 SELECT v.site, AVG(s.reading) FROM Survey s JOIN Visited v ON s.taken = v.id WHERE s.quant = 'rad' GROUP BY v.site
를 생성한다. 그런데 결과를 확인해보니 일부 측정값이 NULL이어서 평균 계산이 왜곡된 상황이다. 연구원이 “측정값에 NULL이 있네요. 유효한 측정값만으로 평균을 다시 계산해주세요”라고 요청하자, AI는 즉시 COUNT(s.reading)
과 COUNT(*)
를 구분해서 유효한 측정값의 개수와 전체 시도 횟수를 모두 보여주는 개선된 쿼리를 제안한다.
이어서 “NULL 측정의 비율도 같이 보여주세요”라는 추가 요구에 AI는 ROUND(100.0 * COUNT(s.reading) / COUNT(*), 1) as success_rate
를 추가한 최종 쿼리로 발전시킨다:
SELECT v.site,
COUNT(s.reading) as valid_measurements,
COUNT(*) as total_attempts,
ROUND(100.0 * COUNT(s.reading) / COUNT(*), 1) as success_rate,
ROUND(AVG(s.reading), 2) as avg_radiation
FROM Survey s
JOIN Visited v ON s.taken = v.id
WHERE s.quant = 'rad'
GROUP BY v.site
ORDER BY success_rate DESC, avg_radiation DESC;
-1|3|3|100.0|9.62
# DR-3|4|4|100.0|5.54
# DR-4|1|1|100.0|1.46 # MSK
성능 문제가 발생하면 AI는 다시 최적화 방안을 제안한다. “위 쿼리를 더 빠르게 실행할 방법이 있나요?”라는 질문에 AI는 CTE(Common Table Expression)를 활용해 먼저 방사능 데이터만 필터링하는 radiation_data
테이블을 만들고, 이를 바탕으로 집계하는 방식을 제안한다. 더 나아가 Survey(quant)
, Survey(taken)
, Visited(id)
컬럼에 인덱스를 생성하여 JOIN 성능을 향상시키는 구체적인 방법까지 제시한다. 이처럼 AI와의 대화는 단순한 질의응답이 아니라 문제 발견, 해결책 제시, 추가 최적화까지 이어지는 완전한 개발 프로세스가 된다.
11.6 고급 분석 발전
연구가 깊어질수록 더 복잡하고 정교한 분석이 필요해진다. 처음에는 단순한 평균이나 개수를 구했다면, 이제는 이상치 제거, 시계열 트렌드, 데이터 품질 검증 같은 고급 기법이 등장한다. “이상치를 제외한 평균 방사능 수치를 구해주세요”라는 요청에 AI는 IQR(Interquartile Range) 방법으로 1-3분위수를 계산하고, q1 - 1.5 * (q3 - q1)
부터 q3 + 1.5 * (q3 - q1)
범위를 벗어나는 값들을 제외한 정제된 평균을 제시한다. 여러 CTE를 연결한 복잡한 쿼리지만, AI는 각 단계의 목적을 명확히 이해하고 SQLite의 윈도우 함수를 적절히 활용해 정확한 결과를 만들어낸다.
시간의 흐름에 따른 패턴 분석도 자연스럽게 요구된다. “연도별로 측정 활동의 트렌드를 분석해주세요”라고 하면 AI는 단순한 연도별 집계를 넘어서서 참여 탐사대원 수, 방문 지점 수, 측정 유형별 분포, 1인당 측정 횟수까지 종합적인 지표들을 계산한다. substr(v.dated, 1, 4)
로 연도를 추출하고, 조건부 집계 SUM(CASE WHEN s.quant = 'rad' THEN 1 ELSE 0 END)
로 측정 유형별 분포를 구하며, ROUND(1.0 * total_measurements / unique_explorers, 1)
로 효율성 지표까지 제시하는 포괄적인 분석을 수행한다.
더 나아가 데이터 품질 검증은 모든 분석의 기반이 되는 핵심 과정이다. “데이터 품질 문제를 종합적으로 분석해주세요”라는 요청에 AI는 UNION ALL을 활용해 다섯 가지 품질 이슈를 동시에 점검한다: NULL 측정값, 고아 레코드(방문 기록이 없는 측정), 미지의 인물 ID, 극단적 방사능 값, 미래 날짜까지 체크해서 각각의 발생 빈도와 백분율을 제시한다. 이런 종합적 품질 분석을 통해 연구자는 데이터의 신뢰성을 정량적으로 평가하고 후속 분석 방향을 결정할 수 있다.
11.7 AI Text-to-SQL 시스템
김박사는 수많은 시행착오 끝에 깨달았다. 첫 번째 시도에서 ‘lake’, ‘pb’ 같은 암호를 얻었고, 두 번째에서 스키마를 추가해 실명을 얻었으며, Few-shot learning으로 패턴을 학습시키고, Chain-of-Thought로 복잡한 논리까지 처리할 수 있게 되었다. 이 모든 경험을 종합하여, 그는 마침내 완성된 AI Text-to-SQL 시스템의 핵심을 이해하게 되었다. 고품질 SQL 생성의 열쇠는 바로 체계적인 컨텍스트 제공에 있다는 것을.
11.7.1 모든 결정은 컨텍스트
Text-to-SQL의 품질은 다음 공식으로 결정된다: SQL 쿼리 품질 = 컨텍스트 품질 × AI 능력 × 프롬프트 기법. 이 공식에서 AI 능력은 이미 정해진 상수이고, 프롬프트 기법은 상대적으로 쉽게 개선할 수 있는 요소지만, 가장 결정적인 영향을 미치는 것은 컨텍스트 품질이다. 왜냐하면 아무리 강력한 AI 엔진이라도, 아무리 정교한 프롬프트 엔지니어링 기법을 사용해도, 기본적인 정보가 부족하면 결국 추측에 의존할 수밖에 없기 때문이다.
컨텍스트 품질이 미치는 영향의 차이는 극명하다. 부족한 컨텍스트를 받은 AI는 “1930년에 측정한 사람들”이라는 질문에 대해 SELECT * FROM measurements WHERE year = 1930
같은 추측성 쿼리를 생성한다. 존재하지 않는 테이블명을 사용하고, 날짜 데이터의 실제 저장 형태를 모르며, 테이블 간의 관계를 파악하지 못한다. 반면 충분한 컨텍스트를 받은 AI는 Survey와 Visited 테이블의 관계를 정확히 파악하고, substr(v.dated,1,4) = '1930'
처럼 날짜 추출 방식까지 정확하게 적용하는 완벽한 쿼리를 즉시 생성한다.
더 근본적인 문제는 잘못된 컨텍스트가 가져오는 연쇄적 오류다. 스키마 정보가 부족하면 AI는 잘못된 JOIN 조건을 만들고, 샘플 데이터가 없으면 데이터 형식을 오해하며, 도메인 지식이 결여되면 비즈니스 로직을 잘못 해석한다. 이런 오류들은 단순히 쿼리 실행 오류로 끝나는 것이 아니라, 겉보기에는 정상 작동하지만 실제로는 완전히 잘못된 결과를 만들어내는 더 위험한 상황을 초래한다.
반대로 체계적이고 풍부한 컨텍스트는 AI의 능력을 극대화시킨다. 정확한 스키마 정보는 테이블 관계 파악을 완벽하게 하고, 적절한 샘플 데이터는 실제 데이터 패턴 학습을 가능하게 하며, 도메인 지식은 비즈니스 요구사항의 정확한 해석을 돕는다. 이런 요소들이 결합되면 AI는 단순한 SQL 생성기를 넘어서서, 사용자의 진정한 의도를 파악하고 최적화된 해법을 제시하는 지능적 파트너가 된다.
11.7.2 4계층 우선순위 시스템
컨텍스트를 효과적으로 제공하려면 우선순위가 있는 계층 구조를 이해해야 한다. 모든 정보가 동일한 중요도를 갖는 것은 아니다. 김박사의 경험에 따르면, 정보의 중요도는 명확한 계층을 이루며, 이러한 순서를 무시하고 무작정 많은 정보를 제공하는 것은 오히려 AI의 판단을 흐릴 수 있다. 가장 핵심적인 정보부터 차례대로 제공하되, 질문의 복잡도에 따라 어디까지 포함할지를 전략적으로 결정해야 한다는 것이 핵심 통찰이다.
첫 번째 계층인 데이터베이스 스키마는 90%의 중요도를 가지는 절대적으로 필수적인 요소다. 단순한 테이블명 나열이 아니라 완전한 스키마 정보를 제공해야 한다:
-- ✅ 완전한 스키마 정보 예시
CREATE TABLE Person (
id text PRIMARY KEY, -- 탐사대원 ID (고유)
NOT NULL, -- 이름
personal text NOT NULL -- 성
family text
);
CREATE TABLE Survey (
integer, -- 측정 ID
taken -- 측정한 탐사대원 (Person.id 참조)
person text, CHECK(quant IN ('rad', 'sal', 'temp')), -- 측정 항목
quant text real, -- 측정값 (NULL 가능)
reading
FOREIGN KEY (person) REFERENCES Person(id),
FOREIGN KEY (taken) REFERENCES Visited(id)
);
스키마 정보는 테이블명과 컬럼명부터 시작하여 데이터 타입(text, integer, real 등), 제약조건(NOT NULL, CHECK, UNIQUE), Primary Key와 Foreign Key 관계, 그리고 성능 최적화를 위한 인덱스 정보까지 체계적으로 포함해야 한다. 이러한 세부 사항들은 AI가 데이터베이스의 구조를 완전히 이해하고 정확한 JOIN 조건과 최적화된 쿼리를 생성하는 데 필수적이다.
두 번째 계층인 샘플 데이터는 70%의 중요도를 가지는 권장 요소다. 스키마만으로는 알 수 없는 실제 데이터의 양상과 특성을 AI에게 교육시킨다. 대표적인 예시 데이터 3-5개를 보여주는 것만으로도 AI는 데이터의 전체 패턴을 이해하고 더 정확한 쿼리를 생성할 수 있다:
-- ✅ 대표적이고 교육적인 샘플 데이터
SELECT 'Person 테이블 샘플:' as info;
SELECT 'dyer', 'William', 'Dyer' UNION
SELECT 'pb', 'Frank', 'Pabodie' UNION
SELECT 'lake', 'Anderson', 'Lake';
SELECT 'Survey 테이블 샘플 (NULL 케이스 포함):' as info;
SELECT 619, 'dyer', 'rad', 9.82 UNION
SELECT 622, 'dyer', 'rad', 7.8 UNION
SELECT 735, NULL, 'sal', 0.06; -- NULL person 케이스
효과적인 샘플 데이터를 선택할 때는 네 가지 기준을 동시에 고려해야 한다. 첫째는 대표성으로, 일반적인 데이터 패턴을 보여주는 생각이다. 둘째는 다양성으로 서로 다른 특성을 가진 케이스들을 포함한다. 셋째는 문제 상황으로 NULL 값, 특수값, 엣지 케이스를 반드시 포함시켜 AI가 예외 상황에도 견고하게 대응할 수 있도록 한다. 마지막은 관계성으로, 테이블 간 JOIN이 어떻게 연결되는지 보여주는 대표적 사례를 포함해야 한다.
세 번째 계층인 비즈니스 규칙과 도메인 지식은 50%의 중요도를 가지며, AI가 전혀 추측할 수 없는 영역별 전문 지식을 제공한다. 남극 탐사 데이터의 경우 ’rad’가 방사능 수치를 의미하고, ’sal’이 염분 농도, ’temp’가 온도라는 것은 도메인 지식 없이는 알 수 없다. 또한 측정값이 NULL인 경우가 측정 실패나 장비 오작동을 의미하며, 1920년대 데이터의 특성상 수기 기록으로 인한 오차 가능성이 있다는 점도 중요한 맥락 정보다.
## 비즈니스 규칙
- rad: 방사능 수치 (단위: 기본값)
- sal: 염분 농도 (단위: g/L)
- temp: 온도 (단위: 섭씨 Celsius)
- 측정값이 NULL인 경우: 측정 실패 또는 장비 오작동
- 1920년대 데이터: 수기 기록으로 오차 가능
- DR-1, DR-3: 남극 드라이 밸리 지역 측정 지점
- MSK-4: 맥머도 기지 근처 측정 지점
네 번째 계층인 성능 및 기술적 고려사항은 30%의 중요도를 가지는 선택적 요소다. 일반적인 단순 질문에는 불필요하지만, 복잡한 쿼리나 대용량 데이터 처리가 필요한 상황에서만 추가로 제공한다. 기술적 환경에 대한 명확한 정보 없이는 성능 최적화를 기대할 수 없기 때문이다.
SQLite 3.x를 사용하는 이 데이터베이스에서 Survey 테이블은 백만 개 이상의 레코드를 담고 있어 성능 고려가 필수적이다. 날짜 필터링 시에는 substr() 함수를 사용하는 것이 권장되며, 전체 테이블 스캔은 피해야 할 패턴이다. 다행히 (person, quant)와 (taken) 컬럼에는 인덱스가 기존에 설정되어 있어 대부분의 조인 연산에서 빠른 성능을 기대할 수 있다.
11.7.3 프롬프트 템플릿
수많은 시행착오를 거쳐 김박사가 도달한 결론은 하나의 완벽한 프롬프트는 존재하지 않는다는 것이었다. 단순한 집계 함수 질문에 복잡한 예시를 잔뜩 넣으면 AI가 오히려 혼란스러워하고, 반대로 복잡한 다중 테이블 분석에 기본적인 스키마 정보만 제공하면 정확도가 크게 떨어진다. 이는 마치 초보자에게 대학원 수준의 교재를 주거나, 전문가에게 초등학교 교과서를 건네는 것과 같다.
따라서 그는 질문의 복잡도와 맥락에 따라 적응적으로 변화하는 3단계 프롬프트 전략을 고안했다. 이 전략의 핵심은 Zero-shot에서 시작하여 Few-shot 학습을 거쳐 Chain-of-Thought 추론에 이르는 점진적 복잡도 증가에 있다. 각 단계는 서로 다른 인지적 접근 방식을 AI에게 요구하며, 질문의 성격에 따라 최적의 성능을 발휘하도록 설계되었다.
이러한 체계적 접근법이 왜 필요한지는 남극 탐사 데이터의 특성을 보면 명확해진다. “총 몇 명의 탐사대원이 있나요?”라는 단순한 질문과 “1930년대 각 지점별 방사능 수치의 계절별 변화 패턴에서 통계적으로 유의미한 이상치를 제거한 후 트렌드 분석”이라는 복잡한 질문은 전혀 다른 접근이 필요하다. 전자는 직관적이고 즉각적인 답변이 가능하지만, 후자는 단계적 사고와 도메인 지식이 필수적이다.
첫 번째 템플릿은 단순한 질문용 기본형이다. COUNT, SUM, AVG와 같은 기본 집계나 단일 테이블 조회에 최적화되어 있으며, 스키마 정보와 샘플 데이터, 그리고 기본 요구사항만으로 구성된다:
## 데이터베이스 스키마
[스키마 정보]
## 샘플 데이터
[3-5개 대표 예시]
## 질문
{사용자의 구체적인 질문}
## 요구사항
- SQLite 문법 사용
- 가독성 있는 포매팅
- NULL 값 고려
두 번째는 Few-Shot 학습을 활용한 패턴 학습용 템플릿이다. 컨텍스트에 예시 질문-답변 패턴을 포함시켜 AI가 학습하도록 한다:
## 컨텍스트
[스키마 + 샘플 데이터]
## 예시 질문-답변 패턴
질문: "가장 많은 측정을 한 사람은?"
답변: SELECT p.personal || ' ' || p.family as name, COUNT(*) FROM Survey s JOIN Person p ON s.person = p.id GROUP BY s.person ORDER BY COUNT(*) DESC LIMIT 1;
질문: "DR-1 지점의 평균 방사능은?"
답변: SELECT AVG(s.reading) FROM Survey s JOIN Visited v ON s.taken = v.id WHERE v.site = 'DR-1' AND s.quant = 'rad';
## 새로운 질문
{사용자 질문}
세 번째는 복잡한 분석용 Chain-of-Thought 형이다. AI에게 단계적 사고 과정을 유도하여 복잡한 문제도 체계적으로 해결하도록 설계되었다:
## 데이터베이스 정보
[전체 컨텍스트]
## 분석 요청
{복잡한 질문}
## 접근 방식
다음 단계로 생각해서 SQL을 작성해주세요:1. 필요한 테이블 식별
2. JOIN 조건 결정
3. WHERE 조건 설정
4. GROUP BY/ORDER BY 로직
5. 최종 SELECT 컬럼 선택
각 단계별 설명과 함께 최종 SQL을 제공해주세요.
그러나 가장 중요한 통찰은 질문의 복잡도에 따라 컨텍스트를 동적으로 조절해야 한다는 것이다. 단순한 ‘COUNT’ 질문에 전체 컨텍스트를 넣으면 불필요한 노이즈가 발생하고, 복잡한 분석에 기본 스키마만 제공하면 부정확한 결과를 얻는다. 따라서 김박사는 질문 복잡도를 자동으로 평가하는 시스템을 개발했다:
def assess_complexity(question):
"""질문 복잡도 자동 평가"""
= {
complexity_keywords "simple": ["count", "avg", "sum", "max", "min", "단순"],
"moderate": ["group by", "join", "그룹별", "평균", "비교"],
"complex": ["subquery", "이상치", "트렌드", "복합", "분석"]
}
= question.lower()
question_lower for level, keywords in complexity_keywords.items():
if any(keyword in question_lower for keyword in keywords):
return level
return "moderate"
def get_dynamic_context(db_path, question):
"""질문 복잡도에 따른 동적 컨텍스트 생성"""
= assess_complexity(question)
complexity = get_context(db_path) # 기본 스키마+샘플
context
if complexity == "simple":
return context["schema"] # 스키마만
elif complexity == "moderate":
return context # 스키마+샘플
else:
return context + "\n## 주의: 복잡한 분석을 위해 단계별 접근이 필요합니다."
이 모든 이론과 도구들이 어떻게 하나로 통합되는지 보여주기 위해, 김박사는 survey.db
를 위한 최종 완성본 컨텍스트 시스템을 개발했다. 이 시스템에는 그동안의 모든 실험과 실패가 녹아들어 있다:
# 남극 탐사 데이터 특화 컨텍스트
= """
ANTARCTIC_SURVEY_CONTEXT ## 데이터베이스: 남극 탐사 데이터 (1927-1932)
### 테이블 구조
CREATE TABLE Person (
id text PRIMARY KEY, -- 탐사대원 고유 ID
personal text NOT NULL, -- 이름
family text NOT NULL -- 성
);
CREATE TABLE Site (
name text PRIMARY KEY, -- 측정 지점명
lat real NOT NULL, -- 위도 (남위, 음수값)
long real NOT NULL -- 경도 (서경, 음수값)
);
CREATE TABLE Survey (
taken integer NOT NULL, -- 측정 세션 ID
person text, -- 측정자 (Person.id, NULL 가능)
quant text NOT NULL, -- 측정 항목
reading real, -- 측정값 (NULL: 측정 실패)
FOREIGN KEY (taken) REFERENCES Visited(id),
FOREIGN KEY (person) REFERENCES Person(id)
);
CREATE TABLE Visited (
id integer PRIMARY KEY, -- 방문 세션 ID
site text NOT NULL, -- 측정 지점 (Site.name)
dated text -- 방문 날짜 (YYYY-MM-DD, NULL 가능)
FOREIGN KEY (site) REFERENCES Site(name)
);
이 컨텍스트에는 남극 탐사데이터의 도메인 특성('rad', 'sal', 'temp'의 의미와 단위), 1927-1932년의 역사적 배경, 그리고 대표적인 샘플 데이터가 포함되어 있다. 또한 실명 출력을 위한 Person 대블 JOIN, 시계열 분석을 위한 Visited.dated 필터링 등 일반적인 쿼리 패턴에 대한 가이드도 제공한다.
"""
def create_optimized_prompt(user_question: str) -> str:
"""완성된 AI Text-to-SQL 시스템의 핵심 - 모든 경험이 집약된 최적화 함수"""
return f"""
{ANTARCTIC_SURVEY_CONTEXT}
## 사용자 질문
{user_question}
## 요구사항
- SQLite 호환 문법 사용
- NULL 값 적절히 처리 (김박사의 첫 번째 교훈)
- 가독성 있는 포매팅 (유지보수성 고려)
- 실명 출력 시 Person 테이블 조인 필수 (두 번째 교훈)
"""
이로써 김박사는 AI Text-to-SQL의 핵심 원리를 완전히 터득했다. 컨텍스트가 왕이라는 단순한 진리. 좋은 컨텍스트는 좋은 SQL을 낳고, 좋은 SQL은 정확한 통찰을 제공한다. 이 모든 과정을 통해 그는 1927년 윈리엄 다이어의 수기 기록에서 현대 AI와의 대화로 이어지는 완벽한 가교를 만들어냈다.
이것이 바로 김박사가 도달한 AI Text-to-SQL의 완성된 시스템이다. 무수한 시행착오를 통해 학습한 모든 교훈이 하나의 elegant한 프레임워크로 수렴되었다. 컨텍스트의 계층적 구조, 질문 복잡도에 따른 동적 조절, 그리고 도메인 특화 최적화. 이 세 가지 핵심 원리가 어우러져 1927년 남극의 수기 데이터를 현대 AI와 완벽하게 연결하는 시스템이 탄생했다.
💭 생각해볼 점
자연어와 SQL이 만나는 이 시대는 단순한 기술 혁신을 넘어선다. 1927년 남극에서 윌리엄 다이어가 펜으로 기록한 한 줄의 데이터가, 거의 100년 후 AI와의 대화를 통해 새로운 패턴을 드러내는 순간을 보면서 우리는 깨닫는다. 데이터의 진정한 가치는 저장에 있는 것이 아니라 질문에 있다는 것을.
SQL을 배우지 않고도 데이터베이스와 대화할 수 있게 된 지금, 우리에게 남은 가장 중요한 능력은 무엃일까? 그것은 바로 “의미 있는 질문을 던질 수 있는 통찰력”이다. AI는 완벽한 SQL을 작성해주지만, 그 SQL이 담아야 할 진짜 질문은 여전히 인간만이 발견할 수 있다. 다음 장에서는 이런 통찰력을 어떻게 시스템화하고 자동화할 수 있는지, 즉 AI 시대의 새로운 발견 방법론에 대해 살펴볼 것이다.