MSSQL LAB
MSSQL 1.10 · 1부. 기초

문자열과 날짜 함수

자주 사용하는 내장 함수와 형 변환입니다. 열에 함수를 씌우면 인덱스를 사용하지 못한다는 것으로 1부를 맺습니다.

예상 학습 시간 20분 난이도 입문
개념 설명

값을 가공해서 냅니다

지금까지는 담긴 값을 그대로 꺼냈습니다. 함수를 사용하면 꺼내면서 가공할 수 있습니다 — 일부만 자르거나, 날짜에서 연도만 빼거나, 형식을 바꾸는 일입니다.

자주 사용하는 것만 봅니다. 전부 외울 필요는 없고, 이런 일을 할 수 있다는 것만 알아 두면 필요할 때 이름을 찾으면 됩니다.

최소 예제

길이 — LEN 과 DATALENGTH

둘 다 길이를 재지만 세는 것이 다릅니다.

SQL
SELECT LEN(N'가나다  ')        AS len_결과,
       DATALENGTH(N'가나다  ') AS bytes_결과,
       LEN(N'  가나다')        AS 앞공백_len;
결과
len_결과bytes_결과앞공백_len
3105
(1개 행이 영향을 받음)

LEN 은 뒤쪽 공백을 세지 않습니다. 앞쪽 공백은 셉니다(5). 글자 다섯인데 3 이 나오는 자리가 여기입니다. 실제로 몇 바이트를 차지하는지는 DATALENGTH 로 봅니다 — 한글 3자에 공백 2개가 각각 2바이트씩이라 10 입니다.

상세 사용법

잘라내기

SQL
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가운데
조인 질문드립니다조인 드립니다질문드립니
트랜잭션 질문드립니다트랜잭드립니다션 질문드
페이징 질문드립니다페이징드립니다 질문드립
(3개 행이 영향을 받음)

SUBSTRING(값, 시작, 길이)시작은 1 부터 입니다. 0 이 아닙니다. 목록 화면에서 본문을 미리 보여 줄 때 자주 사용합니다.

찾기와 바꾸기

SQL
SELECT CHARINDEX(N'질문', N'인덱스 질문드립니다')   AS 위치,
       CHARINDEX(N'없는말', N'인덱스 질문드립니다') AS 못찾으면,
       REPLACE(N'인덱스 질문드립니다', N'질문', N'답변') AS 바꾼결과;
결과
위치못찾으면바꾼결과
50인덱스 답변드립니다
(1개 행이 영향을 받음)

못 찾으면 0 입니다. NULL 이 아니므로 WHERE CHARINDEX(…) > 0 처럼 비교해 사용합니다.

상세 사용법

공백 다듬기 — 여기서 한글이 깨집니다

TRIM 은 앞뒤 공백을 없앱니다. 그런데 넘기는 값에 N 을 붙이지 않으면 1.4 에서 본 일이 그대로 벌어집니다.

SQL
SELECT '[' + TRIM('  가운데  ') + ']'      AS N없이,
       N'[' + TRIM(N'  가운데  ') + N']' AS N붙여서;
결과
N없이N붙여서
[???][가운데]
(1개 행이 영향을 받음)

함수에 넘기는 리터럴에도 N 이 필요합니다. 열에서 꺼낸 값은 그 열의 형식을 따르므로 문제가 없지만, 문장에 직접 적는 한글은 늘 N 을 붙이는 습관을 들이십시오.

함수하는 일
TRIM앞뒤 공백을 없앱니다
LTRIM · RTRIM앞만 · 뒤만 없앱니다
UPPER · LOWER대문자 · 소문자로 바꿉니다
CONCAT_WS구분자를 끼워 이어 붙입니다. NULL 은 건너뜁니다
SQL
SELECT CONCAT_WS(N' / ', N'홍길동', NULL, N'서울') AS concat_ws결과;
결과
concat_ws결과
홍길동 / 서울
(1개 행이 영향을 받음)

가운데 NULL 을 건너뛰면서 구분자도 함께 빠졌습니다. 주소를 이어 붙일 때처럼 빈 자리가 생길 수 있는 곳에 알맞습니다.

상세 사용법

날짜에서 꺼내기

SQL
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;
결과
numreg_date요일
12026-01-01 12:00:0020261112Thursday
22026-01-01 15:00:0020261115Thursday
(2개 행이 영향을 받음)

DATEPART 는 숫자를, DATENAME 은 이름을 냅니다. 요일 이름은 서버의 언어 설정을 따릅니다 — 위 결과가 영어인 것은 이 서버가 그렇게 설치되어서입니다. 화면에 낼 글자라면 서버 설정에 기대지 말고 응용 프로그램에서 만드는 편이 안전합니다.

더하고 빼기 — 월말을 조심하십시오

SQL
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-312026-02-012026-02-282026-02-28
(1개 행이 영향을 받음)

1월 31일에 한 달을 더하면 2월 31일이 없으므로 그 달의 말일이 됩니다. 달마다 날수가 달라서 생기는 일이고, 결제일이나 만료일을 셈할 때 걸립니다.

차이 재기 — 경계를 셉니다

SQL
SELECT DATEDIFF(DAY,   '2026-01-01', '2026-03-01') AS 일수,
       DATEDIFF(MONTH, '2026-01-31', '2026-02-01') AS 개월수_주의;
결과
일수개월수_주의
591
(1개 행이 영향을 받음)

하루 차이인데 1개월이 나왔습니다. DATEDIFF 는 두 날짜 사이의 기간이 아니라 경계를 몇 번 넘었는지를 셉니다. 1월 31일에서 2월 1일로 가면서 달의 경계를 한 번 넘었으므로 1 입니다. 나이나 근속 기간을 이렇게 셈하면 어긋납니다.

상세 사용법

글자로 바꾸기 — CONVERT 와 FORMAT

SQL
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_23style_120style_111
2026-01-312026-01-31 14:05:092026/01/31
(1개 행이 영향을 받음)

CONVERT 의 셋째 인자는 형식 번호입니다. 23(날짜만) · 120(날짜와 시각) 두 개만 알아 두면 대개 됩니다.

FORMAT 은 원하는 모양을 직접 적을 수 있습니다. 여기서도 한글에는 N 이 필요합니다.

SQL
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일
(1개 행이 영향을 받음)

FORMAT 은 느립니다. 행마다 .NET 서식 엔진을 부르기 때문에 수천 행을 다루면 차이가 크게 납니다. 목록 화면에서는 CONVERT 를 쓰거나, 날짜를 그대로 넘기고 응용 프로그램에서 모양을 만드는 편이 낫습니다.

안전한 변환 — TRY_CAST

1.4 에서 본 245 번 오류를 기억하십니까. TRY_CAST 는 변환할 수 없을 때 오류 대신 NULL 을 냅니다.

SQL
SELECT TRY_CAST(N'123' AS INT) AS 되는것,
       TRY_CAST(N'열' AS INT)   AS 안되면_NULL;
결과
되는것안되면_NULL
123NULL
(1개 행이 영향을 받음)

밖에서 들어온 값을 다룰 때 유용합니다. 문장 전체가 오류로 멈추는 대신 그 행만 NULL 이 되므로, 나머지를 처리하고 잘못된 값만 따로 가려낼 수 있습니다.

상세 사용법

열에 함수를 씌우면 인덱스를 사용하지 못합니다

1부에서 마지막으로 남길 이야기입니다. 조건에서 열을 함수로 감싸면 그 열에 인덱스가 있어도 사용하지 못합니다.

-- 인덱스를 사용하지 못합니다
WHERE YEAR(reg_date) = 2026

-- 같은 뜻이고 인덱스를 사용합니다
WHERE reg_date >= '2026-01-01' AND reg_date < '2027-01-01'

인덱스는 열에 담긴 값으로 정렬되어 있습니다. YEAR(reg_date) 는 그 값이 아니라 가공한 결과라서, 모든 행을 하나씩 계산해 보기 전에는 조건에 맞는지 알 수 없습니다.

열은 그대로 두고 값 쪽을 가공하십시오. 지금은 자료가 적어 차이가 보이지 않지만, 5.4 에서 큰 자료로 실제 차이를 확인합니다.

실습 문제

직접 해보기

1. 제목 미리보기 만들기 난이도 하

글 제목을 앞 6자만 보이고 그 뒤에 를 붙여 보세요. 6자 이하인 제목은 그대로 나와야 합니다.

길이를 재서 조건을 나누면 됩니다. CASE 는 2.8 에서 다루지만 여기서는 IIF 로도 됩니다.
SELECT TOP 5 title, IIF(LEN(title) > 6, LEFT(title, 6) + N'…', title) AS 미리보기 FROM Board.POSTS ORDER BY num; -- LEN 이 뒤 공백을 세지 않는다는 점도 함께 떠올려 두십시오.
2. 인덱스를 살리는 조건 난이도 중

"2026년 1월에 작성한 글" 을 찾는 조건을 열에 함수를 씌우지 않고 적어 보세요.

SELECT COUNT(*) FROM Board.POSTS WHERE reg_date >= '2026-01-01' AND reg_date < '2026-02-01'; -- 다음 달 1일보다 작다(<)로 적는 것이 요령입니다. -- BETWEEN '2026-01-01' AND '2026-01-31' 로 적으면 -- 1월 31일 낮에 작성한 글이 빠집니다(1.6). -- 이렇게 적으면 인덱스를 사용하지 못합니다. -- WHERE YEAR(reg_date) = 2026 AND MONTH(reg_date) = 1
요약
  • LEN뒤쪽 공백을 세지 않습니다. 실제 크기는 DATALENGTH 입니다.
  • SUBSTRING 의 시작 자리는 1 부터이고, CHARINDEX 는 못 찾으면 0 입니다.
  • 함수에 넘기는 한글에도 N 을 붙입니다. 빠뜨리면 TRIMFORMAT 도 글자를 깨뜨립니다.
  • DATEADD 로 한 달을 더하면 없는 날짜는 말일이 됩니다.
  • DATEDIFF 는 기간이 아니라 경계를 넘은 횟수를 셉니다.
  • FORMAT 은 느립니다. 목록에는 CONVERT 를 쓰거나 응용 프로그램에서 모양을 만듭니다.
  • 조건에서 열을 함수로 감싸지 마십시오. 인덱스를 사용하지 못합니다.