Skip to main content

SQL 기초

SQL은 데이터베이스에서 정확한 데이터를 가져오기 위한 공통 언어입니다. 모델 성능 문제처럼 보이는 이슈도 실제로는 SQL 추출 오류인 경우가 많습니다.

학습 목표

  • SELECT부터 JOIN, GROUP BY까지 핵심 쿼리를 이해합니다.
  • 윈도우 함수와 CTE를 활용하여 복잡한 분석 쿼리를 작성할 수 있습니다.
  • EXPLAIN을 사용하여 쿼리 성능을 진단할 수 있습니다.
  • 학습 데이터 추출 쿼리를 재현 가능하게 작성할 수 있습니다.

기본 실행 순서(논리적)

SQL 쿼리의 논리적 처리 순서는 작성 순서와 다릅니다. 이 순서를 이해하면 에러 원인을 빠르게 파악할 수 있습니다.
순서역할
1FROM / JOIN대상 테이블 결정
2WHERE행 필터링
3GROUP BY집계 단위 정의
4HAVING집계 결과 필터링
5SELECT최종 컬럼 선택/생성
6DISTINCT중복 제거
7ORDER BY정렬
8LIMIT / OFFSET출력 제한
WHERE에서 SELECT의 별칭(alias)을 사용할 수 없는 이유가 바로 이 실행 순서 때문입니다. WHERESELECT보다 먼저 실행됩니다.

기본 패턴 예시

SELECT user_id, COUNT(*) AS request_count
FROM inference_logs
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY user_id
ORDER BY request_count DESC
LIMIT 20;

조인 시 체크 포인트

  • 조인 키가 PK/FK 관계인지 확인합니다.
  • INNER JOINLEFT JOIN 차이를 이해하고 선택합니다.
  • 조인 후 행 수가 예상보다 늘어나면 중복 키를 의심합니다.
조인 타입동작사용 시점
INNER JOIN양쪽 모두 일치하는 행만두 테이블 모두에 데이터가 있어야 할 때
LEFT JOIN왼쪽 전체 + 오른쪽 일치왼쪽 기준으로 빠진 데이터를 찾을 때
RIGHT JOIN오른쪽 전체 + 왼쪽 일치LEFT JOIN을 뒤집은 것 (잘 안 씀)
FULL OUTER JOIN양쪽 전체 합집합양쪽 데이터 누락을 모두 확인할 때
CROSS JOIN모든 조합 (카테시안 곱)조합 생성 시 (주의: 행 폭증)

윈도우 함수 (Window Function)

윈도우 함수는 GROUP BY 없이 집계와 원본 행을 함께 볼 수 있어 분석에 매우 유용합니다.
-- 사용자별 요청 순번 매기기
SELECT
    user_id,
    created_at,
    prompt,
    ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS req_seq,
    COUNT(*) OVER (PARTITION BY user_id) AS total_requests
FROM inference_logs
WHERE created_at >= '2024-01-01';

-- 일별 추론 요청 수와 7일 이동평균
SELECT
    DATE(created_at) AS dt,
    COUNT(*) AS daily_count,
    AVG(COUNT(*)) OVER (ORDER BY DATE(created_at) ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7d
FROM inference_logs
GROUP BY DATE(created_at)
ORDER BY dt;
자주 쓰는 윈도우 함수: ROW_NUMBER(), RANK(), LAG(), LEAD(), SUM() OVER(), AVG() OVER(). PARTITION BY는 그룹 기준, ORDER BY는 정렬 기준을 지정합니다.

CTE (Common Table Expression)

CTE는 복잡한 쿼리를 단계별로 나누어 가독성과 재사용성을 높입니다.
-- 활성 사용자의 모델별 사용 통계
WITH active_users AS (
    SELECT user_id
    FROM inference_logs
    WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
    GROUP BY user_id
    HAVING COUNT(*) >= 10
),
model_usage AS (
    SELECT
        l.user_id,
        l.model_name,
        COUNT(*) AS call_count,
        AVG(l.latency_ms) AS avg_latency
    FROM inference_logs l
    JOIN active_users a ON l.user_id = a.user_id
    GROUP BY l.user_id, l.model_name
)
SELECT *
FROM model_usage
WHERE avg_latency > 1000
ORDER BY call_count DESC;

EXPLAIN 실행계획 읽기

EXPLAIN은 쿼리가 실제로 어떻게 실행되는지 보여줍니다. 느린 쿼리의 원인을 찾을 때 필수입니다.
EXPLAIN ANALYZE
SELECT user_id, COUNT(*)
FROM inference_logs
WHERE created_at >= '2024-01-01'
GROUP BY user_id;
실행계획 항목좋은 신호나쁜 신호
Scan 타입Index Scan, Index Only ScanSeq Scan (대량 테이블)
예상 행 수 vs 실제 행 수비슷함크게 차이남 (통계 갱신 필요)
Sort 방식Sort Key + IndexSort Method: external merge
Join 방식Hash Join, Merge JoinNested Loop (대량 데이터)
Seq Scan이 항상 나쁜 것은 아닙니다. 작은 테이블이나 대부분의 행을 읽어야 할 때는 Seq Scan이 더 효율적입니다. 큰 테이블에서 소수 행만 필요할 때 인덱스가 중요합니다.

데이터 품질 검증 쿼리

학습 데이터 추출 후 반드시 실행해야 할 검증 쿼리 패턴입니다.
-- 1. 총 행 수 확인
SELECT COUNT(*) AS total_rows FROM training_data;

-- 2. 라벨 분포 확인
SELECT label, COUNT(*) AS cnt, ROUND(COUNT(*)::numeric / SUM(COUNT(*)) OVER() * 100, 2) AS pct
FROM training_data
GROUP BY label
ORDER BY cnt DESC;

-- 3. 결측치 비율 확인
SELECT
    COUNT(*) AS total,
    COUNT(*) - COUNT(feature_1) AS null_feature_1,
    COUNT(*) - COUNT(feature_2) AS null_feature_2,
    ROUND((COUNT(*) - COUNT(feature_1))::numeric / COUNT(*) * 100, 2) AS null_pct_1
FROM training_data;

-- 4. 기간 범위 확인
SELECT MIN(created_at) AS start_date, MAX(created_at) AS end_date,
       MAX(created_at) - MIN(created_at) AS date_range
FROM training_data;

-- 5. 중복 행 확인
SELECT user_id, prompt, COUNT(*) AS dup_count
FROM training_data
GROUP BY user_id, prompt
HAVING COUNT(*) > 1
ORDER BY dup_count DESC
LIMIT 10;

성능 점검 기본

  • SELECT * 대신 필요한 컬럼만 조회합니다.
  • 필터 컬럼에 인덱스가 있는지 확인합니다.
  • 큰 조인 전에는 CTE/서브쿼리로 대상 행을 줄입니다.
  • 실행 계획(EXPLAIN ANALYZE)으로 전체 스캔 여부를 확인합니다.
  • 대량 데이터 조회 시 LIMIT으로 먼저 샘플을 확인합니다.
1:N 관계를 모르고 조인하면 행 수가 폭증합니다. 먼저 조인 전/후 카운트를 비교하고, 키 중복 여부를 확인하세요.
-- 조인 전후 행 수 비교
SELECT COUNT(*) FROM table_a;  -- 예: 1000
SELECT COUNT(*) FROM table_a JOIN table_b ON ...;  -- 예: 3000이면 1:N 의심
  1. 총 행 수 2) 라벨 분포 3) 결측치 비율 4) 기간 범위가 요구사항과 일치하는지 확인하세요. 쿼리 결과를 바로 모델에 넣지 말고 통계 요약을 먼저 확인하세요.
프롬프트 실험 로그 분석, 사용자 피드백 집계, 모델 버전별 성능 비교, 비용 집계에 SQL이 핵심입니다. 특히 A/B 테스트 결과 분석과 토큰 사용량 추적에 윈도우 함수가 자주 활용됩니다.
WHERE, JOIN, ORDER BY에 자주 사용되는 컬럼에 인덱스를 만듭니다. 단, 인덱스가 많으면 INSERT/UPDATE 성능이 떨어집니다. EXPLAIN으로 Seq Scan이 발생하는 쿼리를 먼저 찾고, 해당 컬럼에 인덱스를 추가하세요.

체크리스트

  • 쿼리 목적(분석/피처/운영 리포트)이 분명한가요?
  • 결과 검증 지표(행 수, 분포, 결측치)를 기록했나요?
  • 성능 문제가 생길 때 EXPLAIN ANALYZE를 확인하나요?
  • CTE를 활용하여 복잡한 쿼리를 단계별로 분리했나요?
  • 학습 데이터 추출 후 품질 검증 쿼리를 실행했나요?

다음 문서