데이터 가공
데이터 가공: SQL 쿼리 내에서 값을 변환·조합·조건 분기하는 기능. 애플리케이션 코드로 처리하지 않고 DB 레벨에서 원하는 형태로 가공해 반환한다.
왜 쓰는가?
- 애플리케이션에서 가공 로직을 줄여 코드 단순화
- DB에서 한 번에 처리하므로 네트워크 전송 데이터 감소
- NULL 처리, 조건 분기 등 쿼리 결과를 즉시 사용 가능한 형태로 반환
조건 분기 — CASE WHEN
값에 따라 다른 결과를 반환하는 SQL의 if-else.
단순 CASE (값 비교)
SELECT
name,
CASE grade
WHEN 'A' THEN '우수'
WHEN 'B' THEN '보통'
WHEN 'C' THEN '미흡'
ELSE '미분류'
END AS 등급명
FROM student;
검색 CASE (범위·복합 조건)
SELECT
name,
salary,
CASE
WHEN salary >= 5000000 THEN '고연봉'
WHEN salary >= 3000000 THEN '중연봉'
ELSE '저연봉'
END AS 연봉구간
FROM employee;
-- 집계와 함께: 상태별 건수 피벗
SELECT
COUNT(CASE WHEN status = 'PAID' THEN 1 END) AS 결제완료,
COUNT(CASE WHEN status = 'PENDING' THEN 1 END) AS 결제대기,
COUNT(CASE WHEN status = 'CANCEL' THEN 1 END) AS 취소
FROM orders;
NULL 처리 함수
| 함수 | 설명 | 예시 |
|---|---|---|
COALESCE(a, b, c, ...) |
첫 번째 non-NULL 값 반환 | COALESCE(nickname, name, '익명') |
IFNULL(a, b) |
a가 NULL이면 b 반환 (MySQL) | IFNULL(score, 0) |
NULLIF(a, b) |
a = b이면 NULL 반환, 아니면 a | NULLIF(quantity, 0) |
IS NULL / IS NOT NULL |
NULL 여부 조건 | WHERE email IS NOT NULL |
-- 닉네임 > 이름 > '익명' 순으로 표시
SELECT COALESCE(nickname, name, '익명') AS 표시명 FROM member;
-- 0으로 나누기 방지 (NULLIF로 분모가 0이면 NULL → 결과도 NULL)
SELECT total_sales / NULLIF(order_count, 0) AS 건당_매출 FROM stats;
주의: NULL = NULL은 FALSE다. NULL 비교는 반드시 IS NULL / IS NOT NULL 사용. WHERE column = NULL은 항상 결과가 없다.
문자열 함수
| 함수 | 설명 | 예시 |
|---|---|---|
CONCAT(a, b, ...) |
문자열 연결 | CONCAT(first_name, ' ', last_name) |
SUBSTRING(str, pos, len) |
부분 문자열 추출 | SUBSTRING('hello', 2, 3) → ell |
LENGTH(str) |
바이트 길이 | LENGTH('abc') → 3 |
CHAR_LENGTH(str) |
문자 수 (한글 정확) | CHAR_LENGTH('한글') → 2 |
UPPER(str) / LOWER(str) |
대/소문자 변환 | UPPER('hello') → HELLO |
TRIM(str) |
앞뒤 공백 제거 | TRIM(' hi ') → hi |
REPLACE(str, from, to) |
문자열 치환 | REPLACE('abc', 'b', 'X') → aXc |
LIKE |
패턴 매칭 | name LIKE '김%' |
숫자 함수
| 함수 | 설명 | 예시 |
|---|---|---|
ROUND(n, d) |
반올림 (d: 소수점 자릿수) | ROUND(3.567, 2) → 3.57 |
FLOOR(n) |
내림 (정수) | FLOOR(3.9) → 3 |
CEIL(n) |
올림 (정수) | CEIL(3.1) → 4 |
ABS(n) |
절댓값 | ABS(-5) → 5 |
MOD(n, m) |
나머지 | MOD(10, 3) → 1 |
날짜 함수
| 함수 | 설명 | 예시 |
|---|---|---|
NOW() |
현재 날짜시간 | 2024-01-15 14:30:00 |
CURDATE() |
현재 날짜 | 2024-01-15 |
DATE_FORMAT(d, fmt) |
날짜 포맷 변환 | DATE_FORMAT(NOW(), '%Y-%m') |
DATEDIFF(d1, d2) |
날짜 차이 (일 수) | DATEDIFF('2024-01-15', '2024-01-01') → 14 |
DATE_ADD(d, INTERVAL n unit) |
날짜 더하기 | DATE_ADD(NOW(), INTERVAL 7 DAY) |
YEAR(d) / MONTH(d) / DAY(d) |
연·월·일 추출 | YEAR('2024-01-15') → 2024 |
-- 최근 30일 가입 회원
SELECT * FROM member
WHERE created_at >= DATE_ADD(NOW(), INTERVAL -30 DAY);
-- 월별 매출 집계
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS 월,
SUM(amount) AS 월매출
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY 월;
형변환 — CAST / CONVERT
-- 문자열 → 숫자
SELECT CAST('123' AS UNSIGNED);
SELECT CAST('3.14' AS DECIMAL(10, 2));
-- 숫자 → 문자열
SELECT CAST(age AS CHAR) FROM member;
-- 날짜 변환
SELECT CAST('2024-01-15' AS DATE);
SELECT CONVERT('2024-01-15', DATE); -- CONVERT는 MySQL 방언
언제 쓰는지
| 상황 | 함수/기능 | 사용 시점 |
|---|---|---|
| 조건에 따라 값 변환 | ✅ CASE WHEN | 상태 코드 → 텍스트, 범위별 분류 |
| NULL 처리 | ✅ COALESCE / IFNULL | 기본값 대체, 필수 정보 표시 |
| 문자 추출/합치기 | ✅ SUBSTRING, CONCAT | 마스킹, 이름 조합 |
| 날짜 비교 | ✅ DATE_FORMAT, DATEDIFF | 월별·연도별 필터링 |
| 숫자 계산 | ✅ ROUND, MOD | 할인가, 나머지 계산 |
| 타입 변환 | ✅ CAST | 문자→숫자, 숫자→문자 |
| 기본값 설정 | ✅ NULLIF | 0 제외, 분모 처리 |
특징
함수별 특징
1. 함수별 NULL 처리
-- 대부분의 함수는 NULL 입력 → NULL 출력
CONCAT('a', NULL, 'b') -- NULL (NOT 'ab')
SUBSTRING(NULL, 1, 2) -- NULL
DATE_FORMAT(NULL, '%Y-%m') -- NULL
-- 예외: NULL 관련 함수
COALESCE(NULL, 'default') -- 'default' (NULL 입력도 처리)
IFNULL(NULL, 0) -- 0
2. 인덱스와 함수 (성능 영향)
-- ❌ 함수를 쓰면 인덱스 무효화
WHERE YEAR(created_at) = 2024 -- 풀 스캔
WHERE LENGTH(name) = 5 -- 풀 스캔
-- ✅ 인덱스 활용
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'
WHERE name LIKE 'Kim%' -- LIKE는 인덱스 활용
3. CASE의 실행 순서
-- WHEN을 위에서부터 순서대로 평가, 첫 TRUE에서 멈춤
CASE
WHEN age < 20 THEN '청소년' -- 1순위 체크
WHEN age < 65 THEN '성인' -- 위가 FALSE일 때만
ELSE '노년'
END;
-- age = 25인 경우: "성인" (첫 번째 FALSE, 두 번째 TRUE)
4. GROUP BY와 함께 쓸 때
-- CASE와 COUNT 조합으로 조건별 집계
SELECT
COUNT(CASE WHEN status = 'ACTIVE' THEN 1 END) AS 활성,
COUNT(CASE WHEN status = 'INACTIVE' THEN 1 END) AS 비활성
FROM member;
장점
- DB 레벨 가공으로 애플리케이션 코드 단순화
- 쿼리 한 번으로 원하는 형태의 데이터 반환
단점
- 복잡한 가공 로직은 쿼리 가독성 저하
- 함수 적용 컬럼은 인덱스를 타지 못한다
주의할 점
인덱스 무효화: WHERE YEAR(created_at) = 2024처럼 인덱스 컬럼에 함수를 씌우면 인덱스 풀 스캔이 발생한다.
NULL 전파: 대부분의 함수는 인자 중 하나라도 NULL이면 결과가 NULL이 된다. CONCAT('a', NULL, 'b') → NULL. NULL이 섞일 수 있는 경우 COALESCE로 기본값을 지정한다.