4부 · 묻기의 기본 (2) 필터링 심화
← SELECT 기초 · 목차 · 다음: 5부 JOIN →
WHERE를 더 깊게 팝니다. "제목에 '바다'가 든 책", "가격이 9000·11000·15000 중 하나인 책", "저자가 비어 있는 책" 같은 현실적인 조건들을 표현하는 도구(LIKE·IN·BETWEEN·IS NULL)와, 결과를 분류하는 CASE, 중복을 없애는 DISTINCT를 배웁니다. 끝에서 SQL이 실제로 실행되는 순서라는, 초보의 흔한 오류를 설명해 주는 핵심 비밀을 다룹니다.
flowchart LR
E["거르기 심화<br/>LIKE · IN · CASE · DISTINCT"]
classDef here fill:#d5f2e0,stroke:#27ae60,color:#000,stroke-width:3px
class E here
예제는 공용 데이터를 씁니다. 출력은 실제 실행 결과입니다.
4.7 LIKE: 패턴으로 검색
정확히 일치가 아니라 "~를 포함", "~로 시작" 같은 부분 일치엔 LIKE를 씁니다. 두 가지 와일드카드(자리 표시 기호)를 씁니다.
%— 아무 글자가 0개 이상_— 아무 글자 정확히 1개
-- 저자 이름에 '헤'가 들어가는 책 SELECT title, author FROM books WHERE author LIKE '%헤%';
출력:
title author ---------- ---------- 데미안 헤르만 헤세 노인과 바다 어니스트 헤밍웨이
| 패턴 | 의미 | 매칭 예 |
|---|---|---|
'김%' | '김'으로 시작 | 김하나, 김철수 |
'%@mail.com' | '@mail.com'으로 끝 | hana@mail.com |
'%바다%' | '바다'를 포함 | 노인과 바다 |
'_984' | 한 글자 + '984' | 1984 |
대소문자와 한글. SQLite의LIKE는 ASCII 영문에선 기본적으로 대소문자를 구분하지 않습니다('a%'가 'Apple'에 매칭). 다만 한글이나 그 외 유니코드엔 이 규칙이 적용되지 않습니다. 정밀한 텍스트 검색이 필요하면LIKE만으론 한계가 있어, 본격적으로는 전문 검색(full-text search) 기능을 따로 씁니다(입문 범위 밖).
4.8 IN · BETWEEN: 목록과 범위
IN — "이 목록 중 하나"
여러 값 중 하나와 일치하는지 볼 때, OR를 길게 쓰는 대신 IN을 씁니다.
-- price = 9000 OR price = 11000 OR price = 15000 과 같은 의미 SELECT title, price FROM books WHERE price IN (9000, 11000, 15000);
출력:
title price ---------- ----- 모비딕 15000 1984 11000 노인과 바다 9000
NOT IN은 반대로 "목록에 없는 것"입니다.
BETWEEN — "이 범위 안" (양 끝 포함)
-- price >= 10000 AND price <= 12000 과 같은 의미 (양 끝 포함!) SELECT title, price FROM books WHERE price BETWEEN 10000 AND 12000 ORDER BY price;
출력:
title price ---------- ----- 이방인 10000 1984 11000 데미안 12000
BETWEEN a AND b는 a와 b를 포함합니다. 날짜에도 잘 어울립니다: WHERE order_date BETWEEN '2026-01-01' AND '2026-01-31'은 1월 주문 전체.
4.9 NULL 다시 다루기
2부 2.5에서 배웠듯, 빈 값 비교는 =이 아니라 IS NULL / IS NOT NULL입니다.
SELECT title, author FROM books WHERE author IS NULL;
출력:
title author ---------- ------ 고서 필사본
(저자 칸이 비어 있게 나옵니다.) 한 가지 더, NULL일 때 대신 보여 줄 값을 주는 COALESCE가 유용합니다.
-- 저자가 없으면 '저자 미상'으로 표시 SELECT title, COALESCE(author, '저자 미상') AS author FROM books;
COALESCE(a, b)는 "a가 NULL이 아니면 a, NULL이면 b"를 돌려줍니다. 비어 있는 칸을 보기 좋게 채울 때 자주 씁니다.
4.10 CASE: 조건에 따라 다른 값
행마다 조건을 따져 다른 결과를 내고 싶을 때 CASE를 씁니다. 프로그래밍의 if/else에 해당합니다. 가격대를 등급으로 분류해 봅니다.
SELECT title, price, CASE WHEN price >= 15000 THEN '고가' WHEN price >= 10000 THEN '중가' ELSE '저가' END AS tier FROM books ORDER BY price DESC;
출력:
title price tier ---------- ----- ---- 고서 필사본 50000 고가 모비딕 15000 고가 데미안 12000 중가 1984 11000 중가 이방인 10000 중가 노인과 바다 9000 저가
WHEN 조건 THEN 값을 위에서부터 따져, 처음 맞는 것에서 멈춥니다. 어느 것도 안 맞으면 ELSE 값. END로 닫고 AS로 별칭을 붙입니다. 데이터를 분류하거나 라벨을 붙일 때 두루 쓰입니다.
4.11 DISTINCT: 중복 제거
같은 값이 여러 번 나올 때 고유한 것만 보고 싶다면 DISTINCT.
-- 주문한 적 있는 고객 번호(중복 없이) SELECT DISTINCT customer_id FROM orders ORDER BY customer_id;
출력:
customer_id ----------- 1 2 3 4
주문은 8건이지만 고객 번호는 중복을 빼면 4개입니다. "주문 기록이 있는 고객은 몇 명?", "어떤 책들이 한 번이라도 팔렸나?" 같은 질문에 쓰입니다.
4.12 핵심 비밀: SQL은 적은 순서대로 실행되지 않는다
초보가 가장 자주 부딪치는 오류가 여기서 풀립니다. SQL을 쓰는 순서와 데이터베이스가 실행하는 순서가 다릅니다.
우리가 쓰는 순서: SELECT → FROM → WHERE → ORDER BY.
하지만 실제 실행 순서는 이렇습니다:
flowchart LR
F["① FROM<br/>테이블에서 행을 가져온다"]
W["② WHERE<br/>조건으로 행을 거른다"]
G["③ GROUP BY<br/>(있다면) 묶는다"]
H["④ HAVING<br/>(있다면) 묶음을 거른다"]
S["⑤ SELECT<br/>보여줄 칸·별칭을 만든다"]
O["⑥ ORDER BY<br/>정렬한다"]
L["⑦ LIMIT<br/>개수를 자른다"]
F --> W --> G --> H --> S --> O --> L
classDef step fill:#d5f2e0,stroke:#27ae60,color:#000
class F,W,G,H,S,O,L step
핵심은 WHERE가 SELECT보다 먼저 실행된다는 점입니다. 그래서 표준 SQL에서는 SELECT에서 만든 별칭을 WHERE에서 쓸 수 없습니다 — WHERE가 돌 때는 그 별칭이 아직 존재하지 않으니까요.
-- 표준 SQL(PostgreSQL·MySQL 등)에서는 오류: WHERE는 별칭 sale_price를 아직 모른다 SELECT title, price * 0.9 AS sale_price FROM books WHERE sale_price < 11000; -- 어디서나 안전한 방법: WHERE에는 원래 식을 그대로 쓴다 SELECT title, price * 0.9 AS sale_price FROM books WHERE price * 0.9 < 11000;
SQLite의 관대함, 그리고 함정. 사실 위 첫 번째 쿼리는 SQLite에서는 그냥 동작합니다. SQLite가 표준을 느슨하게 확장해WHERE에서도 별칭을 받아 주기 때문입니다. 편해 보이지만 함정입니다 — 같은 쿼리를 PostgreSQL이나 MySQL로 옮기면 오류가 납니다. 그래서 별칭에 의존하지 말고WHERE엔 원래 식을 쓰는 습관을 들이는 게 좋습니다. "SQLite에서 됐는데 실무 DB에서 안 된다"는 당황을 미리 피하는 길입니다. 실행 순서의 원칙(WHERE가SELECT보다 먼저)을 이해하는 것은, 이런 DB별 차이를 넘어 여전히 중요합니다.
반대로 ORDER BY는 SELECT 다음에 실행되므로, 별칭으로 정렬하는 것은 어디서나 됩니다.
-- ✅ ORDER BY는 SELECT 이후라 별칭 사용 가능 SELECT title, price * 0.9 AS sale_price FROM books ORDER BY sale_price;
이 실행 순서 하나를 머릿속에 그려 두면, "왜 이 별칭이 여기선 되고 저기선 안 되지?", "왜 WHERE에선 집계 함수를 못 쓰지?"(→ HAVING을 써야 함, 5부) 같은 의문이 한꺼번에 풀립니다.
4.13 정리
LIKE로 패턴 검색(%=0글자 이상,_=1글자). 한글 대소문자·정밀 검색엔 한계가 있다.IN(목록 중 하나),BETWEEN(범위, 양 끝 포함)으로 조건을 간결하게.- 빈 값은
IS NULL/IS NOT NULL, 대체값은COALESCE. CASE로 행마다 조건 분기(if/else),DISTINCT로 중복 제거.- SQL의 실행 순서는
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT. 그래서WHERE에선 별칭을 못 쓰고,ORDER BY에선 쓸 수 있다. 이 순서가 많은 오류를 설명한다.
이제 한 테이블에 대한 질문은 자유자재입니다. 하지만 가장 흥미로운 질문들("누가 무엇을 샀나", "어느 책이 가장 많이 팔렸나")은 여러 테이블에 걸쳐 있습니다. 다음 부에서 테이블을 잇는 JOIN과, 데이터를 요약하는 GROUP BY로 넘어갑니다 — SQL의 진짜 힘이 드러나는 곳입니다.
직접 해 보기
- 제목에 '바다'가 들어간 책을
LIKE로 찾아보세요. - 가격이 9000·10000·12000원 중 하나인 책을
IN으로 찾아보세요. - 모든 책을
CASE로 "재고 있음(stock > 0)" / "품절(stock = 0)"로 분류해 제목과 함께 보여 주세요. orders에서 주문된 적 있는 책 번호(book_id)를DISTINCT로 중복 없이 뽑아 보세요.- (실행 순서 확인)
SELECT price * 2 AS double_price FROM books WHERE double_price > 20000;는 SQLite에서는 동작하지만, PostgreSQL·MySQL에서는 오류가 납니다. 왜일까요? 어느 DB로 옮겨도 안전하려면WHERE를 어떻게 고쳐야 할까요?
← SELECT 기초 · 목차 · 다음: 5부 JOIN →