Skip to main content

Pandas-SQL

Pandas는 read_sql()로 SQL 쿼리 결과를 DataFrame으로 읽고, to_sql()로 DataFrame을 데이터베이스에 저장할 수 있습니다. SQL의 데이터 추출 능력과 Pandas의 분석 능력을 결합하는 가장 직접적인 방법입니다.

학습 목표

  • pd.read_sql()로 SQL 쿼리 결과를 DataFrame으로 변환할 수 있다
  • 파라미터화된 쿼리로 SQL 인젝션을 방지할 수 있다
  • to_sql()로 분석 결과를 데이터베이스에 저장할 수 있다
  • 대용량 데이터를 chunksize로 분할 처리할 수 있다

왜 중요한가

실무 ML 프로젝트의 데이터는 대부분 데이터베이스에 있습니다. CSV 파일로 내보내서 분석하는 대신, Python에서 직접 SQL을 실행하면 최신 데이터를 실시간으로 분석할 수 있고, 대용량 데이터도 서버 측에서 필터링하여 효율적으로 처리할 수 있습니다.

데이터베이스 연결

import pandas as pd
from sqlalchemy import create_engine

# SQLite (파일 기반, 설치 불필요)
engine = create_engine('sqlite:///sample.db')

# PostgreSQL
# engine = create_engine('postgresql://user:password@host:5432/dbname')

# MySQL
# engine = create_engine('mysql+pymysql://user:password@host:3306/dbname')

예시 데이터베이스 생성

import numpy as np

np.random.seed(42)

# 예시 테이블 생성
employees = pd.DataFrame({
    'id': range(1, 201),
    'name': [f'직원_{i}' for i in range(1, 201)],
    'department': np.random.choice(['개발', '영업', '마케팅', '인사'], 200),
    'salary': np.random.normal(4500, 800, 200).round(0),
    'experience': np.random.uniform(1, 20, 200).round(1),
    'hire_date': pd.date_range('2015-01-01', periods=200, freq='15D')
})

orders = pd.DataFrame({
    'order_id': range(1, 501),
    'employee_id': np.random.randint(1, 201, 500),
    'amount': np.random.uniform(100, 10000, 500).round(0),
    'order_date': pd.date_range('2024-01-01', periods=500, freq='6H')
})

# 테이블 저장
employees.to_sql('employees', engine, if_exists='replace', index=False)
orders.to_sql('orders', engine, if_exists='replace', index=False)
print("테이블 생성 완료")

read_sql — SQL 쿼리 실행

# 전체 테이블 읽기
df = pd.read_sql('SELECT * FROM employees', engine)
print(f"직원 수: {len(df)}")
print(df.head())

# 조건 필터링
dev_team = pd.read_sql(
    "SELECT * FROM employees WHERE department = '개발' AND salary > 5000",
    engine
)
print(f"\n개발팀 고연봉: {len(dev_team)}명")

# 집계 쿼리
dept_stats = pd.read_sql("""
    SELECT department,
           COUNT(*) as count,
           ROUND(AVG(salary), 0) as avg_salary,
           ROUND(AVG(experience), 1) as avg_experience
    FROM employees
    GROUP BY department
    ORDER BY avg_salary DESC
""", engine)
print("\n부서별 통계:")
print(dept_stats)

JOIN 쿼리

# 직원별 주문 현황 조인
employee_orders = pd.read_sql("""
    SELECT e.name, e.department, e.salary,
           COUNT(o.order_id) as order_count,
           COALESCE(SUM(o.amount), 0) as total_amount
    FROM employees e
    LEFT JOIN orders o ON e.id = o.employee_id
    GROUP BY e.id, e.name, e.department, e.salary
    ORDER BY total_amount DESC
    LIMIT 10
""", engine)
print("상위 10명 주문 실적:")
print(employee_orders)

파라미터화된 쿼리

# SQL 인젝션 방지: 파라미터 바인딩
from sqlalchemy import text

department = '개발'
min_salary = 4000

# 올바른 방법: 파라미터 바인딩
query = text("""
    SELECT * FROM employees
    WHERE department = :dept AND salary > :min_sal
""")
df_safe = pd.read_sql(query, engine, params={'dept': department, 'min_sal': min_salary})
print(f"파라미터 쿼리 결과: {len(df_safe)}행")
f-string이나 문자열 결합으로 SQL을 구성하면 SQL 인젝션 공격에 취약합니다. 항상 파라미터 바인딩(:param 또는 ?)을 사용하세요.

to_sql — 결과 저장

# 분석 결과를 새 테이블로 저장
analysis_result = pd.read_sql("""
    SELECT department,
           AVG(salary) as avg_salary,
           COUNT(*) as employee_count
    FROM employees
    GROUP BY department
""", engine)

# 데이터베이스에 저장
analysis_result.to_sql(
    'department_analysis',
    engine,
    if_exists='replace',  # 기존 테이블 덮어쓰기
    index=False
)

# 저장 확인
verify = pd.read_sql('SELECT * FROM department_analysis', engine)
print("저장된 분석 결과:")
print(verify)
if_exists동작
'fail'테이블 존재 시 에러 (기본값)
'replace'기존 테이블 삭제 후 새로 생성
'append'기존 테이블에 행 추가

대용량 데이터 처리

# chunksize로 분할 읽기
chunks = pd.read_sql('SELECT * FROM orders', engine, chunksize=100)

total_amount = 0
for i, chunk in enumerate(chunks):
    total_amount += chunk['amount'].sum()
    print(f"  청크 {i+1}: {len(chunk)}행, 누적 금액: {total_amount:,.0f}")

print(f"\n총 주문 금액: {total_amount:,.0f}")
수백만 행의 데이터는 한 번에 읽지 말고 chunksize로 분할하거나, SQL의 WHERE, LIMIT으로 필요한 데이터만 가져오세요. 서버에서 집계하고 결과만 가져오는 것이 가장 효율적입니다.

SQL vs Pandas — 어디서 처리할까?

작업SQL 권장Pandas 권장
대용량 필터링O
집계 (GROUP BY)O
JOINO
복잡한 변환O
시각화O
피벗 테이블O
ML 전처리O

AI/ML에서의 활용

  • 학습 데이터 추출: SQL로 학습 데이터를 추출하고 Pandas로 전처리합니다
  • 피처 스토어: 전처리된 피처를 데이터베이스에 저장하여 재사용합니다
  • 모델 결과 저장: 예측 결과를 데이터베이스에 저장하여 비즈니스 시스템과 연동합니다
  • 실시간 분석: 최신 데이터를 SQL로 추출하여 실시간 분석 파이프라인을 구축합니다
read_sql()은 테이블 이름이나 SQL 쿼리 모두 받을 수 있는 범용 함수입니다. read_sql_query()는 SQL 쿼리만, read_sql_table()은 테이블 이름만 받습니다. 대부분의 경우 read_sql()을 사용하면 충분합니다.

체크리스트

  • pd.read_sql()로 SQL 쿼리 결과를 DataFrame으로 변환할 수 있다
  • 파라미터 바인딩으로 안전한 쿼리를 작성할 수 있다
  • to_sql()로 분석 결과를 데이터베이스에 저장할 수 있다
  • chunksize로 대용량 데이터를 분할 처리할 수 있다
  • SQL과 Pandas의 역할을 적절히 분배할 수 있다

다음 문서