🗄️ 데이터베이스

SQL

Structured Query Language

관계형 데이터베이스 질의 언어. SELECT, INSERT, UPDATE, DELETE 등의 명령어로 데이터 조작.

📖 상세 설명

SQL이란?

SQL(Structured Query Language)은 1970년대 IBM에서 개발한 관계형 데이터베이스 관리 시스템(RDBMS)을 위한 표준 질의 언어입니다. 데이터의 정의, 조작, 제어를 위한 선언적 언어로, "무엇을" 원하는지 기술하면 데이터베이스가 "어떻게" 처리할지 결정합니다.

SQL의 분류

  • DDL (Data Definition Language): 스키마 정의 - CREATE, ALTER, DROP, TRUNCATE
  • DML (Data Manipulation Language): 데이터 조작 - SELECT, INSERT, UPDATE, DELETE
  • DCL (Data Control Language): 권한 제어 - GRANT, REVOKE
  • TCL (Transaction Control Language): 트랜잭션 제어 - COMMIT, ROLLBACK, SAVEPOINT

핵심 개념

  • 테이블 (Table): 행(Row)과 열(Column)로 구성된 데이터 저장 단위
  • 기본 키 (Primary Key): 각 행을 고유하게 식별하는 컬럼
  • 외래 키 (Foreign Key): 다른 테이블의 기본 키를 참조하는 컬럼
  • 인덱스 (Index): 검색 속도를 높이기 위한 자료구조
  • 정규화 (Normalization): 데이터 중복을 최소화하는 설계 기법

JOIN 종류

  • INNER JOIN: 양쪽 테이블에 매칭되는 행만 반환
  • LEFT JOIN: 왼쪽 테이블 전체 + 오른쪽 매칭 (없으면 NULL)
  • RIGHT JOIN: 오른쪽 테이블 전체 + 왼쪽 매칭
  • FULL OUTER JOIN: 양쪽 테이블 전체 (매칭 없으면 NULL)
  • CROSS JOIN: 모든 조합 (카티전 곱)

주요 RDBMS

  • 오픈소스: PostgreSQL, MySQL, MariaDB, SQLite
  • 상용: Oracle, Microsoft SQL Server, IBM DB2
  • 클라우드: Amazon RDS/Aurora, Google Cloud SQL, Azure SQL

💻 코드 예제

기본 CRUD 연산

SQL
-- CREATE: 데이터 삽입
INSERT INTO users (name, email, created_at)
VALUES ('Alice', 'alice@example.com', NOW());

-- READ: 데이터 조회
SELECT id, name, email
FROM users
WHERE created_at >= '2024-01-01'
ORDER BY created_at DESC
LIMIT 10;

-- UPDATE: 데이터 수정
UPDATE users
SET email = 'alice.new@example.com', updated_at = NOW()
WHERE id = 1;

-- DELETE: 데이터 삭제
DELETE FROM users
WHERE id = 1;

JOIN과 집계

SQL
-- 주문과 사용자 정보 조인
SELECT
    u.name AS user_name,
    o.order_id,
    o.total_amount,
    o.created_at AS order_date
FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE o.status = 'completed'
ORDER BY o.created_at DESC;

-- 집계 함수와 GROUP BY
SELECT
    u.id,
    u.name,
    COUNT(o.id) AS order_count,
    SUM(o.total_amount) AS total_spent,
    AVG(o.total_amount) AS avg_order_value
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
HAVING COUNT(o.id) >= 5
ORDER BY total_spent DESC;

서브쿼리와 CTE

SQL
-- 서브쿼리: 평균보다 많이 구매한 사용자
SELECT name, email
FROM users
WHERE id IN (
    SELECT user_id
    FROM orders
    GROUP BY user_id
    HAVING SUM(total_amount) > (
        SELECT AVG(total_amount) FROM orders
    )
);

-- CTE (Common Table Expression): 가독성 향상
WITH monthly_sales AS (
    SELECT
        DATE_TRUNC('month', created_at) AS month,
        SUM(total_amount) AS revenue
    FROM orders
    WHERE status = 'completed'
    GROUP BY DATE_TRUNC('month', created_at)
),
growth AS (
    SELECT
        month,
        revenue,
        LAG(revenue) OVER (ORDER BY month) AS prev_revenue
    FROM monthly_sales
)
SELECT
    month,
    revenue,
    ROUND((revenue - prev_revenue) / prev_revenue * 100, 2) AS growth_rate
FROM growth
WHERE prev_revenue IS NOT NULL;

윈도우 함수

SQL
-- 순위 함수
SELECT
    name,
    department,
    salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank,
    DENSE_RANK() OVER (ORDER BY salary DESC) AS overall_rank,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num
FROM employees;

-- 누적 합계와 이동 평균
SELECT
    order_date,
    daily_revenue,
    SUM(daily_revenue) OVER (ORDER BY order_date) AS cumulative_revenue,
    AVG(daily_revenue) OVER (
        ORDER BY order_date
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) AS moving_avg_7days
FROM daily_sales;

인덱스와 성능 최적화

SQL
-- 인덱스 생성
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);

-- 복합 인덱스 (왼쪽 컬럼부터 사용됨)
CREATE INDEX idx_orders_status_date ON orders(status, created_at);

-- EXPLAIN으로 실행 계획 확인
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id = 123 AND status = 'completed'
ORDER BY created_at DESC
LIMIT 10;

💬 현업 대화 예시

주니어 개발자
"이 쿼리가 왜 이렇게 느려요? SELECT * 해서 데이터 다 가져오는데..."
시니어 개발자
"SELECT *는 안티패턴이야. 필요한 컬럼만 명시해. 그리고 EXPLAIN 돌려서 인덱스 타는지 확인해봐. WHERE 절 컬럼에 인덱스가 없으면 Full Table Scan 발생해."
백엔드 개발자
"N+1 문제가 뭐예요? ORM 쓰는데 쿼리가 엄청 많이 날아가요."
DBA
"루프 안에서 연관 데이터 가져올 때 생기는 문제야. 1번 쿼리 + N번 추가 쿼리. JOIN으로 한 번에 가져오거나 ORM의 Eager Loading 쓰면 해결돼."
팀장
"이 리포트 쿼리 운영 DB에서 돌리면 안 되나요?"
아키텍트
"무거운 분석 쿼리는 Read Replica에서 돌리세요. 운영 DB 부하 주면 서비스 전체가 느려집니다. 락 걸리면 더 심각하고요."

⚠️ 주의사항

⚠️ SQL Injection 방어
사용자 입력을 직접 쿼리에 연결하지 마세요. 반드시 Prepared Statement/Parameterized Query를 사용하세요. "WHERE id = " + userId는 절대 금지입니다.
⚠️ 인덱스 설계
WHERE, JOIN, ORDER BY에 사용되는 컬럼에 인덱스를 만드세요. 단, 인덱스가 많으면 INSERT/UPDATE 성능이 저하됩니다. 복합 인덱스는 컬럼 순서가 중요합니다 (Leftmost Prefix).
⚠️ 대량 데이터 처리
대량 UPDATE/DELETE는 청크 단위로 나눠서 실행하세요. 한 번에 처리하면 락 오래 잡고 WAL 로그 폭증합니다. 배치 작업은 LIMIT과 루프를 조합하세요.
⚠️ NULL 처리
NULL은 = 연산자로 비교 불가합니다. IS NULL / IS NOT NULL을 사용하세요. NULL 포함 연산 결과는 대부분 NULL이 됩니다. COALESCE로 기본값 처리 권장.

🔗 관련 용어

📚 더 배우기