문자열과 날짜 함수
자주 사용하는 내장 함수와 형 변환입니다. 열에 함수를 씌우면 인덱스를 사용하지 못한다는 것으로 1부를 맺습니다.
값을 가공해서 냅니다
지금까지는 담긴 값을 그대로 꺼냈습니다. 함수를 사용하면 꺼내면서 가공할 수 있습니다 — 일부만 자르거나, 날짜에서 연도만 빼거나, 형식을 바꾸는 일입니다.
자주 사용하는 것만 봅니다. 전부 외울 필요는 없고, 이런 일을 할 수 있다는 것만 알아 두면 필요할 때 이름을 찾으면 됩니다.
길이 — LEN 과 DATALENGTH
둘 다 길이를 재지만 세는 것이 다릅니다.
SELECT LEN(N'가나다 ') AS len_결과, DATALENGTH(N'가나다 ') AS bytes_결과, LEN(N' 가나다') AS 앞공백_len;
| len_결과 | bytes_결과 | 앞공백_len |
|---|---|---|
| 3 | 10 | 5 |
LEN 은 뒤쪽 공백을 세지 않습니다.
앞쪽 공백은 셉니다(5). 글자 다섯인데 3 이 나오는 자리가 여기입니다.
실제로 몇 바이트를 차지하는지는 DATALENGTH 로 봅니다 —
한글 3자에 공백 2개가 각각 2바이트씩이라 10 입니다.
잘라내기
SELECT TOP 3 title, LEFT(title, 3) AS 왼쪽3, RIGHT(title, 4) AS 오른쪽4, SUBSTRING(title, 4, 5) AS 가운데 FROM Board.POSTS ORDER BY num;
| title | 왼쪽3 | 오른쪽4 | 가운데 |
|---|---|---|---|
| 조인 질문드립니다 | 조인 | 드립니다 | 질문드립니 |
| 트랜잭션 질문드립니다 | 트랜잭 | 드립니다 | 션 질문드 |
| 페이징 질문드립니다 | 페이징 | 드립니다 | 질문드립 |
SUBSTRING(값, 시작, 길이) 의 시작은 1 부터
입니다. 0 이 아닙니다. 목록 화면에서 본문을 미리 보여 줄 때 자주 사용합니다.
찾기와 바꾸기
SELECT CHARINDEX(N'질문', N'인덱스 질문드립니다') AS 위치, CHARINDEX(N'없는말', N'인덱스 질문드립니다') AS 못찾으면, REPLACE(N'인덱스 질문드립니다', N'질문', N'답변') AS 바꾼결과;
| 위치 | 못찾으면 | 바꾼결과 |
|---|---|---|
| 5 | 0 | 인덱스 답변드립니다 |
못 찾으면 0 입니다. NULL 이 아니므로
WHERE CHARINDEX(…) > 0 처럼 비교해 사용합니다.
공백 다듬기 — 여기서 한글이 깨집니다
TRIM 은 앞뒤 공백을 없앱니다. 그런데 넘기는 값에
N 을 붙이지 않으면 1.4 에서 본 일이 그대로
벌어집니다.
SELECT '[' + TRIM(' 가운데 ') + ']' AS N없이, N'[' + TRIM(N' 가운데 ') + N']' AS N붙여서;
| N없이 | N붙여서 |
|---|---|
| [???] | [가운데] |
함수에 넘기는 리터럴에도 N 이 필요합니다.
열에서 꺼낸 값은 그 열의 형식을 따르므로 문제가 없지만, 문장에 직접 적는 한글은
늘 N 을 붙이는 습관을 들이십시오.
| 함수 | 하는 일 |
|---|---|
| TRIM | 앞뒤 공백을 없앱니다 |
| LTRIM · RTRIM | 앞만 · 뒤만 없앱니다 |
| UPPER · LOWER | 대문자 · 소문자로 바꿉니다 |
| CONCAT_WS | 구분자를 끼워 이어 붙입니다. NULL 은 건너뜁니다 |
SELECT CONCAT_WS(N' / ', N'홍길동', NULL, N'서울') AS concat_ws결과;
| concat_ws결과 |
|---|
| 홍길동 / 서울 |
가운데 NULL 을 건너뛰면서 구분자도 함께 빠졌습니다. 주소를 이어 붙일 때처럼 빈 자리가 생길 수 있는 곳에 알맞습니다.
날짜에서 꺼내기
SELECT TOP 2 num, reg_date, YEAR(reg_date) AS 년, MONTH(reg_date) AS 월, DAY(reg_date) AS 일, DATEPART(HOUR, reg_date) AS 시, DATENAME(WEEKDAY, reg_date) AS 요일 FROM Board.POSTS ORDER BY num;
| num | reg_date | 년 | 월 | 일 | 시 | 요일 |
|---|---|---|---|---|---|---|
| 1 | 2026-01-01 12:00:00 | 2026 | 1 | 1 | 12 | Thursday |
| 2 | 2026-01-01 15:00:00 | 2026 | 1 | 1 | 15 | Thursday |
DATEPART 는 숫자를, DATENAME 은
이름을 냅니다. 요일 이름은 서버의 언어 설정을 따릅니다 — 위 결과가
영어인 것은 이 서버가 그렇게 설치되어서입니다. 화면에 낼 글자라면 서버 설정에
기대지 말고 응용 프로그램에서 만드는 편이 안전합니다.
더하고 빼기 — 월말을 조심하십시오
SELECT CAST('2026-01-31' AS DATE) AS 기준, DATEADD(DAY, 1, CAST('2026-01-31' AS DATE)) AS 하루뒤, DATEADD(MONTH, 1, CAST('2026-01-31' AS DATE)) AS 한달뒤, EOMONTH(CAST('2026-02-05' AS DATE)) AS 그달말일;
| 기준 | 하루뒤 | 한달뒤 | 그달말일 |
|---|---|---|---|
| 2026-01-31 | 2026-02-01 | 2026-02-28 | 2026-02-28 |
1월 31일에 한 달을 더하면 2월 31일이 없으므로 그 달의 말일이 됩니다. 달마다 날수가 달라서 생기는 일이고, 결제일이나 만료일을 셈할 때 걸립니다.
차이 재기 — 경계를 셉니다
SELECT DATEDIFF(DAY, '2026-01-01', '2026-03-01') AS 일수, DATEDIFF(MONTH, '2026-01-31', '2026-02-01') AS 개월수_주의;
| 일수 | 개월수_주의 |
|---|---|
| 59 | 1 |
하루 차이인데 1개월이 나왔습니다.
DATEDIFF 는 두 날짜 사이의 기간이 아니라
경계를 몇 번 넘었는지를 셉니다. 1월 31일에서 2월 1일로 가면서 달의
경계를 한 번 넘었으므로 1 입니다. 나이나 근속 기간을 이렇게 셈하면 어긋납니다.
글자로 바꾸기 — CONVERT 와 FORMAT
DECLARE @d DATETIME2(0) = '2026-01-31 14:05:09'; SELECT CONVERT(VARCHAR(30), @d, 23) AS style_23, CONVERT(VARCHAR(30), @d, 120) AS style_120, CONVERT(VARCHAR(30), @d, 111) AS style_111;
| style_23 | style_120 | style_111 |
|---|---|---|
| 2026-01-31 | 2026-01-31 14:05:09 | 2026/01/31 |
CONVERT 의 셋째 인자는 형식 번호입니다.
23(날짜만) · 120(날짜와 시각) 두 개만 알아 두면 대개 됩니다.
FORMAT 은 원하는 모양을 직접 적을 수 있습니다.
여기서도 한글에는 N 이 필요합니다.
DECLARE @d datetime2(0) = '2026-01-31 14:05:09'; SELECT FORMAT(@d, 'yyyy년 M월 d일') AS N없이, FORMAT(@d, N'yyyy년 M월 d일') AS N붙여서;
| N없이 | N붙여서 |
|---|---|
| 2026? 1? 31? | 2026년 1월 31일 |
FORMAT 은 느립니다. 행마다 .NET 서식
엔진을 부르기 때문에 수천 행을 다루면 차이가 크게 납니다. 목록 화면에서는
CONVERT 를 쓰거나, 날짜를 그대로 넘기고 응용
프로그램에서 모양을 만드는 편이 낫습니다.
안전한 변환 — TRY_CAST
1.4 에서 본 245 번 오류를 기억하십니까. TRY_CAST 는
변환할 수 없을 때 오류 대신 NULL 을 냅니다.
SELECT TRY_CAST(N'123' AS INT) AS 되는것, TRY_CAST(N'열' AS INT) AS 안되면_NULL;
| 되는것 | 안되면_NULL |
|---|---|
| 123 | NULL |
밖에서 들어온 값을 다룰 때 유용합니다. 문장 전체가 오류로 멈추는 대신 그 행만 NULL 이 되므로, 나머지를 처리하고 잘못된 값만 따로 가려낼 수 있습니다.
열에 함수를 씌우면 인덱스를 사용하지 못합니다
1부에서 마지막으로 남길 이야기입니다. 조건에서 열을 함수로 감싸면 그 열에 인덱스가 있어도 사용하지 못합니다.
-- 인덱스를 사용하지 못합니다 WHERE YEAR(reg_date) = 2026 -- 같은 뜻이고 인덱스를 사용합니다 WHERE reg_date >= '2026-01-01' AND reg_date < '2027-01-01'
인덱스는 열에 담긴 값으로 정렬되어 있습니다.
YEAR(reg_date) 는 그 값이 아니라 가공한 결과라서, 모든 행을
하나씩 계산해 보기 전에는 조건에 맞는지 알 수 없습니다.
열은 그대로 두고 값 쪽을 가공하십시오. 지금은 자료가 적어 차이가 보이지 않지만, 5.4 에서 큰 자료로 실제 차이를 확인합니다.
직접 해보기
글 제목을 앞 6자만 보이고 그 뒤에 … 를 붙여 보세요. 6자 이하인 제목은 그대로 나와야 합니다.
"2026년 1월에 작성한 글" 을 찾는 조건을 열에 함수를 씌우지 않고 적어 보세요.
LEN은 뒤쪽 공백을 세지 않습니다. 실제 크기는DATALENGTH입니다.SUBSTRING의 시작 자리는 1 부터이고,CHARINDEX는 못 찾으면 0 입니다.- 함수에 넘기는 한글에도
N을 붙입니다. 빠뜨리면TRIM도FORMAT도 글자를 깨뜨립니다. DATEADD로 한 달을 더하면 없는 날짜는 말일이 됩니다.DATEDIFF는 기간이 아니라 경계를 넘은 횟수를 셉니다.FORMAT은 느립니다. 목록에는CONVERT를 쓰거나 응용 프로그램에서 모양을 만듭니다.- 조건에서 열을 함수로 감싸지 마십시오. 인덱스를 사용하지 못합니다.