SELECT
UPPER('hello') -- 'HELLO' (대문자 변환)
LOWER('WORLD') -- 'world' (소문자 변환)
LENGTH('SQL') -- 3 (문자열 길이)
SUBSTRING('Hello', 1, 3) -- 'Hel' (문자열 자르기)
CONCAT('Hello', ' ', 'World')-- 'Hello World' (문자열 합치기)
TRIM(' hello ') -- 'hello' (공백 제거)
SELECT
ROUND(3.567, 2) -- 3.57 (반올림)
CEIL(3.2) -- 4 (올림)
FLOOR(3.9) -- 3 (내림)
ABS(-10) -- 10 (절댓값)
MOD(10, 3) -- 1 (나머지)
POWER(2, 3) -- 8 (제곱)
SELECT
NOW() -- 현재 날짜 + 시간
CURDATE() -- 현재 날짜
YEAR('2024-05-03') -- 2024
MONTH('2024-05-03') -- 5
DAY('2024-05-03') -- 3
DATEDIFF('2024-12-31', '2024-01-01') -- 365 (날짜 차이)
SELECT
COUNT(*) -- 전체 행 수
SUM(salary) -- 급여 합계
AVG(salary) -- 급여 평균
MAX(salary) -- 최고 급여
MIN(salary) -- 최저 급여
FROM employees;
GROUP BY와 함께 사용:
SELECT department, AVG(salary) AS 평균급여
FROM employees
GROUP BY department;
-- IF
SELECT IF(score >= 60, '합격', '불합격') FROM students;
-- CASE
SELECT
name,
CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
WHEN score >= 70 THEN 'C'
ELSE 'F'
END AS grade
FROM students;
-- COALESCE: NULL이면 대체값 반환
SELECT COALESCE(phone, '번호 없음') FROM customers;
SELECT
CAST('123' AS INT) -- 문자 → 숫자
CAST(3.14 AS CHAR) -- 숫자 → 문자
DATE_FORMAT(NOW(), '%Y-%m-%d') -- 날짜 형식 변환
CONVERT()는 두 가지 용도로 사용된다.
SELECT CONVERT('123', UNSIGNED INTEGER) -- 문자 → 정수: 123
SELECT CONVERT(3.14, CHAR) -- 숫자 → 문자: '3.14'
SELECT CONVERT('2024-05-03', DATE) -- 문자 → 날짜
SELECT CONVERT(NOW(), TIME) -- 날짜시간 → 시간만
SELECT CONVERT('안녕하세요' USING utf8mb4);
SELECT CONVERT('hello' USING latin1);
한글 깨짐 같은 인코딩 이슈 해결 시 유용하다.
| CAST | CONVERT | |
|---|---|---|
| 문법 | CAST(값 AS 타입) | CONVERT(값, 타입) |
| 문자셋 변환 | ❌ 불가 | ✅ 가능 |
| SQL 표준 | ✅ 표준 | ⚠️ 비표준 (MySQL 등) |
타입 변환만 할 거라면 표준인
CAST()를, 문자셋 변환이 필요하면CONVERT()를 쓰는 게 일반적이다.
집계 함수와 윈도우 함수의 가장 큰 차이는 행의 수다.
| 집계 함수 | 윈도우 함수 | |
|---|---|---|
| 결과 행 수 | 줄어듦 | 그대로 유지 |
| 사용 키워드 | GROUP BY | OVER() |
| 용도 | 그룹별 요약 | 행 유지하며 계산 |
예를 들어 아래 테이블이 있다고 할 때:
| name | salary |
|---|---|
| 철수 | 500 |
| 영희 | 600 |
| 민준 | 400 |
| 지수 | 350 |
집계 함수 (GROUP BY 사용):
SELECT AVG(salary) FROM employees;
-- 결과: 1행 (462.5)
-- 철수, 영희, 민준, 지수 정보가 사라짐
윈도우 함수 (OVER() 사용):
SELECT name, salary, AVG(salary) OVER() AS avg_salary
FROM employees;
| name | salary | avg_salary |
|---|---|---|
| 철수 | 500 | 462.5 |
| 영희 | 600 | 462.5 |
| 민준 | 400 | 462.5 |
| 지수 | 350 | 462.5 |
4행이 그대로 유지되면서 평균값 컬럼이 추가된다. "철수의 급여가 전체 평균보다 높은지" 같은 비교가 필요할 때 윈도우 함수가 필수다.
함수명() OVER (
PARTITION BY 그룹기준컬럼 -- 그룹 나누기 (생략 가능)
ORDER BY 정렬기준컬럼 -- 정렬 기준 (생략 가능)
)
순위 함수:
SELECT name, score,
RANK() OVER (ORDER BY score DESC) AS rank, -- 1,2,2,4
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank, -- 1,2,2,3
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num -- 1,2,3,4
FROM students;
| name | score | RANK | DENSE_RANK | ROW_NUMBER |
|---|---|---|---|---|
| 철수 | 95 | 1 | 1 | 1 |
| 영희 | 90 | 2 | 2 | 2 |
| 민준 | 90 | 2 | 2 | 3 |
| 지수 | 85 | 4 | 3 | 4 |
행 참조 함수:
SELECT name, month, sales,
LAG(sales, 1) OVER (ORDER BY month) AS 전월매출,
LEAD(sales, 1) OVER (ORDER BY month) AS 다음달매출
FROM monthly_sales;
분위 함수:
SELECT name, score,
NTILE(4) OVER (ORDER BY score DESC) AS 사분위,
PERCENT_RANK() OVER (ORDER BY score DESC) AS 백분위
FROM students;
PARTITION BY는 부서별 울타리를 치는 것이다. 각 울타리 안에서만 계산이 이루어진다.
SELECT name, department, salary,
AVG(salary) OVER (PARTITION BY department) AS 부서_평균급여
FROM employees;
| name | department | salary | 부서_평균급여 |
|---|---|---|---|
| 철수 | 개발 | 500 | 550 |
| 영희 | 개발 | 600 | 550 |
| 민준 | 영업 | 400 | 375 |
| 지수 | 영업 | 350 | 375 |
PARTITION BY가 없으면 전체 평균(462.5)이 나오지만, PARTITION BY department를 쓰면 부서별로 분리해서 계산한다.
ORDER BY는 파티션 안에서 행을 순서대로 세우고, 위에서부터 누적해서 계산한다.
SELECT name, salary,
SUM(salary) OVER (ORDER BY salary) AS 누적합계
FROM employees;
ORDER BY salary로 정렬 후 누적 계산:
| name | salary | 누적합계 |
|---|---|---|
| 지수 | 350 | 350 |
| 민준 | 400 | 750 |
| 철수 | 500 | 1250 |
| 영희 | 600 | 1850 |
SELECT name, department, salary,
SUM(salary) OVER (
PARTITION BY department
ORDER BY salary
) AS 부서내_누적합
FROM employees;
부서별로 나누고(PARTITION BY), 그 안에서 급여 순으로 누적(ORDER BY)한다.
ROWS BETWEEN A AND B는 계산할 행의 범위(프레임) 를 직접 지정한다.
함수명() OVER (
ORDER BY 컬럼
ROWS BETWEEN 시작 AND 끝
)
| 키워드 | 의미 |
|---|---|
UNBOUNDED PRECEDING | 맨 처음 행 |
UNBOUNDED FOLLOWING | 맨 마지막 행 |
CURRENT ROW | 현재 행 |
N PRECEDING | 현재 행에서 N행 앞 |
N FOLLOWING | 현재 행에서 N행 뒤 |
누적합 (기본 패턴):
SUM(sales) OVER (
ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
-- 맨 처음부터 현재 행까지 누적
전체 합계:
SUM(sales) OVER (
ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)
-- 항상 전체 합계 (현재 행 위치 무관)
이동 평균 (3개월):
AVG(sales) OVER (
ORDER BY month
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
)
-- 직전 2행 + 현재 행, 총 3개 행의 평균
기본값 주의:
ROWS BETWEEN을 생략하면 기본값은UNBOUNDED PRECEDING AND CURRENT ROW다.LAST_VALUE()같은 함수에서 의도치 않은 결과가 나올 수 있으므로 항상 명시하는 것이 좋다.
LAST_VALUE(컬럼)은 범위 안에서 가장 마지막 행의 값을 가져온다.
-- 잘못된 예
SELECT month, sales,
LAST_VALUE(sales) OVER (ORDER BY month) AS 마지막달매출
FROM monthly_sales;
ROWS BETWEEN을 생략하면 기본 범위가 UNBOUNDED PRECEDING ~ CURRENT ROW다. 그러면 2월 행에서 범위는 "1월~2월"이고, 그 범위의 마지막은 2월 자신이 된다. 즉 LAST_VALUE가 그냥 현재 행 값을 돌려줘서 아무 의미가 없어진다.
-- 올바른 예
SELECT month, sales,
LAST_VALUE(sales) OVER (
ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS 마지막달매출
FROM monthly_sales;
범위가 "맨 처음 ~ 맨 끝" 전체로 고정되므로, 현재 행이 1월이든 3월이든 상관없이 항상 마지막 달(12월)의 값을 가져온다.
| month | sales | 마지막달매출 |
|---|---|---|
| 1월 | 100 | 180 |
| 2월 | 150 | 180 |
| 3월 | 120 | 180 |
| 4월 | 200 | 180 |
| 5월 | 180 | 180 |
윈도우 함수 = 집계 함수처럼 계산하지만 행을 줄이지 않는다
OVER (
PARTITION BY → 어느 그룹 안에서 계산할지 (울타리)
ORDER BY → 어떤 순서로 누적할지
ROWS BETWEEN → 정확히 몇 번째 행까지 범위로 볼지
)
| 개념 | 한 줄 요약 |
|---|---|
| 집계 함수 | 여러 행 → 1행으로 줄임 |
| 윈도우 함수 | 행 유지하며 계산 결과 컬럼 추가 |
| PARTITION BY | 그룹별 울타리 치기 |
| ORDER BY | 순서 정해서 위에서부터 누적 |
| UNBOUNDED PRECEDING | 맨 처음 행 |
| UNBOUNDED FOLLOWING | 맨 마지막 행 |
| CURRENT ROW | 지금 이 행 |
| LAST_VALUE | 범위 내 마지막 행의 값 |