SQL WHERE 절
더 많은 작업
- 상위 문서: SQL
- WHERE Clause
- FROM 절에서 읽은 행 가운데 조건을 만족하는 행만 남기는 SQL 절
WHERE 절은 SELECT, UPDATE, DELETE 문에서 처리할 행을 고르는 조건을 적는 곳이다. 조건식의 결과가 참(TRUE)인 행만 남고, 거짓(FALSE)이나 알 수 없음(UNKNOWN)인 행은 버려진다. NULL과의 비교는 UNKNOWN이 되므로 조건을 쓸 때 NULL 처리를 따로 생각해야 한다. WHERE 절이 없으면 모든 행이 대상이 된다.
B_EMP
| 사원번호 | 이름 | 부서번호 | 관리자번호 | 급여 | 입사일 | 커미션 |
|---|---|---|---|---|---|---|
| 1001 | 김대표 | 10 | NULL | 9000 | 2015-01-05 | NULL |
| 1002 | 이부장 | 20 | 1001 | 6000 | 2017-03-15 | NULL |
| 1003 | 박과장 | 20 | 1002 | 4500 | 2019-07-01 | NULL |
| 1004 | 최대리 | 30 | 1001 | 4000 | 2020-11-20 | 500 |
| 1005 | 정사원 | 30 | 1004 | 3000 | 2022-02-28 | 0 |
| 1006 | 한신입 | NULL | 1002 | 2500 | 2024-08-01 | NULL |
| 구분 | 연산자 | 의미 |
|---|---|---|
| 비교 연산자 | =, >, >=, <, <= | 같다, 크다, 크거나 같다, 작다, 작거나 같다 |
| <>, !=, ^= | 같지 않다. 세 가지 모두 Oracle에서 쓸 수 있으며 표준은 <>이다.
| |
| SQL 연산자 | BETWEEN a AND b | a 이상 b 이하(양 끝 포함) |
| IN (목록) | 목록 값 중 하나와 같다 | |
| LIKE '패턴' | 문자 패턴과 일치한다. %는 0개 이상의 아무 문자, _는 정확히 1개의 아무 문자
| |
| IS NULL | 값이 NULL이다 | |
| 논리 연산자 | NOT, AND, OR | 부정, 그리고, 또는 |
| 부정 연산자 | NOT BETWEEN a AND b | a 미만이거나 b 초과 |
| NOT IN (목록), IS NOT NULL | 목록 값 모두와 다르다, NULL이 아니다 |
| 조건 | 결과 행 | 설명 |
|---|---|---|
급여 BETWEEN 3000 AND 4500 |
박과장, 최대리, 정사원 | 4500과 3000도 포함된다. |
급여 BETWEEN 4500 AND 3000 |
0행 | 급여 >= 4500 AND 급여 <= 3000과 같아서 작은 값을 앞에 써야 한다.
|
급여 NOT BETWEEN 3000 AND 4500 |
김대표, 이부장, 한신입 | |
부서번호 IN (10, 30) |
김대표, 최대리, 정사원 | 부서번호 = 10 OR 부서번호 = 30과 같다.
|
이름 LIKE '%장' |
이부장, 박과장 | '장'으로 끝나는 이름 |
이름 LIKE '_대%' |
김대표, 최대리 | 두 번째 글자가 '대'인 이름 |
부서번호 IS NULL |
한신입 | |
부서번호 = NULL |
0행 | NULL과의 비교는 UNKNOWN이므로 IS NULL을 써야 한다. |
부서번호 <> 20 |
김대표, 최대리, 정사원 | 부서번호가 NULL인 한신입은 포함되지 않는다. !=, ^=, NOT 부서번호 = 20도 결과가 같다.
|
커미션 IS NOT NULL |
최대리, 정사원 | 커미션 0은 NULL이 아니다. |
%나 _ 문자 자체를 찾으려면 ESCAPE로 지정한 문자를 앞에 붙인다.
SELECT 'A_1' FROM DUAL WHERE 'A_1' LIKE 'A\_%' ESCAPE '\'; -- 1행: '_' 문자 자체와 일치
SELECT 'AB1' FROM DUAL WHERE 'AB1' LIKE 'A\_%' ESCAPE '\'; -- 0행
SELECT 'AB1' FROM DUAL WHERE 'AB1' LIKE 'A_%'; -- 1행: '_'가 아무 문자 1개
| 순위 | 연산자 |
|---|---|
| 1 | 괄호 ( ) |
| 2 | 비교 연산자, SQL 연산자(BETWEEN, IN, LIKE, IS NULL) |
| 3 | NOT |
| 4 | AND |
| 5 | OR |
AND가 OR보다 먼저 계산되므로 괄호 유무에 따라 결과가 달라진다.
-- (1) 괄호 없음: 부서번호 = 30 OR (부서번호 = 20 AND 급여 >= 5000)
SELECT 이름, 부서번호, 급여 FROM B_EMP
WHERE 부서번호 = 30 OR 부서번호 = 20 AND 급여 >= 5000;
-- (2) 괄호 있음
SELECT 이름, 부서번호, 급여 FROM B_EMP
WHERE (부서번호 = 30 OR 부서번호 = 20) AND 급여 >= 5000;
| 쿼리 | 결과 | 행 수 |
|---|---|---|
| (1) 괄호 없음 | 이부장(20, 6000), 최대리(30, 4000), 정사원(30, 3000) | 3 |
| (2) 괄호 있음 | 이부장(20, 6000) | 1 |
- (1)에서는 30번 부서 사원이 급여와 상관없이 모두 나온다.
NOT 부서번호 = 20 AND 급여 >= 4000은(NOT 부서번호 = 20) AND 급여 >= 4000으로 처리되어 김대표, 최대리 2행이 나온다.
Oracle은 비교하는 두 값의 형식에 따라 문자열을 다르게 비교한다.
| 경우 | 비교 방법 |
|---|---|
| 양쪽이 CHAR(또는 문자 리터럴) | 짧은 쪽 끝에 공백을 채워 길이를 맞춘 뒤 비교한다. 뒤 공백만 다르면 같다. |
| 한쪽이라도 VARCHAR2 | 공백을 채우지 않고 그대로 비교한다. 뒤 공백도 문자로 친다. |
| 문자와 숫자 | 문자를 숫자로 바꾸어 비교한다(암시적 형변환). |
CHAR(5) 칼럼 C와 VARCHAR2(5) 칼럼 V에 'AB'와 'AB '(뒤 공백 1개)를 넣은 테이블로 확인한 결과이다. C에는 길이 5로 공백이 채워져 저장된다.
| 조건 | 일치 행 수 | 이유 |
|---|---|---|
C = 'AB' |
2 | CHAR와 리터럴이므로 공백 패딩 비교 |
V = 'AB' |
1 | VARCHAR2는 'AB '(공백 포함)와 'AB'를 다르게 본다. |
C = V |
0 | 'AB '(CHAR)와 'AB', 'AB '(VARCHAR2)를 공백 패딩 없이 비교 |
'AB' = 'AB ' |
1 | 리터럴끼리는 CHAR 비교 |
NOT IN 목록에 NULL이 하나라도 있으면 결과는 0행이다. 부서번호 NOT IN (20, NULL)은 부서번호 <> 20 AND 부서번호 <> NULL이고, 부서번호 <> NULL은 항상 UNKNOWN이기 때문이다. 반면 IN (20, NULL)은 OR로 풀리므로 20번 부서 2행이 정상으로 나온다. 서브쿼리 결과에 NULL이 섞일 때 같은 문제가 생긴다(데이터베이스 Null, 데이터베이스 서브 쿼리).
| 조건 | 결과 |
|---|---|
부서번호 NOT IN (20) |
김대표, 최대리, 정사원(3행) |
부서번호 NOT IN (20, NULL) |
0행 |
부서번호 IN (20, NULL) |
이부장, 박과장(2행) |
WHERE 절은 그룹을 만들기 전의 개별 행에 적용되므로 집계 함수를 쓸 수 없다. 그룹 조건은 HAVING 절에 쓴다(SQL 그룹 함수).
SELECT 부서번호 FROM B_EMP WHERE AVG(급여) >= 5000 GROUP BY 부서번호;
-- ORA-00934: group function is not allowed here
SELECT 부서번호, AVG(급여) FROM B_EMP
GROUP BY 부서번호
HAVING AVG(급여) >= 5000; -- 10번(9000), 20번(5250)
Oracle의 ROWNUM은 WHERE 절을 통과한 행에 1부터 차례로 붙는 번호이다. WHERE ROWNUM <= 3은 3행을 돌려주지만 WHERE ROWNUM = 2는 0행이다. 첫 행이 ROWNUM 1을 받자마자 조건(=2)에서 탈락해 다음 행이 다시 1이 되기 때문이다. 정렬한 뒤 상위 N개를 고르는 방법은 Top N 쿼리에서 다룬다.
SELECT 문은 적힌 순서가 아니라 다음 순서로 논리적으로 처리된다.
| 순서 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 절 | FROM | WHERE | GROUP BY | HAVING | SELECT | ORDER BY |
WHERE 절은 SELECT 절보다 먼저 처리되므로 SELECT 절에서 붙인 별칭을 WHERE 절에서 쓸 수 없다.
SELECT 이름, 급여*12 AS 연봉 FROM B_EMP WHERE 연봉 >= 50000;
-- ORA-00904: "연봉": invalid identifier
SELECT 이름, 급여*12 AS 연봉 FROM B_EMP WHERE 급여*12 >= 50000; -- 식을 다시 쓴다
- ORDER BY 절은 SELECT 다음에 처리되므로 별칭을 쓸 수 있다(SQL ORDER BY 절).
- AND가 OR보다 먼저 계산된다. 괄호 유무에 따른 결과 행 수 차이를 묻는 문제가 자주 나온다.
- BETWEEN은 양 끝을 포함하고, 작은 값을 앞에 써야 한다.
LIKE의%와_차이, ESCAPE 사용법 = NULL은 결과가 없고 IS NULL을 써야 한다. NOT IN 목록에 NULL이 있으면 0행이다.<>조건에서 NULL 행은 빠진다.- CHAR끼리는 공백을 채워 비교하고, VARCHAR2가 끼면 그대로 비교한다.
- WHERE에는 집계 함수와 SELECT 별칭을 쓸 수 없다(FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY).