모던지 / SQL 개발자 / SQL 기본 및 활용 2강. WHERE 절과 연산자

SQL 기본 및 활용 2강. WHERE 절과 연산자

조건절에 컬럼을 가공해 넣는 순간, 인덱스는 있는데 쓰이지 않는 상태가 됩니다.

풀고 시작

문제 1. 값이 없는 행까지 포함해 급여가 300 이하인 사원을 모두 뽑으려 합니다. 조건절 작성으로 가장 적절한 것은?
NULL은 등호로 비교할 수 없으므로 IS NULL을 써야 합니다. 비교 조건만 쓰면 값이 없는 행은 참도 거짓도 아니어서 결과에서 빠지고, 컬럼에 연산을 걸면 조건은 맞아도 인덱스를 쓰지 못하게 됩니다.

조건은 참인 행만 남깁니다

WHERE 절은 FROM으로 가져온 행에서 조건이 참인 행만 남깁니다. 여기서 "참이 아닌"과 "거짓인"이 다르다는 사실이 중요합니다. SQL의 논리값은 참과 거짓 외에 알 수 없음(unknown)이 있고, NULL이 비교에 참여하면 이 세 번째 값이 나옵니다. 알 수 없음은 참이 아니므로 결과에서 빠집니다.

이 성질 때문에 부정 조건이 특히 위험합니다. 급여가 300이 아닌 사원을 뽑으면 급여가 비어 있는 사원은 나오지 않습니다. 그 사원의 급여가 300인지 아닌지 알 수 없으니까요. 업무상 값이 없는 행까지 포함해야 한다면 조건을 하나 더 붙여야 합니다.

연산자를 종류별로

비교 연산자는 같음, 다름, 크다, 작다, 크거나 같다, 작거나 같다입니다. 다름을 나타내는 표기가 세 가지나 있어 헷갈리는데 셋 다 같은 뜻입니다.

SQL 연산자라고 따로 묶어 부르는 네 가지가 있습니다.

연산자 하는 일 주의할 점
BETWEEN a AND b a 이상 b 이하 양쪽 끝을 포함합니다
IN (목록) 목록 중 하나와 같음 목록에 NULL이 있으면 부정형에서 문제가 됩니다
LIKE 패턴 문자열 패턴 일치 앞이 열린 패턴은 인덱스를 타기 어렵습니다
IS NULL 값이 없음 등호로는 판정할 수 없습니다

LIKE의 와일드카드는 두 개입니다. 백분율 기호는 0글자 이상의 임의 문자열을, 밑줄은 정확히 한 글자를 뜻합니다. 그래서 밑줄 세 개는 세 글자짜리를 찾습니다. 패턴 문자 자체를 찾고 싶으면 ESCAPE 절로 탈출 문자를 지정합니다.

논리 연산자는 AND, OR, NOT입니다. 우선순위는 NOT이 가장 높고 그다음 AND, 마지막이 OR입니다. 이 순서를 잊으면 조건이 조용히 뒤집힙니다. 부서가 영업이거나 총무이고 급여가 300 이상인 사원을 찾겠다고 조건을 나열하면, 실제로는 영업 전체와 총무 중 급여 300 이상이 나옵니다. AND가 먼저 묶이니까요. 괄호를 쓰는 것이 실력이 아니라 습관이어야 하는 이유입니다.

부정형 IN과 NULL이 만나면

시험이 좋아하는 함정이 하나 있습니다. NOT IN 목록에 NULL이 섞이면 결과가 한 건도 나오지 않습니다.

이유를 따라가 보죠. NOT IN은 각 값과 다른지를 AND로 이어 붙인 것과 같습니다. 목록에 NULL이 있으면 그 항목의 비교 결과가 알 수 없음이 되고, AND로 이어진 조건에 알 수 없음이 하나라도 끼면 전체가 참이 될 수 없습니다. 참이 아니면 행이 남지 않습니다.

반면 IN 목록에 NULL이 있어도 문제가 없습니다. IN은 OR로 이어 붙인 것과 같아서 다른 항목 하나가 참이면 전체가 참이 되니까요. 서브쿼리 결과를 NOT IN에 넣는 경우에 이 사고가 특히 자주 납니다. 서브쿼리가 NULL을 하나라도 내보내면 상위 쿼리 결과가 통째로 비어 버리죠. 8강에서 NOT EXISTS로 바꿔 쓰는 이야기를 다시 하게 됩니다.

조건절을 쓰는 습관이 성능을 만듭니다

같은 결과를 내는 조건도 쓰는 방식에 따라 속도가 크게 갈립니다. 핵심 규칙은 하나입니다. 왼쪽 컬럼을 가공하지 마세요.

가입일에서 연도만 뽑아 비교하는 대신 가입일 자체를 기간으로 비교하고, 급여를 12로 나눠 비교하는 대신 반대쪽 값을 12로 곱합니다. 컬럼에 함수나 연산을 씌우면 데이터베이스는 그 컬럼의 인덱스를 쓸 수 없습니다. 저장된 값이 아니라 계산 결과를 봐야 하니까요.

자료형이 다른 값과 비교할 때도 같은 일이 벌어집니다. 문자 컬럼을 숫자와 비교하면 제품이 묵시적 형 변환을 걸어 주는데, 그 변환이 컬럼 쪽에 붙으면 인덱스가 무력해집니다. 그래서 조건에 쓰는 값은 컬럼과 같은 자료형으로 맞춰 주는 편이 안전합니다.

다음 강에서는 이 조건과 결과를 가공하는 단일행 함수를 봅니다. 방금 말한 "컬럼을 가공하지 말라"는 조언과 함수의 유용함이 어떻게 공존하는지도 함께 보게 됩니다.

인출 문제

문제 1. NOT IN 목록에 NULL이 포함되었을 때의 결과로 옳은 것은?
NOT IN은 각 항목과 다른지를 AND로 이어 붙인 형태라서, NULL 비교가 알 수 없음을 만들면 전체가 참이 되지 못합니다. IN은 OR로 이어지므로 같은 상황에서도 결과가 나옵니다.
문제 2. 논리 연산자의 우선순위를 높은 것부터 나열한 것은?
NOT이 가장 먼저, 그다음 AND, 마지막이 OR입니다. 이 순서 때문에 OR 조건을 괄호로 묶지 않으면 의도와 다른 집합이 나오므로 괄호를 습관처럼 쓰는 편이 안전합니다.
문제 3. 다음 중 인덱스를 활용하기 가장 어려운 조건절은?
컬럼에 함수를 씌우면 저장된 값이 아니라 계산 결과를 비교해야 하므로 그 컬럼의 인덱스를 쓸 수 없습니다. 같은 의미라도 컬럼을 그대로 두고 범위로 비교하도록 바꿔 쓰는 편이 낫습니다.
문제 4. LIKE 연산자에서 밑줄 문자의 의미는?
밑줄은 임의의 한 글자이고, 0글자 이상은 백분율 기호가 담당합니다. 자릿수를 정확히 맞춰 찾을 때 밑줄을 필요한 개수만큼 쓰면 됩니다.

더 풀기

출제기준의 세세항목을 따라 이 강의 범위에서 새로 낸 문제입니다. 모의고사도 여기에서 뽑습니다.

묶음 1

문제 1. WHERE 절이 하는 일을 가장 정확하게 설명한 것은?
WHERE는 행 단위로 조건을 평가하고 참인 것만 통과시킵니다. 데이터를 지우지는 않으며 그룹을 거르는 것은 HAVING입니다.
문제 2. 조건 평가 결과가 알 수 없음일 때 그 행은 어떻게 되나요?
WHERE는 참인 행만 남깁니다. 거짓과 알 수 없음은 모두 통과하지 못합니다.
문제 3. 비교 연산자 가운데 같지 않음을 나타내는 표준 표기는?
표준은 작은 부등호와 큰 부등호를 이어 쓴 형태입니다. 느낌표와 등호를 함께 쓰는 표기는 여러 제품이 지원하는 확장입니다.
문제 4. BETWEEN 연산자의 경계값 포함 여부로 옳은 것은?
BETWEEN은 이상과 이하를 뜻합니다. 그래서 경계값이 결과에 들어갑니다.
문제 5. 급여가 백과 이백과 삼백인 세 행에서 급여가 백과 삼백 사이라는 조건을 걸면 결과 행 수는?
BETWEEN은 경계를 포함하므로 백과 삼백도 함께 남아 세 행입니다.
문제 6. 날짜 컬럼에 대해 특정 하루를 조회하려 할 때 BETWEEN을 쓰면서 자주 저지르는 실수는?
날짜형에 시각이 포함되면 그날 자정만 경계에 걸리고 그 이후 시각은 빠집니다. 다음 날 자정 미만이라는 조건으로 쓰는 편이 안전합니다.
문제 7. IN 연산자를 가장 잘 설명한 것은?
IN은 여러 개의 같음 비교를 OR로 이은 것과 같습니다. 범위는 BETWEEN, NULL 확인은 IS NULL이 담당합니다.
문제 8. LIKE 연산자에서 임의의 한 글자를 나타내는 기호는?
퍼센트는 길이에 상관없는 임의의 문자열이고 밑줄은 정확히 한 글자입니다.
문제 9. 이름이 김으로 시작하는 사원을 찾는 조건으로 알맞은 것은?
뒤에 퍼센트를 붙이면 김으로 시작하는 모든 문자열이 걸립니다. 앞에 붙이면 김으로 끝나는 것이 되고 밑줄을 붙이면 두 글자만 걸립니다.
문제 10. 이름이 정확히 세 글자인 사원을 찾는 LIKE 패턴으로 알맞은 것은?
밑줄은 정확히 한 글자를 뜻하므로 세 개를 이으면 세 글자가 됩니다. 퍼센트는 길이를 제한하지 않습니다.

묶음 2

문제 1. LIKE 패턴 안에서 퍼센트 문자 자체를 찾고 싶다면 어떻게 해야 하나요?
ESCAPE로 지정한 문자를 앞에 붙이면 그 뒤의 기호가 특수 기호가 아니라 글자로 취급됩니다.
문제 2. NULL 여부를 확인하는 올바른 표현은?
NULL은 값이 아니므로 등호 비교가 성립하지 않습니다. 전용 표현인 IS NULL과 IS NOT NULL을 써야 합니다.
문제 3. 논리 연산자의 우선순위를 바르게 나열한 것은?
NOT이 가장 먼저이고 그다음 AND, 마지막이 OR입니다. 의도를 분명히 하려면 괄호를 쓰는 편이 안전합니다.
문제 4. 부서가 십이거나 이십이면서 직급이 과장인 사원을 찾으려 할 때 괄호 없이 조건을 이어 쓰면 어떤 문제가 생기나요?
AND가 OR보다 먼저 결합하므로 의도와 다른 묶음이 됩니다. 부서 조건을 괄호로 묶어야 합니다.
문제 5. 부서코드가 십과 이십과 NULL인 세 행에서 부서코드가 십과 이십 목록에 없다는 조건을 걸면 결과 행 수는?
NOT IN은 각 값과 같지 않음을 AND로 잇는데 NULL과의 비교는 알 수 없음이라 참이 되지 않습니다. 그래서 NULL 행도 빠져 결과가 없습니다.
문제 6. NOT IN을 쓸 때 목록 안에 NULL이 하나라도 있으면 어떤 일이 벌어지나요?
목록의 NULL과 비교하는 순간 알 수 없음이 되고, AND로 이어져 전체가 참이 되지 못합니다. 이럴 때는 NOT EXISTS가 안전합니다.
문제 7. IN 목록 안에 NULL이 있을 때는 왜 문제가 되지 않나요?
OR로 이어진 조건에서는 하나만 참이면 됩니다. AND로 이어지는 NOT IN과 결정적으로 다른 지점입니다.
문제 8. 조건절에서 컬럼에 함수를 씌우면 흔히 생기는 성능 문제는?
인덱스는 컬럼의 원래 값으로 정렬되어 있습니다. 값을 변형해 비교하면 그 순서를 활용할 수 없습니다.
문제 9. 조건절에서 컬럼 값을 가공하지 않고 조건 쪽을 가공하는 편이 좋은 이유는?
입사일에 함수를 씌우는 대신 비교할 날짜 범위를 계산해 부등호로 쓰면 인덱스를 그대로 탈 수 있습니다.
문제 10. 숫자 컬럼에 문자 값을 조건으로 주었을 때 벌어지는 일로 가장 알맞은 것은?
제품이 알아서 형을 맞추면 편해 보이지만 변환 방향에 따라 인덱스를 못 쓰게 됩니다. 자료형을 맞추어 쓰는 것이 원칙입니다.

묶음 3

문제 1. 급여가 백과 이백과 NULL인 세 행에서 급여가 백오십보다 크다는 조건을 걸면 결과 행 수는?
이백만 조건을 만족합니다. NULL은 비교가 알 수 없음이 되어 빠집니다.
문제 2. 같은 세 행에서 급여가 백오십보다 크지 않다는 조건을 걸면 결과 행 수는?
백만 남습니다. NULL은 부정 조건에서도 여전히 알 수 없음이라 빠집니다. 두 조건의 결과를 합쳐도 전체가 되지 않는 이유입니다.
문제 3. 값이 있는 행과 NULL인 행을 모두 얻으려면 조건을 어떻게 써야 하나요?
NULL은 어떤 비교로도 걸리지 않으므로 IS NULL을 따로 붙여야 합니다.
문제 4. 문자 컬럼에 저장된 값이 십과 구일 때 문자로 비교하면 어느 쪽이 더 크게 판정되나요?
문자 비교는 앞 글자부터 차례로 봅니다. 구가 일보다 뒤에 오므로 문자로는 구가 큽니다. 숫자로 저장했다면 십이 큽니다.
문제 5. 조건절에 부등호를 여러 개 이어 범위를 표현할 때 BETWEEN과 비교해 얻는 이점으로 가장 알맞은 것은?
BETWEEN은 양쪽을 모두 포함합니다. 한쪽만 포함해야 한다면 부등호로 써야 정확합니다.
문제 6. 사원 이름에 안이라는 글자가 들어간 사원을 찾는 패턴으로 알맞은 것은?
앞뒤에 퍼센트를 붙이면 위치에 상관없이 그 글자가 포함된 값을 찾습니다. 다만 앞에 퍼센트가 붙으면 인덱스를 쓰기 어렵습니다.
문제 7. 앞쪽에 퍼센트를 붙인 LIKE 조건이 인덱스를 쓰기 어려운 이유로 가장 알맞은 것은?
앞이 정해져야 정렬된 구간을 좁힐 수 있습니다. 시작이 열려 있으면 전부 훑는 수밖에 없습니다.
문제 8. 여러 조건을 AND로 이을 때 어느 조건을 먼저 쓰는지가 결과에 미치는 영향은?
SQL은 무엇을 원하는지만 적습니다. 순서를 바꾸어도 결과는 같고 실행 계획은 최적화기가 통계를 보고 정합니다.
문제 9. 조건절에 상수 대신 바인드 변수를 쓰는 이점으로 가장 알맞은 것은?
값만 다른 문장을 매번 새로 해석하면 비용이 큽니다. 바인드 변수는 그 비용을 줄이고 값이 문장 구조를 바꾸지 못하게 막습니다.
문제 10. 다음 중 WHERE 절에서 쓸 수 없는 것은?
집계 함수는 그룹이 만들어진 뒤에 계산되므로 WHERE 시점에는 쓸 수 없습니다. 집계 결과로 거르려면 HAVING을 써야 합니다.

이전: 1강 관계형 데이터베이스와 SELECT · 다음: 3강 단일행 함수

모던지 · 궁금하면 모던지 GitHub · 2026-09-10