MySQL 문법 및 예제 정리 - 날짜, WITH
MySQL에서 날짜와 시간을 다루는 함수는 로그 분석, 기간별 집계, 리텐션 계산의 기반이다. 이 글에서는 날짜 데이터 타입의 차이(DATE vs DATETIME vs TIMESTAMP)와 날짜 비교·범위 추출·CTE(WITH) 활용 패턴을 정리한다.
1. MySQL의 날짜 관련 데이터 타입
| 타입 | 저장 형식 | 특징 | 예시 |
|---|---|---|---|
DATE |
YYYY-MM-DD |
날짜만 저장 (시간 없음) | '2023-09-01' |
DATETIME |
YYYY-MM-DD HH:MM:SS |
날짜+시간 저장, 타임존 변환 없이 입력값 그대로 보존 | '2023-09-01 15:30:00' |
TIMESTAMP |
YYYY-MM-DD HH:MM:SS |
UTC 기준 저장, 세션 타임존에 따라 조회 시 변환 (1970~2038 범위) | '2023-09-01 15:30:00' |
TIME |
HH:MM:SS |
시간만 저장 (음수·초과 시간 허용) | '15:30:00' |
YEAR |
YYYY |
연도만 저장 (1901~2155) | '2023' |
실무 판단 기준: 생성 시각 이력은 타임존 이슈가 있는
TIMESTAMP보다DATETIME(UTC 저장 규약)을, 서버 로컬 기준 만료 계산이 필요한 경우엔TIMESTAMP를 선택하는 것이 일반적이다.1
2. MySQL의 날짜 관련 함수
- MySQL은 날짜와 시간을 다루는 함수를 다수 제공한다. 자주 쓰이는 함수는 다음과 같다.2
1) NOW()
- 현재 날짜와 시간을 반환한다 (형식:
DATETIME). - 예시:
SELECT NOW();
# 출력: 2023-09-01 15:30:00
2) CURDATE()
- 현재 날짜만 반환한다 (
DATE형식). - 예시:
SELECT CURDATE();
# 출력: 2023-09-01
3) CURTIME()
- 현재 시간만 반환한다 (
TIME형식). - 예시:
SELECT CURTIME();
# 출력: 15:30:00
4) DATE()
DATETIME또는TIMESTAMP값에서 날짜만 추출한다.- 예시:
SELECT DATE(NOW());
# 출력: 2023-09-01
5) YEAR(), MONTH(), DAY()
- 날짜에서 연도, 월, 일을 각각 추출한다.
- 예시:
SELECT YEAR(NOW()), MONTH(NOW()), DAY(NOW());
# 출력: 2023 9 1
6) DATE_ADD()와 DATE_SUB()
- 날짜에 기간을 더하거나 빼는 함수다. 일, 월, 연도 등의 단위를 지정해 사용한다.
DATE_ADD()
- 지정된 날짜에 특정 기간을 더한다.
- 예시: 10일 후 날짜를 계산
SELECT DATE_ADD('2023-09-01', INTERVAL 10 DAY);
# 출력: 2023-09-11
DATE_SUB()
- 지정된 날짜에서 특정 기간을 뺀다.
- 예시: 2개월 전 날짜를 계산
SELECT DATE_SUB('2023-09-01', INTERVAL 2 MONTH);
# 출력: 2023-07-01
7) DATEDIFF()
- 두 날짜 간의 일 수 차이를 계산한다.
- 예시:
SELECT DATEDIFF('2023-09-10', '2023-09-01');
# 출력: 9
8) TIMESTAMPDIFF()
- 두 날짜/시간 간의 차이를 특정 단위(연도, 월, 일, 시간 등)로 반환한다.
- 예시: 시간 차이 계산
SELECT TIMESTAMPDIFF(HOUR, '2023-09-01 10:00:00', '2023-09-01 15:00:00');
# 출력: 5
9) STR_TO_DATE()
- 문자열을 날짜로 변환한다. 다양한 형식의 날짜 문자열을
DATE형식으로 바꿀 때 쓴다. - 예시:
SELECT STR_TO_DATE('01,09,2023', '%d,%m,%Y');
# 출력: 2023-09-01
10) DATE_FORMAT()
- 날짜를 지정된 형식으로 변환한다.
DATE,DATETIME값을 원하는 형식으로 표시할 때 쓴다. - 예시:
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s');
# 출력: 2023-09-01 15:30:00
3. 날짜 비교와 범위 선택
1) 날짜 비교
MySQL에서는 날짜를 비교할 때 일반적인 비교 연산자 (=, <, >, <=, >=, !=)를 사용할 수 있다.
SELECT *
FROM Orders
WHERE order_date = '2023-09-01';
# 위 쿼리는 **`order_date`**가 `'2023-09-01'`과 일치하는 모든 행을 반환한다.
2) 날짜 범위 선택 (BETWEEN)
BETWEEN연산자를 사용하면 두 날짜 사이에 있는 데이터를 쉽게 조회할 수 있다.
SELECT *
FROM Orders
WHERE order_date BETWEEN '2023-08-01' AND '2023-08-31';
# 이 쿼리는 **`order_date`가 '2023-08-01'과 '2023-08-31' 사이**에 있는 주문을 반환한다. **`BETWEEN`은 경계 값을 포함**하므로 두 날짜의 00:00:00도 결과에 들어간다.[^3]
3) 날짜 필터링을 위한 서브쿼리
- 조건에 맞는 날짜 범위를 서브쿼리로 계산해 넘길 수도 있다.
# 이 쿼리는 **최근 7일 내의 주문**을 조회한다. `CURDATE()`와 **`INTERVAL`**로 조건을 동적으로 설정한다.
SELECT *
FROM Orders
WHERE order_date >= (SELECT CURDATE() - INTERVAL 7 DAY);
4) 범위 조건의 경계와 주의 사항
order_date가DATETIME이면 위BETWEEN예제의 상한'2023-08-31'은2023-08-31 00:00:00으로 해석되므로, 그날 낮에 기록된 행은 결과에서 빠진다. 경계일 하루치를 통째로 포함하려면order_date >= '2023-08-01' AND order_date < '2023-09-01'처럼 반개구간으로 쓴다.3TIMESTAMP컬럼에 문자열이나NOW()를 비교하면 세션 타임존을 기준으로 변환되므로, 서버의 타임존 설정이 바뀌면 같은 데이터라도 조건의 결과가 달라질 수 있다.TIMESTAMP는 1장 표의 범위(1970-01-01 00:00:01UTC ~2038-01-19 03:14:07UTC)를 벗어난 값을 저장할 수 없으므로, 2038년 이후 만료 시각을 담을 컬럼에는DATETIME을 쓴다.
4. 날짜 연산과 추가 활용
1) 날짜와 시간의 더하기 및 빼기
DATE_ADD()와DATE_SUB()함수는 날짜 연산을 쉽게 처리할 수 있다. 특정 날짜에 일, 월, 연도 등을 더하거나 빼서 계산한다.
SELECT DATE_ADD(CURDATE(), INTERVAL 10 DAY) AS ten_days_later,
DATE_SUB(CURDATE(), INTERVAL 5 DAY) AS five_days_ago;
/*
출력:
| ten_days_later | five_days_ago |
|----------------|---------------|
| 2023-09-11 | 2023-08-27 |
*/
2) 시간 차이 계산
TIMEDIFF()함수는 두 시간 사이의 차이를 계산할 때 사용된다.
SELECT TIMEDIFF('2023-09-01 15:30:00', '2023-09-01 10:00:00') AS time_difference;
# 출력: 05:30:00
3) 특정 형식의 날짜 추출 및 변환
DATE_FORMAT()을 통해 날짜의 출력 형식을 바꿀 수 있다. 날짜를 다양한 형식으로 표시할 수 있으며, 주로 보고서처럼 출력 형식 요구사항이 있을 때 쓴다.
SELECT DATE_FORMAT(NOW(), '%W, %M %d, %Y') AS formatted_date;
# 출력: Friday, September 01, 2023
WITH
WITH구문은 MySQL에서 공통 테이블 표현식(CTE, Common Table Expression)을 정의할 때 사용된다.- CTE는 복잡한 쿼리를 더 읽기 쉽고 관리하기 쉽게 만들어 준다.
- CTE는 MySQL 8.0 이상에서만 지원하므로, 5.7 이하에서는 파생 테이블이나 임시 테이블로 대체해야 한다.4
1. 기본 개념
- CTE 정의:
WITH뒤에 CTE 이름과 그 정의를 작성한다. 일종의 임시 테이블로, 이후 메인 쿼리에서 참조할 수 있다. - 구조:
WITH cte_name AS ( SELECT ... -- CTE 정의 쿼리 ) SELECT ... FROM cte_name;
2. 사용 사례
-
복잡한 쿼리의 간소화: CTE를 사용하면 서브쿼리의 중첩을 피하고, 복잡한 쿼리를 단계별로 분리하여 이해하기 쉽게 만들 수 있다.
-
재사용 가능한 쿼리: 동일한 중간 결과를 여러 번 사용해야 할 때, CTE를 정의하고 이를 참조하면 중복된 코드를 줄일 수 있다.
# 예시
WITH AvgSalary AS (
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
)
SELECT employee_name, salary
FROM employees e
JOIN AvgSalary a ON e.department_id = a.department_id
WHERE e.salary > a.avg_salary;
-- CTE `AvgSalary`: 각 부서의 평균 급여를 계산
-- 메인 쿼리: 이 CTE를 참조해 평균 급여보다 높은 직원들을 선택
- 응용: 집계 함수와 조건문에서 해당하지 않는 값을 반환하는 방법
- 집계 함수나 조건문을 쓸 때 특정 범주나 값이 데이터에 아예 존재하지 않는 경우가 있다. 이럴 때 아래처럼 기준이 되는 범주를 CTE로 먼저 만들어 두면, 해당 범주도 0건으로 함께 반환할 수 있다.
# 설명: 세 개의 상수 문자열을 각각 SELECT로 반환한 뒤, UNION 연산자로 하나의 집합으로 결합하는 쿼리다
# 첫 번째 SELECT는 'Low Salary'를 반환하고, 이 값을 category라는 별칭으로 지정한다.
WITH SalaryCategories AS (
SELECT 'Low Salary' AS category
UNION
SELECT 'Average Salary'
UNION
SELECT 'High Salary'
)
댓글남기기