[데이터베이스실습] 03. SELECT - WHERE 조건
SELECT가 데이터를 꺼내는 명령이라면, WHERE는 그중 원하는 행만 걸러내는 조건이다. 실무 쿼리의 난이도는 사실상 이 WHERE 절을 얼마나 정확히 쓰느냐에 달려 있다고 한다.
이번엔 비교·논리 연산자부터 IN·BETWEEN·LIKE, 그리고 내가 가장 많이 헤맨 NULL 처리까지, 조건을 거는 도구들을 예제와 함께 익혔다.
1. WHERE 절
SELECT [열] FROM [테이블] WHERE [조건식];
조건식이 참(TRUE)인 행만 결과에 포함된다.
SELECT * FROM EMP WHERE DEPTNO = 30; -- 30번 부서 사원만
SELECT * FROM EMP WHERE SAL >= 3000; -- 급여 3000 이상
문자 데이터는 반드시 작은따옴표로 감싼다(별칭에 쓰는 큰따옴표와 구별). 그리고 데이터 값은 대소문자를 엄격히 구분한다. WHERE ENAME = 'SCOTT'은 맞지만 'scott'은 결과가 없다. (단, 열 이름·키워드는 대소문자를 가리지 않는다.)
처음에 'scott'으로 검색했다가 결과가 안 나와서 한참 헤맸는데, 데이터 값은 대소문자를 구분한다는 걸 그제야 알았다.
2. 논리 연산자 (AND, OR, NOT)
조건이 여러 개일 때 AND·OR로 묶고, NOT으로 부정한다.
SELECT * FROM EMP WHERE JOB='SALESMAN' AND DEPTNO=30; -- 둘 다 만족
SELECT * FROM EMP WHERE JOB='CLERK' OR DEPTNO=30; -- 하나만 만족해도
SELECT * FROM EMP WHERE NOT (SAL = 3000); -- 3000이 아닌
AND와 OR가 섞이면 AND가 먼저 평가되므로, 의도한 순서가 있으면 괄호로 묶어 명확히 하는 게 안전하다.
3. 범위·집합·패턴 연산자
IN: 여러 값 중 하나
같은 열이 여러 값 중 하나에 해당하는지 볼 때, OR를 여러 번 쓰는 대신 IN으로 묶는다.
SELECT * FROM EMP WHERE JOB IN ('MANAGER', 'SALESMAN', 'CLERK');
-- 위와 동일: JOB='MANAGER' OR JOB='SALESMAN' OR JOB='CLERK'
SELECT * FROM EMP WHERE JOB NOT IN ('MANAGER', 'CLERK');
BETWEEN A AND B: 범위
SELECT * FROM EMP WHERE SAL BETWEEN 2000 AND 3000; -- 2000 이상 3000 이하
SELECT * FROM EMP WHERE SAL NOT BETWEEN 2000 AND 3000; -- 그 범위 밖
BETWEEN은 양 끝 값을 포함(이상~이하)한다는 점이 중요하다. "2000 이상 3000 미만"처럼 한쪽을 빼려면 SAL >= 2000 AND SAL < 3000으로 직접 써야 한다.
LIKE: 문자열 패턴
| 와일드카드 | 의미 |
|---|---|
| % | 0개 이상의 임의 문자열 |
| _ | 임의의 한 문자 |
SELECT * FROM EMP WHERE ENAME LIKE 'S%'; -- S로 시작
SELECT * FROM EMP WHERE ENAME LIKE '%S'; -- S로 끝
SELECT * FROM EMP WHERE ENAME LIKE '_L%'; -- 두 번째 글자가 L
SELECT * FROM EMP WHERE ENAME LIKE '%AM%'; -- AM 포함
LIKE '%A%'처럼 앞에 %가 붙는 패턴은 인덱스를 활용하지 못해, 큰 테이블에서는 전체를 훑느라 매우 느려질 수 있다. 와일드카드 문자(_, %) 자체를 데이터로 찾아야 할 때는 ESCAPE 절을 쓴다. 예: ... LIKE 'A\_A%' ESCAPE '\' 는 "A_A"로 시작하는 데이터를 찾는다.
4. NULL 처리
NULL은 "값이 아직 정해지지 않았거나 존재하지 않는 상태"를 뜻한다. 계좌를 안 만든 고객의 계좌번호, 미혼 고객의 결혼기념일처럼 "해당 없음·미상"을 나타낸다. 값이 아니므로 NULL과의 산술·비교 연산은 결과가 NULL(알 수 없음)이 된다.
그래서 NULL은 =로 찾을 수 없고, 반드시 IS NULL / IS NOT NULL로 검사한다.
SELECT * FROM EMP WHERE COMM IS NULL; -- 수당이 없는 사원
SELECT * FROM EMP WHERE MGR IS NOT NULL; -- 직속 상관이 있는 사원
WHERE COMM = NULL이라고 쓰면 문법 오류는 아니지만 아무 행도 나오지 않는다. "어떤 값이 NULL과 같은가?"라는 물음의 답은 언제나 "알 수 없음"이라 참이 되는 행이 없기 때문이다. NULL이 비교·논리 연산에서 true/false가 아닌 unknown으로 다뤄지는 이 3치 논리가, NULL 처리에서 가장 자주 발을 거는 지점이었다.
실습 쿼리로 익히기
조건들을 조합해 직접 던져본 쿼리들이다.
-- 30번 부서의 SALESMAN
SELECT EMPNO, ENAME, JOB, SAL, DEPTNO
FROM EMP
WHERE DEPTNO = 30 AND JOB = 'SALESMAN';
-- (20번 또는 30번 부서) 이면서 급여 2000 초과
SELECT EMPNO, ENAME, JOB, SAL, DEPTNO
FROM EMP
WHERE (DEPTNO = 20 OR DEPTNO = 30) AND SAL > 2000;
-- 이름에 E를 포함하는 30번 부서 사원 중, 급여가 1000~2000 범위 밖
SELECT ENAME, EMPNO, SAL, DEPTNO
FROM EMP
WHERE ENAME LIKE '%E%'
AND DEPTNO = 30
AND SAL NOT BETWEEN 1000 AND 2000;
-- 수당이 없고(COMM NULL), 상관이 있고(MGR NOT NULL),
-- 직책이 CLERK 또는 MANAGER이며, 이름 두 번째 글자가 L이 아닌 사원
SELECT *
FROM EMP
WHERE COMM IS NULL
AND MGR IS NOT NULL
AND JOB IN ('CLERK', 'MANAGER')
AND ENAME NOT LIKE '_L%';
마지막 쿼리처럼 여러 조건을 AND로 쌓아 올리는 게 실무 WHERE의 전형이다. 조건을 하나씩 추가하며 결과가 어떻게 좁혀지는지 직접 확인하니 감이 빨리 잡혔다.
수업에서 직접 친 쿼리
수업에서 흥미로웠던 건 문자열도 부등호로 비교된다는 점이었다.
-- 사원이름이 'FORZ'보다 사전순으로 앞에 있는 사원
SELECT * FROM EMP WHERE ENAME < 'FORZ';
"같지 않다"도 세 가지 표기를 다 쳐보며 같은 결과인 걸 확인했다.
SELECT * FROM EMP WHERE SAL <> 3000;
SELECT * FROM EMP WHERE SAL != 3000;
SELECT * FROM EMP WHERE SAL ^= 3000; -- 오라클에서 ^=도 "같지 않다"
"MANAGER도 SALESMAN도 CLERK도 아닌 사원"을 NOT IN, AND 부정, NOT(...OR...) 세 방식으로 모두 풀어보며 같은 집합을 가리킨다는 걸 익혔다.
SELECT * FROM EMP WHERE JOB NOT IN ('MANAGER', 'SALESMAN', 'CLERK');
SELECT * FROM EMP WHERE JOB <> 'MANAGER' AND JOB <> 'SALESMAN' AND JOB <> 'CLERK';
SELECT * FROM EMP WHERE NOT (JOB='MANAGER' OR JOB='SALESMAN' OR JOB='CLERK');
연습문제에서 가장 까다로웠던 Q6은 NULL·NOT NULL·IN·NOT LIKE를 한꺼번에 거는 문제였다.
-- 수당이 없고(COMM NULL), 상관이 있고(MGR NOT NULL),
-- 직책이 CLERK/MANAGER이며, 이름 두 번째 글자가 L이 아닌 사원
SELECT *
FROM EMP
WHERE COMM IS NULL
AND MGR IS NOT NULL
AND JOB IN ('CLERK', 'MANAGER')
AND ENAME NOT LIKE '_L%';
오늘 느낀 점
- WHERE 절은 결국 "참인 행만 남긴다"는 한 원리 위에 IN·BETWEEN·LIKE가 편의 문법으로 얹힌 거였다. IN을 OR로, BETWEEN을 부등호로 풀어 써보니 본질이 같다는 게 보였다.
- 대소문자 구분, NULL 비교처럼 직접 에러를 내보고서야 몸에 익는 것들이 있었다. 눈으로만 읽었으면 또 틀렸을 것 같다.
- 조건을 하나씩 쌓으며 결과가 좁혀지는 걸 보는 게, 복잡한 WHERE를 한 번에 이해하려는 것보다 훨씬 빨랐다.
한 걸음 더
- 부정 비교는 여러 표기가 있다.
<>,!=,^=는 모두 "같지 않다"는 같은 뜻이고,NOT (조건)으로도 같은 결과를 만들 수 있다.IN/BETWEEN/LIKE도 각각NOT IN/NOT BETWEEN/NOT LIKE로 부정형을 쓴다. 표현이 여러 가지여도 결국 같은 집합을 가리킨다는 걸 알면 쿼리를 더 유연하게 읽고 쓸 수 있다. - "수당을 실제로 받는 사원"을 찾으려면
COMM IS NOT NULL AND COMM > 0처럼 NULL 여부와 값 조건을 함께 걸어야 한다. NULL인 행은COMM > 0비교에서 걸러지지 못하고 unknown으로 빠지기 때문이다. 실무 데이터에는 NULL이 흔해서, 조건을 짤 때 "이 열에 NULL이 있을 수 있나?"를 늘 의식하는 게 버그를 줄인다. - 대소문자를 무시하고 검색하고 싶으면
WHERE UPPER(ENAME) = 'SCOTT'처럼 함수로 양쪽을 같은 케이스로 맞춘다. 다만 이렇게 열에 함수를 씌우면 그 열의 인덱스를 못 타게 되는 부작용이 있어, 대용량에서는 함수 기반 인덱스 같은 별도 대비가 필요하다.