SQL WHERE 조건 정리: 비교 연산자부터 NULL 처리까지
들어가며
데이터베이스를 처음 다룰 때 SELECT와 INSERT를 어느 정도 익히고 나면, 다음 단계에서 반드시 마주하게 되는 것이 WHERE 조건이다. 많은 입문자들이 SELECT 구문을 통해 테이블의 데이터를 조회하는 것까지는 비교적 수월하게 따라오지만, WHERE 절을 제대로 이해하지 못한 채로 넘어가는 경우가 적지 않다. 그 결과, 쿼리 자체는 오류 없이 실행되지만 반환된 데이터가 의도와 다른 상황이 반복된다. 실제로 초보 단계에서 가장 많이 발생하는 문제도 “조회는 되는데 결과가 이상하다”는 경우인데, 그 원인을 추적해 보면 대부분 WHERE 조건을 잘못 구성한 탓이다.
관계형 데이터베이스에서 테이블은 수십만, 수백만 건 이상의 레코드를 포함할 수 있다. 이 상황에서 조건 없이 전체 데이터를 조회하는 것은 성능 측면에서도, 데이터 활용 측면에서도 실질적인 의미를 갖기 어렵다. 데이터베이스의 핵심 가치는 대량의 데이터 중에서 필요한 정보를 정확하고 빠르게 추출하는 데 있으며, 그 추출의 기준을 정의하는 것이 바로 WHERE 절이다. 다시 말해, SQL을 얼마나 잘 활용할 수 있는지는 SELECT 문법을 아는 것이 아니라, WHERE를 통해 데이터를 얼마나 정밀하게 필터링할 수 있는지에 달려 있다.
이 글에서는 SQL WHERE 절이 어떤 역할을 하는지부터 시작해, 실제 사용법과 초보자들이 자주 저지르는 실수까지 함께 정리한다. 단순한 문법 나열이 아니라, 데이터 조회의 기준을 올바르게 세우는 것을 목표로 한다.
WHERE 절의 역할
WHERE 절은 데이터를 조회할 때 조건을 설정하는 역할을 한다. 기본적인 형태는 다음과 같다.
SELECT * FROM users WHERE age >= 20;
이 쿼리는 users 테이블에 저장된 전체 레코드 중 나이(age)가 20 이상인 데이터만 반환한다. SELECT가 테이블로부터 데이터를 읽어오는 명령어라면, WHERE는 그 데이터 집합 안에서 조건에 부합하는 행(row)만을 걸러내는 필터 역할을 한다.
처음에는 단순한 조건 구문처럼 보일 수 있지만, WHERE 절은 실질적으로 데이터 조회의 핵심을 담당한다. 관계형 데이터베이스는 운영 기간이 길어질수록 수십만, 수백만 건 이상의 레코드가 누적되는 구조를 가진다. 이 상황에서 WHERE 조건 없이 테이블 전체를 조회하는 것은 데이터베이스 서버에 불필요한 부하를 유발할 뿐 아니라, 반환된 결과에서 원하는 정보를 다시 수작업으로 찾아야 하는 비효율로 이어진다.
데이터베이스 엔진은 WHERE 절에 명시된 조건을 기반으로 실행 계획(Execution Plan)을 수립하고, 인덱스(Index)가 존재하는 경우 이를 활용해 탐색 범위를 최소화한다. 즉, WHERE 절은 단순히 결과를 줄이는 것을 넘어, 쿼리의 성능을 좌우하는 직접적인 요소이기도 하다. 조건을 어떻게 구성하느냐에 따라 동일한 데이터를 조회하더라도 실행 속도에 수십 배 이상의 차이가 발생할 수 있다.
결국 SQL 활용 능력은 데이터를 가져오는 방법을 아는 것이 아니라, 필요한 데이터를 정확하게 정의하고 필터링하는 능력에서 판가름 난다. WHERE 절을 얼마나 정밀하게 다룰 수 있는지가 그 기준이 된다.
비교 연산자
WHERE 절에서 가장 기본이 되는 것은 비교 연산자다. 비교 연산자는 특정 컬럼의 값과 지정한 기준값을 비교해 조건의 참(TRUE) 또는 거짓(FALSE)을 판별하며, 그 결과에 따라 반환할 행을 결정한다.
= : 같다
!= 또는 <> : 같지 않다
> : 크다
< : 작다
>= : 크거나 같다
<= : 작거나 같다
이 연산자들은 숫자형 데이터뿐 아니라 문자열, 날짜 타입에도 동일하게 적용된다. 예를 들어 날짜 컬럼에 >= '2024-01-01' 조건을 사용하면 해당 날짜 이후의 레코드만 필터링할 수 있다. 단, 문자열 비교의 경우 데이터베이스의 콜레이션(Collation) 설정에 따라 대소문자 구분 여부가 달라질 수 있으므로 주의가 필요하다.
이 연산자들로 기본적인 조건을 만들 수 있다. 하지만 실무에서는 하나의 조건만 사용하는 경우보다 여러 조건을 조합하는 경우가 훨씬 많다. 이때 사용하는 것이 논리 연산자인 AND와 OR이다.
SELECT * FROM users WHERE age >= 20 AND city = 'Seoul';
이 쿼리는 나이가 20 이상이면서 서울에 거주하는 사용자만 조회한다. AND는 명시된 모든 조건을 동시에 만족하는 행만 반환하므로, 조건이 추가될수록 결과 집합의 범위는 좁아진다.
SELECT * FROM users WHERE age >= 20 OR city = 'Seoul';
반면 OR는 명시된 조건 중 하나라도 만족하면 해당 행을 반환한다. 조건이 추가될수록 결과 집합의 범위가 넓어지는 구조이기 때문에, AND와 OR는 사용 목적을 명확히 구분해서 써야 한다.
여기서 반드시 주의해야 할 점은 조건의 우선순위다. SQL에서 AND는 OR보다 연산 우선순위가 높다. 따라서 AND와 OR를 함께 사용할 경우, 의도와 다른 조건 조합이 만들어져 예상치 못한 결과가 반환될 수 있다. 이를 방지하려면 괄호를 사용해 조건의 범위를 명시적으로 구분하는 것이 좋다.
SELECT * FROM users WHERE age >= 20 AND (city = 'Seoul' OR city = 'Busan');
괄호가 없다면 이 쿼리는 (age >= 20 AND city = 'Seoul') OR city = 'Busan'으로 해석되어, 부산 거주자는 나이 조건과 무관하게 모두 포함된다. 괄호를 추가함으로써 “나이가 20 이상이면서 서울 또는 부산에 거주하는 사용자”라는 의도를 정확하게 표현할 수 있다. 쿼리 작성 시 괄호를 적극적으로 활용하는 습관은 논리 오류를 사전에 방지하는 데 효과적이다.
LIKE와 NOT
문자열 패턴 검색에는 LIKE 연산자를 사용한다. LIKE는 컬럼 값이 지정한 패턴과 일치하는 행을 반환하며, 완전히 일치하는 값을 찾는 = 연산자와 달리 부분 일치 검색이 가능하다는 점에서 실무에서 매우 자주 활용된다
SELECT * FROM users WHERE name LIKE '김%';
이 쿼리는 이름이 ‘김’으로 시작하는 데이터를 조회한다. 여기서 %는 와일드카드(Wildcard) 문자로, 해당 위치에 어떤 문자열이든 올 수 있다는 의미다. LIKE에서 사용할 수 있는 와일드카드는 두 가지다.
%: 길이에 관계없이 임의의 문자열을 의미한다. 문자가 없는 경우도 포함한다._: 정확히 한 글자의 임의의 문자를 의미한다.
이를 조합하면 다양한 패턴 검색이 가능하다.
-- '김'으로 시작하는 모든 이름
SELECT * FROM users WHERE name LIKE '김%';
-- '수'로 끝나는 모든 이름
SELECT * FROM users WHERE name LIKE '%수';
-- 이름 중간에 '민'이 포함된 경우
SELECT * FROM users WHERE name LIKE '%민%';
-- 성이 한 글자이고 이름이 '민준'인 경우
SELECT * FROM users WHERE name LIKE '_민준';
단, LIKE 연산자는 인덱스를 효율적으로 활용하지 못하는 경우가 있다. 특히 %가 문자열 앞에 위치하는 패턴(LIKE '%수')은 인덱스를 타지 못하고 테이블 전체를 순차 탐색(Full Table Scan)하게 되므로, 대용량 데이터 환경에서는 성능에 주의해야 한다.
특정 값을 제외하고 싶을 때는 NOT을 사용한다. NOT은 조건의 결과를 반전시키는 논리 연산자로, 다음과 같이 두 가지 방식으로 표현할 수 있다.
SELECT * FROM users WHERE city != 'Seoul';
-- 또는
SELECT * FROM users WHERE NOT city = 'Seoul';
두 쿼리는 동일한 결과를 반환하지만, NOT을 명시적으로 사용하는 방식은 조건이 복잡해질수록 가독성 측면에서 유리할 수 있다. NOT은 LIKE와 조합해 사용하는 것도 가능하다.
-- 이름이 '김'으로 시작하지 않는 사용자 조회
SELECT * FROM users WHERE name NOT LIKE '김%';
이처럼 NOT은 단독으로 쓰이기보다 다른 연산자와 결합해 제외 조건을 표현할 때 특히 유용하다. 다만 NOT을 남용하면 쿼리의 의도를 파악하기 어려워질 수 있으므로, 가능한 경우 긍정 조건으로 재작성하는 것이 유지보수 측면에서 바람직하다.
초보자가 자주 하는 실수
가장 흔한 실수는 WHERE 조건을 부정확하게 작성하는 것이다. 특히 문자열 비교 시 따옴표를 빠뜨리거나, 숫자와 문자열 타입을 혼동하는 경우가 많다. 이런 실수는 단순한 오타로 끝나는 것이 아니라 잘못된 데이터 조회로 이어질 수 있어 주의가 필요하다.
-- 잘못된 예: 문자열에 따옴표 누락
SELECT * FROM users WHERE city = Seoul;
-- 올바른 예
SELECT * FROM users WHERE city = 'Seoul';
따옴표가 없는 경우 데이터베이스는 Seoul을 컬럼명이나 예약어로 해석하려 시도하며, 이는 문법 오류(Syntax Error)로 이어진다. 반대로 숫자 타입 컬럼에 따옴표를 붙이는 경우는 오류 없이 실행되는 경우도 있지만, 내부적으로 타입 변환(Type Casting)이 발생해 인덱스를 활용하지 못하고 성능이 저하될 수 있다.
-- 숫자 타입 컬럼에 문자열로 비교 (비권장)
SELECT * FROM users WHERE age = '20';
-- 올바른 예
SELECT * FROM users WHERE age = 20;
두 번째로 흔한 실수는 NULL 값을 비교 연산자로 처리하려는 경우다. SQL에서 NULL은 “값이 없음”을 의미하며, 일반적인 비교 연산자로는 올바르게 처리되지 않는다.
-- 잘못된 예: NULL 비교에 = 사용
SELECT * FROM users WHERE phone = NULL;
-- 올바른 예: IS NULL 사용
SELECT * FROM users WHERE phone IS NULL;
-- NULL이 아닌 경우
SELECT * FROM users WHERE phone IS NOT NULL;WHERE phone = NULL은 항상 아무 결과도 반환하지 않는다. NULL은 어떤 값과도 같지 않으며, NULL끼리 비교해도 TRUE가 되지 않기 때문이다. NULL 여부를 확인할 때는 반드시 IS NULL 또는 IS NOT NULL을 사용해야 한다.
세 번째는 조건 없이 SELECT를 실행하는 것이다.
SELECT * FROM users;
이 쿼리는 테이블의 모든 레코드를 반환한다. 개발 초기나 소규모 데이터 환경에서는 문제가 없어 보이지만, 데이터가 수십만 건 이상으로 늘어나면 서버 메모리와 네트워크 자원을 불필요하게 소모하는 쿼리가 된다. 실무 환경에서는 이러한 쿼리가 서비스 전체의 응답 속도에 영향을 미칠 수 있으며, 심한 경우 데이터베이스 서버 과부하로 이어지기도 한다.
또한 SELECT *는 테이블의 모든 컬럼을 가져오는 구문으로, 실제로 필요하지 않은 컬럼까지 포함해 데이터를 전송한다. 필요한 컬럼만 명시적으로 지정하는 습관이 성능과 유지보수 양쪽 모두에서 바람직하다.
-- 비권장
SELECT * FROM users WHERE age >= 20;
-- 권장
SELECT id, name, city FROM users WHERE age >= 20;
실무에서는 반드시 필요한 데이터만, 필요한 컬럼만 조회하는 습관을 들여야 한다. WHERE 조건을 정확하게 작성하는 것과 더불어, 이러한 기본 원칙을 지키는 것이 안정적인 데이터베이스 운영의 출발점이 된다.
마무리하며
WHERE 절은 단순한 조건문이 아니라, 데이터 조회의 기준을 정의하는 핵심 도구다. SQL을 처음 배울 때는 SELECT 문법을 익히는 것에 집중하기 쉽지만, 실제로 데이터베이스를 다루는 능력은 WHERE를 통해 필요한 데이터를 얼마나 정확하게 정의할 수 있는지에서 판가름 난다. 무엇을 조회할지보다, 무엇을 기준으로 걸러낼지를 명확하게 설계하는 것이 SQL의 본질에 가깝다.
이 글에서 다룬 비교 연산자, AND/OR 조합, LIKE 패턴 검색, NOT을 활용한 제외 조건은 모두 WHERE 절을 구성하는 기본 요소다. 각각의 문법을 개별적으로 이해하는 것도 중요하지만, 실무에서는 이 요소들을 목적에 맞게 조합해 정확한 조건을 설계하는 능력이 더 요구된다. 조건 하나의 차이가 반환되는 데이터 전체를 바꿀 수 있다는 점을 항상 염두에 두어야 한다.
WHERE 절에 대한 이해가 확립되면, 이후 학습 과정도 한결 수월해진다. JOIN은 여러 테이블을 연결할 때 각 테이블에 적용할 조건을 WHERE 또는 ON 절로 제어하고, GROUP BY와 HAVING은 집계된 결과에 조건을 추가하는 구조로 이루어진다. 결국 SQL의 고급 기능들은 WHERE 절의 확장선상에 있다고 볼 수 있으며, 기초가 탄탄할수록 이후 개념의 습득 속도도 빨라진다.
SELECT로 데이터를 가져오는 것보다, WHERE로 원하는 데이터를 정확하게 선택하는 것이 더 중요하다. 쿼리를 작성할 때마다 “이 조건이 정말 내가 원하는 데이터만 반환하는가”를 스스로 검토하는 습관을 들이는 것이, 장기적으로 데이터베이스를 다루는 역량을 높이는 가장 확실한 방법이다.
WHERE 절을 충분히 이해했다면, 다음 단계로는 여러 테이블을 연결해 데이터를 조회하는 JOIN으로 넘어가 보자.
Add your first comment to this post