소스: sql-src/mid_07_string_date/02_date.sql, 03_dialects.sql. 날짜 계산과 DB별 함수 이름 차이를 확인합니다.
SELECT
DATE '2024-01-15' + 10 AS plus_10_day
, DATEADD('MONTH', 1, DATE '2024-01-15') AS mid_month
, DATEADD('MONTH', 1, DATE '2024-01-31') AS from_month_end
FROM orders
WHERE id = 1;PLUS_10_DAY | MID_MONTH | FROM_MONTH_END
------------+------------+---------------
2024-01-25 | 2024-02-15 | 2024-02-29
(1행)날짜 + 숫자 는 그 일수만큼 뒤 날짜이고, DATEADD('MONTH', n, 날짜) 는 개월 단위로 더합니다. 1월 15일에 1개월을 더하면 2월 15일로 같은 일자로 옮겨가지만, 1월 31일에 1개월을 더하면 2월에는 31일이 없어 2월 말일(2024-02-29, 윤년)로 자동 조정됩니다.
1월 31일처럼 옮겨 갈 날이 없으면 말일로 맞추는 규칙은 Oracle ADD_MONTHS, MySQL DATE_ADD, MSSQL DATEADD 가 같습니다.
원래 날짜가 말일이면 다릅니다. 실제 Oracle 은 2024-02-29 + 1개월을 3월 말일(2024-03-31)로 맞추고, MySQL·MSSQL 은 같은 일자인 2024-03-29 를 돌려줍니다. H2 는 Oracle 모드에서도 2024-03-29 를 돌려줘 이 차이는 재현되지 않으며 문법 검토만 했습니다. 월말 정산처럼 "매달 말일"이 필요하면 개월을 더한 뒤 LAST_DAY 를 한 번 더 씌우는 것이 안전합니다.
SELECT
DATE_TRUNC('MONTH', ord_ts) AS month_start
, LAST_DAY(ord_ts) AS month_end
, DATEDIFF('DAY', DATE '2024-01-01', DATE '2024-03-01') AS day_diff
, DATEDIFF('MONTH', DATE '2024-01-01', DATE '2024-03-01') AS month_diff
FROM orders
WHERE id = 3;MONTH_START | MONTH_END | DAY_DIFF | MONTH_DIFF
----------------------+------------+----------+-----------
2024-02-01 00:00:00.0 | 2024-02-29 | 60 | 2
(1행)DATE_TRUNC('MONTH', ...) 는 그달 1일 0시로, LAST_DAY 는 그달의 마지막 날짜로 잘라냅니다. Oracle 은 TRUNC(d, 'MM') 로 월초를 구하지만 H2 는 이 2인자 형태를 지원하지 않아 DATE_TRUNC 로 대신합니다.
DATEDIFF('DAY', ...)·DATEDIFF('MONTH', ...) 는 두 날짜의 일수·개월 수 차이이며, Oracle 의 MONTHS_BETWEEN 은 일수까지 반영한 소수를 주는 점만 다릅니다.
SELECT
FORMATDATETIME(ord_ts, 'yyyy-MM-dd') AS date_to_str
, CAST(PARSEDATETIME('2024-01-31', 'yyyy-MM-dd')
AS DATE) AS str_to_date
FROM orders
WHERE id = 1;DATE_TO_STR | STR_TO_DATE
------------+------------
2024-01-31 | 2024-01-31
(1행)FORMATDATETIME 은 날짜를 지정한 형식의 문자열로, PARSEDATETIME 은 문자열을 날짜로 바꿉니다. Oracle 의 TO_CHAR·TO_DATE 와 역할이 같지만 형식 문자(yyyy-MM-dd)는 Oracle 의 YYYY-MM-DD 와 대소문자 표기가 다릅니다.
SELECT
id
, cust
, ord_ts
FROM orders
WHERE ord_ts BETWEEN DATE '2024-02-01' AND DATE '2024-02-15'
ORDER BY id;ID | CUST | ORD_TS
---+--------+----------------------
2 | 이서연 | 2024-02-01 23:59:00.0
(1행)2월 15일 낮 12시에 주문한 박도윤(id 3)이 빠졌습니다. DATE '2024-02-15' 가 "2월 15일 0시"로 해석되어, 그보다 늦은 시각의 주문은 범위 밖으로 밀려났기 때문입니다.
SELECT
id
, cust
, ord_ts
FROM orders
WHERE ord_ts >= DATE '2024-02-01'
AND ord_ts < DATE '2024-02-16'
ORDER BY id;ID | CUST | ORD_TS
---+--------+----------------------
2 | 이서연 | 2024-02-01 23:59:00.0
3 | 박도윤 | 2024-02-15 12:00:00.0
(2행)종료 조건을 < 2024-02-16(다음 날)으로 바꾸면 2월 15일 안의 모든 시각이 포함되어 박도윤의 주문도 잡힙니다. 이 패턴은 시간 성분이 있는 날짜 컬럼이라면 DB를 가리지 않고 항상 안전합니다.
LENGTHB 는 3절 예제1 의 OCTET_LENGTH 로, TRUNC(날짜,'MM') 은 4절 변형2 의 DATE_TRUNC 로 이미 대안을 확인했습니다. 여기서는 INITCAP·MONTHS_BETWEEN 을 봅니다.
-- Oracle · Tibero
SET MODE Oracle;
-- @error
SELECT INITCAP('hello world') FROM emp WHERE id = 1;
SELECT
CONCAT(UPPER(SUBSTR('hello world', 1, 1)),
LOWER(SUBSTR('hello world', 2))) AS initcap_alt
FROM emp
WHERE id = 1;
-- @error
SELECT MONTHS_BETWEEN(hired, DATE '2024-01-01') FROM emp WHERE id = 1;예상 오류: Function "INITCAP" not foundINITCAP_ALT
-----------
Hello world
(1행)예상 오류: Function "MONTHS_BETWEEN" not found단어 첫 글자만 대문자로 바꾸는 INITCAP 은 H2 에 없습니다(H2 미지원, 문법 검토만). 첫 글자만 SUBSTR+UPPER, 나머지는 SUBSTR+LOWER 로 떼어 이어 붙이면 같은 결과를 만들 수 있습니다. MONTHS_BETWEEN 도 H2 미지원이며, 4절 변형2 에서 실행한 DATEDIFF('MONTH', ...) 가 정수 단위 대안입니다(실제 Oracle 은 일수까지 반영한 소수를 줍니다).
-- MySQL
SET MODE MySQL;
SELECT
LENGTH(name) AS length_result
, CHAR_LENGTH(name) AS char_len
FROM emp
WHERE id = 1;
SELECT
CONCAT('김', NULL) AS concat_with_null
FROM emp
WHERE id = 1;
-- @error
SELECT DATE_ADD(hired, INTERVAL 1 MONTH) FROM emp WHERE id = 1;
SELECT
hired + INTERVAL '1' MONTH AS date_add_alt
FROM emp
WHERE id = 1;LENGTH_RESULT | CHAR_LEN
--------------+---------
3 | 3
(1행)CONCAT_WITH_NULL
----------------
김
(1행)예상 오류: Function "DATE_ADD" not foundDATE_ADD_ALT
------------
2024-02-29
(1행)실제 MySQL 의 LENGTH 는 바이트 수(9)를 주지만 H2 는 문자 수(3)를 주고, 실제 MySQL 의 CONCAT 은 NULL 이 섞이면 결과가 NULL 이지만 H2 는 NULL 을 빼고 이어 붙입니다. 둘 다 H2 에서 재현 안 됨(문법 검토만)입니다.
DATE_ADD 함수명도 H2 미지원이지만(문법 검토만), 날짜 + INTERVAL '값' 단위 표준 구문은 그대로 동작하고 월말 자동 조정도 같습니다. DATEDIFF(a, b) 2인자 형태와 DATE_FORMAT·STR_TO_DATE 도 같은 이유로 H2 미지원이며, 대안은 각각 4절 변형2 의 DATEDIFF('DAY', ...), 변형3 의 FORMATDATETIME·PARSEDATETIME 과 같습니다.
-- MSSQL
SET MODE MSSQLServer;
-- @error
SELECT REPLICATE('0', 3) FROM emp WHERE id = 1;
SELECT
LPAD(CAST(sal AS VARCHAR(10)), 6, '0') AS zero_pad_alt
FROM emp
WHERE id = 1;
-- @error
SELECT CONVERT(VARCHAR(10), hired, 23) FROM emp WHERE id = 1;
SELECT
CONVERT(VARCHAR(10), hired) AS convert_alt
FROM emp
WHERE id = 1;예상 오류: Function "REPLICATE" not foundZERO_PAD_ALT
------------
000900
(1행)예상 오류: Syntax error in SQL statement "SELECT CONVERT(VARCHAR(10), hired[*], 23) FROM emp WHERE id = 1"; expected "(, ., [, ., ::, AT, FORMAT, *, /, %, +, -, ||, NOT, IS, ILIKE, REGEXP, AND, OR, )"CONVERT_ALT
-----------
2024-01-31
(1행)REPLICATE·DATALENGTH·EOMONTH 는 모두 H2 미지원입니다(문법 검토만). DATALENGTH 대안은 3절 예제1 의 OCTET_LENGTH(9)와 같고, EOMONTH 대안은 4절 변형2 의 LAST_DAY 와 같습니다.
MSSQL 의 CONVERT(타입, 값, 스타일번호) 는 스타일 23 이 YYYY-MM-DD 형식인데, H2 는 스타일 번호를 지원하지 않고(문법 검토만) 항상 ISO 형식만 줍니다. FORMAT(hired, 'yyyy-MM-dd') 도 H2 미지원이라 같은 방식으로 확인됩니다.
주의H2 는 모드와 무관하게
LPAD·RPAD·LAST_DAY를 전역으로 지원하고CONVERT는 스타일 번호 없이도 실행되지만, 실제 MSSQL 에는LPAD·RPAD·LAST_DAY가 없고CONVERT는 스타일 번호가 없으면Jan 31 2024같은 영문 형식이 나옵니다(H2 에서 재현 안 됨, 문법 검토만). 실제 코드에서는RIGHT(REPLICATE('0', n) + 값, n)·EOMONTH·CONVERT(..., 23)을 각각 써야 합니다.