SQL 2회차 : SELECT 기본 문법과 필터링 (SELECT/FROM/WHERE, LIKE·BETWEEN·IN, NULL 체크포인트)


SQL 2회차 : SELECT 기본 문법과 필터링 (SELECT/FROM/WHERE, LIKE·BETWEEN·IN, NULL 체크포인트)

한눈에 보는 요약

SELECT는 “어떤 컬럼을 보여줄지”, FROM은 “어떤 테이블에서 가져올지”, WHERE는 “어떤 조건으로 걸러낼지”를 결정합니다. 이 3가지를 정확히 이해하면 조회 쿼리의 80%는 해결됩니다.

필터링은 비교 연산(=, <>, <, >, <=, >=)과 논리 연산(AND/OR/NOT) 조합으로 완성됩니다. LIKE·BETWEEN·IN은 자주 쓰는 “조건 표현식의 단축키”로 생각하시면 학습 속도가 빨라집니다.

초보자에게 가장 흔한 함정은 NULL입니다. NULL은 “값이 없음(미정)”이기 때문에 =, <> 같은 비교로는 제대로 걸러지지 않는 경우가 많고, 이 때문에 “조건이 안 먹는 것처럼 보이는” 상황이 자주 발생합니다.

목차


핵심 포인트

  • SELECT는 “출력 컬럼”, FROM은 “대상 테이블”, WHERE는 “행 필터링”을 담당합니다.
  • 필터링의 기본은 비교 연산 + 논리 연산이며, AND/OR 혼합 시 괄호로 의도를 고정하는 습관이 중요합니다.
  • LIKE·BETWEEN·IN은 조건을 짧고 읽기 쉽게 만들지만, 날짜/NULL 같은 예외 상황에서는 동작을 한 번 더 점검해야 합니다.
  • NULL은 “값이 없다”가 아니라 “미정”에 가깝습니다. 비교 결과가 TRUE/FALSE가 아니라 UNKNOWN이 될 수 있고, WHERE는 TRUE만 통과시킵니다.
  • NULL 처리는 IS NULL / IS NOT NULL을 기본으로 하고, 필요 시 COALESCE 등으로 대체값을 지정해 안정적으로 필터링합니다.

상세 설명

1. SELECT/FROM/WHERE 기본 뼈대

조회 쿼리는 아래 뼈대를 기준으로 생각하시면 됩니다. 초보 단계에서는 “한 줄씩 붙여서 쿼리를 완성한다”는 느낌으로 접근하는 것이 안전합니다.

SELECT
  column1, column2
FROM table_name
WHERE condition;

SELECT에는 출력할 컬럼 목록을 적고, FROM에는 데이터를 가져올 테이블을 적습니다. 마지막 WHERE는 “조건을 만족하는 행만 남기기” 위한 필터링 구문입니다. WHERE가 없으면 조건 없이 전체 행이 조회됩니다.

처음 연습할 때는 다음 순서가 좋습니다.

  • 1) FROM으로 “어디서” 가져올지 정한다
  • 2) SELECT로 “무엇을” 보여줄지 정한다
  • 3) WHERE로 “어떤 행만” 남길지 정한다

2. 컬럼 선택과 별칭(AS)

컬럼은 필요한 것만 선택하는 것이 기본입니다. SELECT *는 학습용으로는 편하지만, 실무에서는 불필요한 컬럼까지 가져와 성능/가독성을 해치기 쉽습니다.

SELECT
  order_id,
  user_id,
  total_amount AS amount,
  order_date AS ordered_at
FROM orders;

AS는 “결과 컬럼 이름”을 읽기 좋게 바꿔주는 기능입니다. 특히 조인(JOIN)을 하거나 집계(집합 함수)를 사용할 때 별칭은 결과 해석 속도를 크게 올려줍니다.

테이블 별칭도 자주 쓰입니다. 긴 테이블명을 매번 쓰는 대신 짧게 줄여서 가독성을 높입니다.

SELECT
  o.order_id,
  o.user_id,
  o.total_amount
FROM orders AS o;

위 예시에서 orders를 o로 줄였고, 컬럼 앞에 o.를 붙여 “어느 테이블 컬럼인지”를 명확히 했습니다. 조인을 배우기 전이라도 이 습관을 들여두면 이후 단계에서 훨씬 편해집니다.

3. 비교/논리 연산으로 필터링 만들기

WHERE의 본질은 “TRUE인 행만 남긴다”입니다. 따라서 조건식이 무엇을 의미하는지, 그리고 AND/OR로 결합하면 어떤 의미가 되는지 문장으로 해석해보는 습관이 중요합니다.

자주 쓰는 연산자 요약 표

아래 표는 초보자가 반드시 기억해야 하는 필터링 연산자와 대표 사용 상황을 정리한 것입니다.

종류 연산자/구문 의미 예시 주의사항
비교 = 같다 status = 'PAID' NULL과 비교는 TRUE가 되지 않습니다
비교 <> 같지 않다 status <> 'CANCEL' NULL은 “같지 않다”로 잡히지 않을 수 있습니다
비교 >, <, >=, <= 대소 비교 total_amount >= 10000 문자/날짜 타입 비교 규칙은 DB마다 다를 수 있습니다
논리 AND 모두 만족 A AND B 조건이 많아질수록 괄호로 의도 고정
논리 OR 하나라도 만족 A OR B AND/OR 혼합 시 우선순위 실수 주의
논리 NOT 부정 NOT (A) NULL과 결합되면 결과가 예상과 달라질 수 있습니다
NULL IS NULL / IS NOT NULL NULL 여부 coupon_id IS NULL = NULL, <> NULL은 사용하지 않습니다

AND/OR 혼합은 반드시 괄호로 의도를 고정하는 것을 권장합니다. 아래 두 조건은 겉보기는 비슷하지만 결과가 달라질 수 있습니다.

-- 의도: (A이면서 B) 또는 C
WHERE (status = 'PAID' AND total_amount >= 10000)
   OR vip_yn = 'Y';

-- 의도: A이면서 (B 또는 C)
WHERE status = 'PAID'
AND (total_amount >= 10000 OR vip_yn = 'Y');

조건을 한글 문장으로 바꿔 읽어보면 실수가 크게 줄어듭니다. “그리고/또는”의 묶음을 눈으로 확정하는 단계가 필요합니다.

4. LIKE, BETWEEN, IN 빠르게 정리

LIKE·BETWEEN·IN은 자주 쓰는 조건 표현식입니다. 다만 편리한 만큼, 경계값(특히 날짜/시간)과 NULL의 영향을 한 번 더 확인하는 습관이 중요합니다.

4-1) LIKE: 패턴 매칭

LIKE는 문자열 패턴을 조건으로 필터링할 때 사용합니다. %는 “0개 이상 아무 문자”, _는 “정확히 1글자”를 의미합니다.

-- '김'으로 시작하는 고객명
WHERE customer_name LIKE '김%';

-- '2025-'로 시작하는 문자열(예: '2025-12-01' 같은 텍스트)
WHERE date_text LIKE '2025-%';

-- 정확히 1글자 + '차' (예: '2차', '3차')
WHERE round_name LIKE '_차';

LIKE는 “포함 검색”처럼 보이지만 실제로는 패턴 규칙이 핵심입니다. 대소문자 구분 여부나 한글 정렬/비교 규칙은 DB/컬레이션 설정에 따라 달라질 수 있으므로, 결과가 이상하면 해당 환경의 문자열 비교 규칙도 함께 점검하시는 것이 좋습니다.

4-2) BETWEEN: 구간(범위) 조건

BETWEEN은 범위를 간단히 표현합니다. 일반적으로 양 끝값을 포함(inclusive)하는 방식으로 동작합니다.

-- 금액이 10,000원 이상 50,000원 이하
WHERE total_amount BETWEEN 10000 AND 50000;

날짜/시간에 BETWEEN을 쓸 때 가장 많이 실수하는 포인트는 “끝 날짜의 포함 여부”입니다. 예를 들어 주문일시가 datetime(시간 포함)이라면, 아래처럼 “시작 이상, 다음날 0시 미만” 형태로 쓰는 것이 안정적입니다.

-- 2025-01 한 달(예: 2025-01-01 00:00:00 이상, 2025-02-01 00:00:00 미만)
WHERE order_date >= '2025-01-01'
  AND order_date <  '2025-02-01';

이 방식은 “월말 23:59:59까지 포함” 같은 애매한 표현을 피할 수 있어, 실무에서 오류가 크게 줄어듭니다.

4-3) IN: 여러 값 중 하나

IN은 “여러 값 중 하나면 통과”라는 조건을 간단히 표현합니다.

-- 상태가 결제완료 또는 배송중인 주문만
WHERE status IN ('PAID', 'SHIPPING');

주의할 점은 IN 목록에 NULL이 섞이는 경우입니다. 예를 들어 status IN ('PAID', NULL) 같은 형태는 결과가 직관과 달라질 수 있습니다. NULL을 포함해야 한다면 IN으로 해결하려 하지 말고 OR status IS NULL처럼 의도를 명시하는 편이 안전합니다.

5. NULL 이해: 왜 조건이 안 먹는가

NULL은 “0”이나 “빈 문자열”이 아닙니다. 가장 정확한 설명은 “값이 아직 정해지지 않았다(미정)”에 가깝습니다. 그래서 SQL은 비교 연산 결과를 TRUE/FALSE만으로 처리하지 않고, 경우에 따라 UNKNOWN이라는 상태를 가질 수 있습니다.

그리고 중요한 규칙이 하나 있습니다. WHERE는 조건식이 TRUE인 행만 남깁니다. FALSE는 당연히 제외되고, UNKNOWN도 제외됩니다. 이 때문에 NULL과 얽히면 “조건이 안 먹는 것처럼” 보이는 상황이 생깁니다.

-- 잘못된 예: NULL을 비교로 잡으려 함 (대부분 동작하지 않음)
WHERE coupon_id = NULL;

-- 올바른 예: NULL은 IS NULL로 체크
WHERE coupon_id IS NULL;

NULL 처리의 기본기는 2가지입니다.

  • NULL을 찾고 싶다: IS NULL
  • NULL이 아닌 값만 보고 싶다: IS NOT NULL

추가로, NULL을 “대체값”으로 바꿔서 비교하고 싶을 때는 COALESCE를 사용할 수 있습니다.

-- coupon_id가 NULL이면 0으로 보고 비교
WHERE COALESCE(coupon_id, 0) <> 0;

COALESCE는 여러 DB에서 공통적으로 쓰이는 함수이며, “첫 번째로 NULL이 아닌 값”을 반환합니다. 다만 “NULL도 의미 있는 상태”인 경우(예: 미등록/미확정)라면 무조건 대체하기보다 IS NULL 조건을 함께 설계하는 편이 더 명확합니다.


실습: “특정 기간 주문”, “특정 카테고리 상품” 조회

실습에서는 아래와 같은 전형적인 커머스 스키마를 가정하겠습니다(컬럼명은 환경에 맞게 바꿔 적용하시면 됩니다).

  • orders: order_id, user_id, order_date, status, total_amount, coupon_id(NULL 가능)
  • products: product_id, product_name, category_id, price, discontinued_at(NULL 가능)
  • categories: category_id, category_name

실습 1) 특정 기간 주문 조회

요구사항: “2025년 1월 주문 중 결제 완료(PAID)만, 주문일시 내림차순”

SELECT
  o.order_id,
  o.user_id,
  o.order_date,
  o.total_amount AS amount,
  o.status
FROM orders AS o
WHERE o.order_date >= '2025-01-01'
  AND o.order_date <  '2025-02-01'
  AND o.status = 'PAID'
ORDER BY o.order_date DESC;

포인트는 “끝 날짜를 포함시키려다 시간 때문에 누락되는 문제”를 피하는 것입니다. datetime이라면 월말 23:59:59를 정확히 지정하기 어렵기 때문에, 다음 달 1일 0시 미만 조건을 권장합니다.

실습 2) 특정 카테고리 상품 조회

요구사항: “카테고리명이 ‘생활용품’인 상품 중, 판매 중(단종되지 않음)만”

SELECT
  p.product_id,
  p.product_name,
  c.category_name,
  p.price
FROM products AS p
JOIN categories AS c
  ON p.category_id = c.category_id
WHERE c.category_name = '생활용품'
  AND p.discontinued_at IS NULL
ORDER BY p.price ASC;

단종 여부처럼 “일시가 기록되면 종료, 기록이 없으면 진행 중”인 컬럼은 NULL이 중요한 의미를 가집니다. 이런 경우는 비교 연산이 아니라 IS NULL로 의도를 표현하는 것이 정확합니다.

응용) IN과 LIKE를 함께 사용해 검색형 조회 만들기

요구사항: “카테고리가 ‘생활용품’ 또는 ‘주방’이고, 상품명에 ‘세제’가 포함된 상품”

SELECT
  p.product_id,
  p.product_name,
  c.category_name,
  p.price
FROM products AS p
JOIN categories AS c
  ON p.category_id = c.category_id
WHERE c.category_name IN ('생활용품', '주방')
  AND p.product_name LIKE '%세제%'
  AND p.discontinued_at IS NULL;

이 쿼리는 “여러 카테고리 OR 조건”을 IN으로 깔끔하게 정리하고, “부분 문자열 포함”을 LIKE로 표현한 형태입니다. 조건이 길어질수록 한 줄씩 추가해가며 결과를 확인하는 방식이 실수를 줄입니다.


따라하기: 실무형 조회 쿼리 작성 루틴

  1. FROM부터 고정합니다. 어떤 테이블(또는 조인 조합)에서 데이터를 가져올지 먼저 정하면, 이후 SELECT/WHERE가 흔들리지 않습니다.

    처음에는 ORDER BY, LIMIT 같은 부가 요소는 빼고 “가져올 수 있나”부터 확인합니다.

  2. SELECT는 최소 컬럼으로 시작합니다. order_id, created_at 같은 키 컬럼부터 출력해 결과를 빠르게 검증합니다.

    필요한 컬럼을 하나씩 추가하면서 쿼리 의미를 명확히 합니다(별칭 AS 적극 활용).

  3. WHERE는 1개 조건씩 추가합니다. 기간 → 상태 → 금액 같은 순서로 점진적으로 좁히면 “어디서 결과가 사라졌는지” 즉시 파악할 수 있습니다.

    AND/OR가 섞이는 순간에는 괄호를 먼저 넣고, 조건을 문장으로 다시 읽어보시기 바랍니다.

  4. NULL 가능 컬럼을 먼저 체크합니다. 조건에 쓰는 컬럼이 NULL을 가질 수 있다면, IS NULL/IS NOT NULL 또는 COALESCE 전략을 사전에 결정해두면 디버깅 시간이 크게 줄어듭니다.

체크포인트: NULL 때문에 조건이 안 먹는 사례

초보자에게 가장 흔한 “조건이 안 먹는 것 같은” 상황을 하나로 정리해보겠습니다.

사례) “쿠폰을 사용하지 않은 주문만” 조회하려고 했는데 결과가 이상하다

쿠폰 미사용 주문을 coupon_id로 판단한다고 가정해보겠습니다. 설계에 따라 쿠폰 미사용이 0일 수도 있고, NULL일 수도 있습니다(또는 둘 다 섞일 수도 있습니다). 이때 아래처럼 쓰면 예상과 다르게 나올 수 있습니다.

-- 의도: 쿠폰이 없는 주문만(미사용)
-- 문제: coupon_id가 NULL인 행은 이 조건에서 TRUE가 되지 않아 빠질 수 있음
SELECT
  order_id, user_id, coupon_id
FROM orders
WHERE coupon_id = 0;

만약 쿠폰 미사용이 NULL로 들어간 데이터가 많다면, 위 조건은 “0인 것만” 잡고 NULL은 놓칩니다. 반대로 “쿠폰을 사용한 주문만 제외”하려고 아래처럼 작성해도 함정이 생깁니다.

-- 의도: 0이 아닌 것만(= 사용한 주문만 남기거나/빼거나)
-- 문제: NULL은 <> 비교가 TRUE가 아니라 UNKNOWN이 될 수 있어 결과가 비어 보일 수 있음
SELECT
  order_id, user_id, coupon_id
FROM orders
WHERE coupon_id <> 0;

해결 방법은 “데이터 정의를 먼저 확정”하고, 그 정의를 SQL에 정확히 표현하는 것입니다. 대표적인 해결 패턴은 아래 2가지입니다.

  • 패턴 A) NULL 자체를 의미로 인정: “쿠폰 미사용 = NULL”이라면 IS NULL을 사용합니다.

    SELECT order_id, user_id, coupon_id
    FROM orders
    WHERE coupon_id IS NULL;
  • 패턴 B) 대체값으로 통일: “쿠폰 미사용 = 0”처럼 보고 싶다면 COALESCE로 NULL을 0으로 바꾼 뒤 비교합니다.

    SELECT order_id, user_id, coupon_id
    FROM orders
    WHERE COALESCE(coupon_id, 0) = 0;

정리하면, “NULL이 존재할 수 있는 컬럼을 비교 연산으로만 처리하려고 하면” 조건이 예상과 다르게 동작할 확률이 높습니다. WHERE는 TRUE만 남긴다는 규칙을 기억하시고, NULL을 명시적으로 다루는 습관을 꼭 들이시기 바랍니다.

추가로 생각해볼 점

  • 조회 결과가 갑자기 0건이 되면, 조건을 하나씩 빼면서 “어느 조건에서 데이터가 사라지는지”를 확인하는 것이 가장 빠른 디버깅 방법입니다.
  • 날짜 필터는 BETWEEN보다 “시작 이상, 다음 구간 미만” 방식이 안전한 경우가 많습니다(특히 datetime).
  • NULL을 대체값으로 처리(COALESCE)할지, NULL 자체를 의미로 유지(IS NULL)할지는 “업무 정의”에 맞춰 일관되게 선택하는 것이 중요합니다.


이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

Reactions

댓글 쓰기

0 댓글