날짜, 문자, 빈 값을 다루는 실무 SQL
실무 데이터는 항상 조금 비어 있다
업무 데이터는 교과서처럼 깨끗하지 않습니다. 주문일에는 시간이 붙어 있고, 이메일은 회사 메일과 개인 메일이 섞여 있습니다. 상품명에는 같은 상품이라도 "노트북", "Notebook", "노트북 15인치"처럼 여러 표현이 들어갑니다. 고객 전화번호가 비어 있거나, 배송 메모가 없는 경우도 흔합니다.
이 장의 목표는 데이터를 완벽하게 만드는 것이 아닙니다. 이미 들어와 있는 데이터를 보고서에 쓸 수 있는 모양으로 정리해서 조회하는 법을 익히는 것입니다.
이번 장에서는 다음 네 가지를 다룹니다.
날짜에서 원하는 기간만 찾기
문자 안에 특정 단어가 들어 있는 행 찾기
NULL을 보기 좋은 표시값으로 바꾸기CASE로 업무용 분류 컬럼 만들기
예시는 다음과 같은 업무 테이블을 기준으로 생각하겠습니다.
orders: 주문 내역customers: 고객 명단products: 상품 재고
AI 생성 이미지
날짜는 "같은 날"도 시간 때문에 달라질 수 있다
엑셀에서 날짜 칸을 보면 2026-06-01처럼 단순해 보입니다. 하지만 데이터베이스에는 날짜와 시간이 함께 저장되는 경우가 많습니다.
예를 들어 주문일이 이렇게 들어 있을 수 있습니다.
2026-06-01 09:13:22
2026-06-01 18:45:10
2026-06-02 00:05:01사람 눈에는 첫 두 줄이 모두 6월 1일 주문입니다. 하지만 데이터베이스 입장에서는 시간까지 포함된 서로 다른 값입니다. 그래서 날짜를 조회할 때는 "하루의 시작부터 다음 날 시작 전까지"로 생각하면 안전합니다.
하려는 일: 2026년 6월 1일에 들어온 주문만 조회한다.
SELECT
order_id,
order_date,
total_amount
FROM orders
WHERE order_date >= TIMESTAMP '2026-06-01 00:00:00'
AND order_date < TIMESTAMP '2026-06-02 00:00:00'
ORDER BY order_date;결과 해석: 2026-06-01 하루 동안 들어온 주문만 조회됩니다. order_date에 시간이 들어 있어도 6월 1일 0시부터 6월 2일 0시 전까지의 주문이 포함됩니다.
초보자가 자주 쓰는 방식은 다음과 같습니다.
WHERE order_date = DATE '2026-06-01'이 방식은 order_date가 순수한 날짜 타입이면 괜찮습니다. 하지만 시간이 붙은 timestamp라면 기대한 결과가 나오지 않을 수 있습니다. 실무에서는 주문일, 결제일, 가입일에 시간이 함께 저장되는 일이 많으므로 기간 조건을 넓게 잡는 습관이 좋습니다.
한 달, 한 해, 최근 기간 조회하기
업무에서는 "이번 달 주문", "작년 가입 고객", "최근 30일 주문" 같은 질문이 자주 나옵니다. 이때도 시작일과 종료일을 정해 조회하면 읽기 쉽고 실수가 적습니다.
하려는 일: 2026년 6월 주문만 조회한다.
SELECT
order_id,
customer_id,
order_date,
total_amount
FROM orders
WHERE order_date >= DATE '2026-06-01'
AND order_date < DATE '2026-07-01'
ORDER BY order_date;결과 해석: 2026년 6월 1일부터 6월 30일 마지막 시간까지의 주문이 조회됩니다. 종료 조건을 2026-07-01보다 작게 잡았기 때문에 6월 전체를 안전하게 포함합니다.
하려는 일: 2026년에 들어온 주문만 조회한다.
SELECT
order_id,
order_date,
total_amount
FROM orders
WHERE order_date >= DATE '2026-01-01'
AND order_date < DATE '2027-01-01'
ORDER BY order_date;결과 해석: 2026년 1월 1일부터 2027년 1월 1일 전까지의 주문이 조회됩니다. 즉 2026년 전체 주문입니다.
최근 기간은 CURRENT_DATE를 사용하면 편합니다. CURRENT_DATE는 PostgreSQL이 알고 있는 "오늘 날짜"입니다.
하려는 일: 오늘을 기준으로 최근 30일 주문을 조회한다.
SELECT
order_id,
order_date,
total_amount
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
ORDER BY order_date DESC;결과 해석: 오늘부터 30일 전 이후에 들어온 주문이 최신순으로 조회됩니다. 매일 SQL을 다시 실행해도 기준일이 자동으로 오늘로 바뀝니다.
날짜에서 연도나 월만 뽑아 보고 싶을 때는 EXTRACT를 사용할 수 있습니다. EXTRACT는 날짜에서 특정 부분만 꺼내는 도구입니다.
하려는 일: 주문일에서 연도와 월을 따로 표시한다.
SELECT
order_id,
order_date,
EXTRACT(YEAR FROM order_date) AS order_year,
EXTRACT(MONTH FROM order_date) AS order_month,
total_amount
FROM orders
ORDER BY order_date;결과 해석: 주문일 옆에 주문 연도와 주문 월이 별도 컬럼으로 표시됩니다. 보고서에서 "몇 년 몇 월 주문인지" 확인할 때 유용합니다.
다만 EXTRACT(MONTH FROM order_date) = 6처럼 월만 조건으로 쓰면 모든 연도의 6월이 함께 조회됩니다. "2026년 6월"처럼 특정 연도와 월을 찾을 때는 앞에서 본 기간 조건이 더 안전합니다.
문자 검색은 LIKE로 시작한다
문자 데이터도 깔끔하지 않습니다. 이메일 주소, 상품명, 상담 메모, 배송 요청사항처럼 사람이 직접 입력한 값은 특히 다양합니다.
이때 LIKE를 사용하면 문자 안에 특정 패턴이 들어 있는 행을 찾을 수 있습니다.
LIKE에서 가장 자주 쓰는 기호는 %입니다.
'ABC%': ABC로 시작하는 값'%ABC': ABC로 끝나는 값'%ABC%': 중간 어딘가에 ABC가 들어 있는 값
하려는 일: 네이버 이메일을 사용하는 고객을 찾는다.
SELECT
customer_id,
customer_name,
email
FROM customers
WHERE email LIKE '%@naver.com'
ORDER BY customer_name;결과 해석: 이메일이 @naver.com으로 끝나는 고객만 조회됩니다. 고객 메일 발송 대상을 도메인별로 확인할 때 사용할 수 있습니다.
하려는 일: 상품명에 '노트북'이 들어간 상품을 찾는다.
SELECT
product_id,
product_name,
price,
stock_qty
FROM products
WHERE product_name LIKE '%노트북%'
ORDER BY product_name;결과 해석: 상품명 앞, 중간, 뒤 어디든 노트북이라는 글자가 포함된 상품이 조회됩니다. "삼성 노트북", "노트북 파우치", "15인치 노트북"이 모두 포함될 수 있습니다.
하려는 일: 배송 메모에 '부재'라는 단어가 들어간 주문을 찾는다.
SELECT
order_id,
order_date,
delivery_memo
FROM orders
WHERE delivery_memo LIKE '%부재%'
ORDER BY order_date DESC;결과 해석: 배송 메모에 "부재 시 문 앞", "고객 부재" 같은 문구가 들어간 주문이 조회됩니다.
PostgreSQL에는 ILIKE도 있습니다. ILIKE는 대문자와 소문자를 구분하지 않고 찾습니다. 영어 상품명이나 이메일을 검색할 때 편합니다.
하려는 일: 상품명에 notebook, Notebook, NOTEBOOK처럼 대소문자와 관계없이 notebook이 들어간 상품을 찾는다.
SELECT
product_id,
product_name,
price
FROM products
WHERE product_name ILIKE '%notebook%'
ORDER BY product_name;결과 해석: notebook의 대문자와 소문자가 섞여 있어도 모두 조회됩니다.
NULL은 0도 아니고 빈 문자열도 아니다
NULL은 "값이 없음"을 뜻합니다. 숫자 0과 다르고, 빈 문자 ''와도 다릅니다.
예를 들어 고객 전화번호가 NULL이면 전화번호가 아직 입력되지 않았다는 뜻입니다. 할인금액이 NULL이면 할인 정보가 없다는 뜻일 수 있습니다. 재고 수량이 0이면 재고가 없다는 뜻이지만, 재고 수량이 NULL이면 아직 수량을 모른다는 뜻일 수 있습니다.
보고서에서는 NULL이 그대로 나오면 읽는 사람이 헷갈릴 수 있습니다. 이때 COALESCE를 사용합니다.
COALESCE는 왼쪽부터 값을 확인해서, 처음으로 NULL이 아닌 값을 보여 줍니다.
하려는 일: 전화번호가 비어 있는 고객은 '미입력'으로 표시한다.
SELECT
customer_id,
customer_name,
COALESCE(phone, '미입력') AS phone_display
FROM customers
ORDER BY customer_name;결과 해석: phone 값이 있으면 실제 전화번호가 표시되고, NULL이면 미입력이라고 표시됩니다. 원본 테이블의 값이 바뀌는 것은 아닙니다. 조회 결과에서만 보기 좋게 바꾸는 것입니다.
숫자에도 사용할 수 있습니다.
하려는 일: 할인금액이 비어 있으면 0원으로 표시해 주문금액과 함께 본다.
SELECT
order_id,
total_amount,
COALESCE(discount_amount, 0) AS discount_amount_display
FROM orders
ORDER BY order_date DESC;결과 해석: 할인금액이 입력된 주문은 그 금액이 표시되고, 할인금액이 NULL인 주문은 0으로 표시됩니다. 보고서에서 빈칸 대신 0을 보여 주고 싶을 때 유용합니다.
날짜도 표시용으로 바꿀 수 있습니다. 날짜와 문자를 섞어 보여 주려면 날짜를 문자로 바꿔야 합니다. 초보자는 아래처럼 TO_CHAR를 사용한다고 이해하면 충분합니다.
하려는 일: 마지막 주문일이 없는 고객은 '주문 없음'으로 표시한다.
SELECT
customer_id,
customer_name,
COALESCE(TO_CHAR(last_order_date, 'YYYY-MM-DD'), '주문 없음') AS last_order_display
FROM customers
ORDER BY customer_name;결과 해석: 마지막 주문일이 있으면 2026-06-15 같은 형식으로 표시되고, 값이 없으면 주문 없음으로 표시됩니다.
중요한 점은 COALESCE가 원본 데이터를 고치는 명령이 아니라는 것입니다. 화면에 보이는 조회 결과를 보기 좋게 바꾸는 도구입니다.
CASE는 업무 기준을 SQL 안에 적는 방법이다
CASE는 조건에 따라 다른 값을 보여 주는 문법입니다. 엑셀의 IF 함수와 비슷하게 생각하면 됩니다.
예를 들어 주문금액이 30만 원 이상이면 "고액 주문", 10만 원 이상이면 "일반 주문", 그보다 작으면 "소액 주문"이라고 표시할 수 있습니다.
하려는 일: 주문금액에 따라 주문 등급을 표시한다.
SELECT
order_id,
customer_id,
total_amount,
CASE
WHEN total_amount >= 300000 THEN '고액 주문'
WHEN total_amount >= 100000 THEN '일반 주문'
ELSE '소액 주문'
END AS amount_grade
FROM orders
ORDER BY total_amount DESC;결과 해석: 주문금액 기준으로 새 컬럼 amount_grade가 만들어집니다. 이 컬럼은 원래 테이블에 있던 컬럼이 아니라 조회할 때 만든 분류 결과입니다.
CASE는 위에서부터 조건을 확인합니다. 그래서 순서가 중요합니다. 위 예시에서 100000 조건을 먼저 쓰면 30만 원 주문도 이미 "일반 주문"으로 분류될 수 있습니다. 큰 구간부터 확인해야 합니다.
고객 상태도 만들 수 있습니다.
하려는 일: 마지막 주문일을 기준으로 고객 상태를 분류한다.
SELECT
customer_id,
customer_name,
last_order_date,
CASE
WHEN last_order_date IS NULL THEN '주문 없음'
WHEN last_order_date >= CURRENT_DATE - INTERVAL '90 days' THEN '최근 구매 고객'
ELSE '휴면 가능 고객'
END AS customer_status
FROM customers
ORDER BY customer_name;결과 해석: 마지막 주문일이 없으면 "주문 없음", 최근 90일 안에 주문했으면 "최근 구매 고객", 그 외에는 "휴면 가능 고객"으로 표시됩니다.
여기서 NULL 조건을 먼저 확인한 점을 눈여겨봐야 합니다. NULL은 날짜 비교가 정상적인 참·거짓으로 나오지 않기 때문에, 비어 있는 경우를 먼저 처리하는 습관이 좋습니다.
상품 재고에도 적용할 수 있습니다.
하려는 일: 재고 수량을 기준으로 재고 위험도를 표시한다.
SELECT
product_id,
product_name,
stock_qty,
safety_stock_qty,
CASE
WHEN stock_qty IS NULL THEN '재고 미확인'
WHEN stock_qty = 0 THEN '품절'
WHEN stock_qty <= safety_stock_qty THEN '재고 부족'
ELSE '정상'
END AS stock_status
FROM products
ORDER BY product_name;결과 해석: 상품마다 재고 상태가 "재고 미확인", "품절", "재고 부족", "정상"으로 표시됩니다. 담당자는 이 결과를 보고 어떤 상품부터 확인해야 하는지 빠르게 판단할 수 있습니다.
날짜, 문자, NULL, CASE를 함께 써 보기
실무 SQL은 한 가지 기능만 쓰는 경우보다 여러 기능을 섞어 쓰는 경우가 많습니다. 하지만 겁낼 필요는 없습니다. 큰 문장을 작은 질문으로 나누면 됩니다.
하려는 일: 2026년 6월 주문 중 배송 메모가 비어 있으면 '메모 없음'으로 표시하고, 주문금액에 따라 등급을 붙인다.
SELECT
order_id,
order_date,
total_amount,
COALESCE(delivery_memo, '메모 없음') AS delivery_memo_display,
CASE
WHEN total_amount >= 300000 THEN '고액 주문'
WHEN total_amount >= 100000 THEN '일반 주문'
ELSE '소액 주문'
END AS amount_grade
FROM orders
WHERE order_date >= DATE '2026-06-01'
AND order_date < DATE '2026-07-01'
ORDER BY order_date DESC;결과 해석: 2026년 6월 주문만 조회됩니다. 배송 메모가 없는 주문은 메모 없음으로 표시되고, 주문금액에 따라 등급이 붙습니다. 원본 주문 데이터는 바뀌지 않습니다.
하려는 일: 상품명에 '노트북'이 들어간 상품만 보고, 재고 상태를 함께 표시한다.
SELECT
product_id,
product_name,
price,
stock_qty,
CASE
WHEN stock_qty IS NULL THEN '재고 미확인'
WHEN stock_qty = 0 THEN '품절'
WHEN stock_qty <= 5 THEN '재고 부족'
ELSE '정상'
END AS stock_status
FROM products
WHERE product_name LIKE '%노트북%'
ORDER BY stock_qty NULLS FIRST;결과 해석: 상품명에 노트북이 들어간 상품만 조회되고, 재고 상태가 함께 표시됩니다. NULLS FIRST는 재고 수량이 비어 있는 상품을 먼저 보여 달라는 뜻입니다. 재고 확인이 필요한 상품을 앞에서 볼 수 있습니다.
실무에서 기억할 기준
날짜, 문자, NULL, CASE는 화려한 기능이 아닙니다. 하지만 보고서를 만들 때 가장 자주 부딪히는 문제를 해결합니다.
실무에서는 다음 기준을 기억하면 됩니다.
날짜와 시간이 함께 저장될 수 있으므로 하루나 한 달은
>= 시작일과< 다음 시작일로 조회한다.문자 일부를 찾을 때는
LIKE '%검색어%'를 사용한다.영어 대소문자를 구분하지 않고 찾고 싶으면 PostgreSQL의
ILIKE를 사용한다.NULL은 0이나 빈 문자열이 아니라 "값이 없음"이다.보고서 표시용 빈값 처리는
COALESCE로 해결한다.업무 기준에 따른 분류는
CASE WHEN ... THEN ... ELSE ... END로 만든다.CASE에서는 더 구체적이거나 더 강한 조건을 위에 둔다.
데이터는 처음부터 깨끗하지 않습니다. 중요한 것은 지저분한 데이터를 보고 포기하지 않는 것입니다. SQL은 큰 장부에서 필요한 행을 찾고, 빈칸을 보기 좋게 바꾸고, 업무 기준에 맞는 이름표를 붙이는 도구입니다. 이 장의 기능을 익히면 원본 데이터와 보고서 사이의 거리를 크게 줄일 수 있습니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.