SUBSTRING은 지정한 위치에서 시작해 원하는 글자 수만큼 문자열의 일부를 반환해요. SQL에서 텍스트 컬럼의 접두사, 코드, 이메일 도메인을 뽑아낼 때 쓰는 함수예요. 예제는 PostgreSQL, MySQL, SQLite, SQL Server에서 동작해요.
SUBSTRING 사용법
SUBSTRING에 문자열, 시작 위치(첫 글자가 1), 가져올 글자 수를 넘겨주세요. 길이를 생략하면 끝까지 모두 가져와요:
SUBSTRING(string, start, length)
SELECT SUBSTRING('Joe Bloggs', 1, 3);
-- Output: Joe
-- No length: the rest of the string (SQL Server needs all three arguments)
SELECT SUBSTRING('Joe Bloggs', 5);
-- Output: Bloggs
실제 쿼리에서는 보통 문자열 자리에 컬럼이 들어가요. ORD-2024-001 같은 주문 번호는 5~8번째 글자에 연도가 들어 있어요:
-- Input: orders
-- order_number
-- ------------
-- ORD-2024-001
-- ORD-2025-014
-- ORD-2026-102
SELECT order_number, SUBSTRING(order_number, 5, 4) AS year
FROM orders;
-- Output:
-- order_number | year
-- -------------+-----
-- ORD-2024-001 | 2024
-- ORD-2025-014 | 2025
-- ORD-2026-102 | 2026
알아 둘 규칙
- 위치는 0이 아니라 1부터 시작해요. 음수 시작 위치는 문자열 끝에서부터 거꾸로 세지만, MySQL과 SQLite에서만 그래요:
SELECT SUBSTRING('Joe Bloggs', -6, 6);
-- Output: Bloggs
-- PostgreSQL and SQL Server return '' (an empty string) instead
- 끝을 넘어가도 오류가 나지 않아요. 존재하는 글자만 반환하고, 시작 위치가 끝을 넘으면 빈 문자열을 반환해요:
SELECT SUBSTRING('Bob', 1, 10);
-- Output: Bob
SELECT SUBSTRING('Hello', 10, 5);
-- Output: '' (an empty string)
- NULL을 넣으면 NULL이 나와요. 대신 값이 필요하다면 호출을 COALESCE로 감싸세요:
SELECT SUBSTRING(NULL, 1, 5);
-- Output: NULL
-- SQL Server rejects a bare NULL here, but a NULL column gives NULL
-
접두사로 필터링할 때는 SUBSTRING 대신 LIKE를 쓰세요.
WHERE SUBSTRING(product_code, 1, 3) = 'ELE'는WHERE product_code LIKE 'ELE%'와 같은 행을 반환하지만,product_code의 인덱스를 활용할 수 있는 건 LIKE 쪽뿐이에요.
다른 문법
SUBSTRING의 다른 이름인 SUBSTR, 그리고 양 끝에서 글자를 가져오는 LEFT와 RIGHT도 자주 보게 될 거예요:
-- Not in SQL Server
SELECT SUBSTR('Joe Bloggs', 1, 3);
-- Output: Joe
-- Not in SQLite: use SUBSTRING('Joe Bloggs', 1, 3) and SUBSTRING('Joe Bloggs', -6)
SELECT LEFT('Joe Bloggs', 3);
-- Output: Joe
SELECT RIGHT('Joe Bloggs', 6);
-- Output: Bloggs
-- PostgreSQL and MySQL: the standard SQL form
SELECT SUBSTRING('Joe Bloggs' FROM 1 FOR 3);
-- Output: Joe
특정 문자 다음부터 시작하려면 먼저 그 문자의 위치를 찾으세요:
SELECT INSTR('joe@example.com', '@');
-- Output: 4
-- PostgreSQL: POSITION('@' IN 'joe@example.com'); SQL Server: CHARINDEX('@', 'joe@example.com')
SUBSTRING은 언제 써야 할까요?
필요한 부분이 문자열의 정해진 위치에 있거나, 찾을 수 있는 특정 문자 바로 뒤에 있을 때 SUBSTRING을 쓰세요. 대부분은 다음 네 가지 경우 중 하나예요.
앞쪽 몇 글자 가져오기
코드나 이름은 앞부분에 의미가 담긴 경우가 많아요. 상품 코드의 카테고리, 이니셜, 긴 설명의 짧은 미리보기 같은 것들이죠. SUBSTRING(column, 1, n)은 앞의 n글자를 가져와요.
고정 형식 값에서 필드 추출하기
모든 값의 형식이 같다면 각 부분은 모든 행에서 같은 위치에 있어요. (303) 555-0147 같은 전화번호의 지역 번호, ORD-2024-001의 연도, 오래된 시스템의 고정 폭 레코드 필드 등이 그래요.
구분자 뒤의 텍스트 추출하기
이메일 도메인, 파일 확장자, URL 경로는 행마다 위치가 달라지는 문자 뒤에서 시작해요. INSTR로 그 위치를 찾고, 한 글자 뒤에서 시작해 나머지 문자열을 가져오세요.
마지막 몇 글자 남기기
카드 번호나 계좌 번호를 마스킹하거나 접미사를 읽으려면 끝에서부터 거꾸로 세야 해요. 문자열 길이로 시작 위치를 계산하거나, MySQL과 SQLite에서는 음수 시작 위치를 쓰세요.
SUBSTRING 실전 예제
코드 속 카테고리별로 상품 수 세기
상품 코드는 세 글자 카테고리로 시작해요. 이 접두사로 그룹화해서 카테고리별 상품 수를 세어 보세요:
-- Input: products
-- product_code | name
-- -------------+-----------
-- ELE-1234 | USB-C Hub
-- FUR-0087 | Desk Chair
-- ELE-5521 | Webcam
-- OFF-2210 | Stapler
SELECT SUBSTRING(product_code, 1, 3) AS category, COUNT(*) AS products
FROM products
GROUP BY SUBSTRING(product_code, 1, 3)
ORDER BY category;
-- Output:
-- category | products
-- ---------+---------
-- ELE | 2
-- FUR | 1
-- OFF | 1
고정 폭 레코드 파싱하기
오래된 시스템에서 내보낸 데이터는 구분자 없이 필드를 고정 위치에 담는 경우가 많아요. 여기서는 이름이 8글자(공백으로 채워져 있으며 TRIM으로 제거해요), 생년월일이 다음 8글자, 도시 코드가 마지막 3글자를 차지해요:
-- Input: legacy_data
-- record
-- -------------------
-- JOHNDOE 19850315NYC
-- MARYLEE 19780912CHI
-- JANEROE 19900722LAX
SELECT
TRIM(SUBSTRING(record, 1, 8)) AS name,
SUBSTRING(record, 9, 8) AS birth_date,
SUBSTRING(record, 17, 3) AS city_code
FROM legacy_data;
-- Output:
-- name | birth_date | city_code
-- --------+------------+----------
-- JOHNDOE | 19850315 | NYC
-- MARYLEE | 19780912 | CHI
-- JANEROE | 19900722 | LAX
파일별 확장자 가져오기
점을 찾은 다음, 그 뒤의 모든 글자를 가져오세요. WHERE 절은 점이 없는 파일을 건너뛰어요:
-- Input: documents
-- filename
-- ---------------
-- q3-report.pdf
-- budget.xlsx
-- README
-- team-photo.jpeg
SELECT
filename,
SUBSTRING(filename, INSTR(filename, '.') + 1) AS extension
FROM documents
WHERE INSTR(filename, '.') > 0;
-- Output:
-- filename | extension
-- ----------------+----------
-- q3-report.pdf | pdf
-- budget.xlsx | xlsx
-- team-photo.jpeg | jpeg
PostgreSQL에서는 INSTR 대신 POSITION('.' IN filename)을 쓰세요. SQL Server에서는 CHARINDEX('.', filename)을 쓰고 LEN(filename) 같은 길이를 지정하세요.
카드 번호 마스킹하기
카드 번호가 모두 16자리는 아니어서, 시작 위치를 고정하면 American Express 번호가 잘려요. 대신 끝에서부터 거꾸로 세세요:
-- Input: payments
-- customer_name | card_number
-- --------------+-----------------
-- Lena Fischer | 4111111111111111
-- Marcus Reid | 5500005555555559
-- Aiko Tanaka | 378282246310005
-- Wrong: position 13 only works for 16-digit cards
SELECT customer_name, '**** ' || SUBSTRING(card_number, 13, 4) AS masked_card
FROM payments;
-- Output:
-- customer_name | masked_card
-- --------------+------------
-- Lena Fischer | **** 1111
-- Marcus Reid | **** 5559
-- Aiko Tanaka | **** 005
-- Right: start 4 characters from the end
SELECT customer_name, '**** ' || SUBSTRING(card_number, LENGTH(card_number) - 3, 4) AS masked_card
FROM payments;
-- Output:
-- customer_name | masked_card
-- --------------+------------
-- Lena Fischer | **** 1111
-- Marcus Reid | **** 5559
-- Aiko Tanaka | **** 0005
MySQL과 SQL Server에서는 || 대신 CONCAT()으로 문자열을 이어 붙이세요. SQL Server는 LENGTH() 대신 LEN()도 써야 해요.
SUBSTRING 더 알아보기
이 블로그의 글:
- SQL LIKE 가이드: 패턴으로 시작하거나 끝나거나 패턴을 포함하는 텍스트 찾기
- SQL COALESCE 가이드: SUBSTRING이 그대로 반환하는 NULL 대체하기
- SQL GROUP BY 가이드: 추출한 값으로 행을 그룹화하고 세기
- SQL 날짜 형식 가이드: 문자열을 자르지 않고 날짜를 포맷하고 날짜 요소 추출하기
공식 문서:
Beekeeper Studio는 무료 오픈 소스 데이터베이스 GUI예요
지금까지 써 본 SQL 쿼리 & 에디터 도구 중 최고예요. 데이터베이스 관리에 필요한 모든 게 다 있어요. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio는 빠르고 직관적이며 사용하기 쉬워요. 다양한 데이터베이스를 지원하고 Windows, Mac, Linux에서 모두 잘 작동해요.
Beekeeper Studio 사용자 후기
"Beekeeper Studio가 기존 SQL 작업 방식을 완전히 바꿔 놓았어요. 빠르고 직관적이라 데이터베이스 작업이 다시 즐거워졌어요."
"여러 데이터베이스 GUI를 써 봤지만, Beekeeper는 기능과 단순함 사이의 균형이 완벽해요. 그냥 잘 돌아가요."