MariaDB / Query / WHERE

MariaDB의 WHERE는 조건식이 참인 행만 남기는 절입니다. 조건식이 0 또는 NULL이면 그 행을 제외하며, 숫자로 평가되는 조건에서는 0이 아닌 값이 참으로 취급됩니다. 비교 조건을 SELECT 열로 먼저 확인한 뒤 WHERE에 적용하면 포함·제외 이유를 이해하기 쉽습니다.

조건식의 값과 필터 결과 비교

실습용 learning 데이터베이스에서 다음 전체 준비 SQL을 실행합니다. CREATE·INSERT·SELECT 권한이 필요하며 where_demo가 이미 있다면 다른 실습 이름을 사용합니다. 실제 업무 자료나 별도의 대용량 샘플 데이터베이스 없이 NULL·날짜 경계·문자열을 비교할 수 있습니다.

USE learning;
CREATE TABLE where_demo (
    id INT PRIMARY KEY,
    category CHAR(1) NOT NULL,
    name VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
    price INT NULL,
    active TINYINT NOT NULL,
    created_at DATETIME NOT NULL
) ENGINE=InnoDB;
INSERT INTO where_demo VALUES
    (1,'A','Alpha',10,1,'2026-01-01 00:00:00'),
    (2,'B','Beta_1',20,0,'2026-01-31 12:00:00'),
    (3,'A','Gamma',NULL,0,'2026-02-01 00:00:00'),
    (4,'B','Delta',30,1,'2026-01-15 09:00:00'),
    (5,'C','100% item',0,0,'2026-01-31 23:59:59'),
    (6,'C','Alpha2',NULL,1,'2026-01-10 08:00:00');
SELECT id, price, price >= 20 AS meets_condition
FROM where_demo ORDER BY id;
SELECT id, price FROM where_demo
WHERE price >= 20 ORDER BY id;
SELECT 2 AS nonzero_value WHERE 2;

첫 조회의 meets_condition은 id 순서대로 0·1·NULL·1·0·NULL입니다. 같은 조건을 WHERE에 넣은 둘째 조회에는 id=2·4만 남습니다. 마지막 SELECT의 결과는 2로, “WHERE 결과가 정확히 숫자 1일 때만 남는다”라고 이해하면 안 된다는 예입니다. 실무 조건은 WHERE price >= 20처럼 비교 목적을 명확히 작성하는 편이 좋습니다.

기본 작성 순서는 SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT입니다. 모두 쓸 필요는 없으며 필터 뒤의 정렬·행 제한은 필요한 경우에만 추가합니다. MariaDB 조회 안내에서 각 절의 용도를 확인할 수 있습니다. LIMIT만 쓰고 ORDER BY를 빼면 어느 행이 먼저 나오는지 보장하지 않습니다.

AND·OR를 섞을 때는 괄호로 의도 표시

SELECT id FROM where_demo
WHERE category = 'A' OR category = 'B' AND active = 1
ORDER BY id;
SELECT id FROM where_demo
WHERE (category = 'A' OR category = 'B') AND active = 1
ORDER BY id;

첫 결과는 1·3·4, 둘째는 1·4입니다. AND가 OR보다 우선하므로 첫 조건은 A이거나, B이면서 활성인 행을 고릅니다. 둘째는 A 또는 B 중에서 활성인 행만 고릅니다. 연산자 우선순위를 기억하더라도 괄호를 쓰면 검토자가 의도를 확인하기 쉽습니다. 문자열 연결과 혼동할 수 있는 ||보다 논리 OR를 명시하는 편이 SQL 모드 차이를 줄입니다.

IN·BETWEEN·LIKE의 범위

SELECT id FROM where_demo WHERE price BETWEEN 10 AND 20 ORDER BY id;
SELECT id FROM where_demo
WHERE category IN ('A','C') AND price IS NOT NULL ORDER BY id;
SELECT id FROM where_demo WHERE name LIKE 'Alpha%' ORDER BY id;
SELECT id FROM where_demo WHERE name LIKE '%!_%' ESCAPE '!' ORDER BY id;
SELECT id FROM where_demo WHERE name LIKE '%!%%' ESCAPE '!' ORDER BY id;

결과는 조회별로 (1,2), (1,5), (1,6), (2), (5)입니다. BETWEEN은 양 끝 10과 20을 포함합니다. IN은 지정 값 중 일치를 검사하고 LIKE의 %는 길이 0 이상의 문자, _는 한 문자를 나타냅니다. 문자 자체인 밑줄·퍼센트를 찾으려면 위처럼 ESCAPE 문자를 명시할 수 있습니다. 이 예제의 name은 대소문자를 구별하는 콜레이션이며, 실제 비교 규칙은 열과 식의 콜레이션을 확인합니다. LIKE 문서에서 패턴 규칙을 확인하세요.

NULL과 NOT IN의 주의점

SELECT id FROM where_demo WHERE price IS NULL ORDER BY id;
SELECT id FROM where_demo WHERE price = NULL ORDER BY id;
SELECT id FROM where_demo WHERE price NOT IN (10,20) ORDER BY id;
SELECT id FROM where_demo WHERE price NOT IN (10,NULL) ORDER BY id;

IS NULL은 id=3·6을 반환하지만 = NULL은 아무 행도 반환하지 않습니다. NOT IN(10,20)은 id=4·5이며 NULL 가격은 포함하지 않습니다. NOT IN(10,NULL)은 모든 행에서 참이 되지 않아 결과가 0행입니다. 알려지지 않은 NULL과의 비교를 단순한 같음·다름으로 처리할 수 없기 때문입니다. 누락 값도 포함해야 한다면 price IS NULL OR ...처럼 별도 의도를 표현하고, 서브쿼리의 제외 목록에 NULL이 있을 수 있는 경우도 검토합니다.

날짜 경계와 집계 전후 필터

SELECT id FROM where_demo
WHERE created_at >= '2026-01-01 00:00:00'
  AND created_at < '2026-02-01 00:00:00'
ORDER BY id;

SELECT category, COUNT(*) AS known_price_rows, SUM(price) AS total_price
FROM where_demo
WHERE price IS NOT NULL
GROUP BY category
HAVING COUNT(*) >= 2
ORDER BY category;

날짜 조회에는 id=1·2·4·5·6이 나옵니다. 1월 말의 시간까지 포함하려면 다음 달 시작 미만인 반개방 범위를 쓰면 경계를 명확히 표현할 수 있습니다. DATETIME에서 종료일을 2026-01-31만 쓰면 자정으로 비교되어 그날 낮·밤 값이 빠질 수 있습니다. BETWEEN의 날짜·시간 예제도 이 차이를 설명합니다. TIMESTAMP 등 시간대 영향을 받는 타입은 접속·업무 시간대도 함께 맞춥니다.

집계 조회는 B·2·50 한 행입니다. WHERE는 가격이 있는 원본 행을 먼저 고르고, HAVING은 묶은 결과 중 두 행 이상인 분류를 남깁니다. 집계 COUNT(*)를 그대로 WHERE에 넣거나 SELECT에서 만든 별칭을 일반 열처럼 WHERE에서 바로 쓰는 오류를 피하세요. 복잡한 계산 결과를 다시 필터링하려면 파생 테이블이나 적절한 집계 절을 검토합니다.

변경 쿼리에도 같은 조건을 먼저 검증

UPDATE·DELETE에도 WHERE를 쓰며, 생략하면 모든 대상 행을 바꿀 수 있습니다. 다음은 실습 InnoDB 테이블에서 id=2만 바꿨다가 되돌리는 예입니다. 같은 접속에서 끝까지 실행하고, 실제 변경 전에는 SELECT로 조건과 행 수를 확인합니다.

SELECT id, active FROM where_demo WHERE id = 2;
START TRANSACTION;
UPDATE where_demo SET active = 1 WHERE id = 2;
SELECT id, active FROM where_demo WHERE id = 2;
ROLLBACK;
SELECT id, active FROM where_demo WHERE id = 2;

출력은 순서대로 (2,0), (2,1), (2,0)입니다. 원본이 계속 변경되는 환경에서는 사전 SELECT와 실제 변경 사이의 경쟁도 고려해야 합니다. 외부 입력 값은 드라이버의 준비된 문장으로 바인딩하고, 열 이름·정렬 방향 등 구조적인 SQL 조각은 허용 목록으로 제한합니다. 필터가 있다고 항상 빠른 것은 아니므로 큰 테이블은 실제 실행 계획과 선택도를 확인하되, 특정 함수나 LIKE 패턴이 모든 버전에서 무조건 인덱스를 쓴다거나 못 쓴다고 단정하지 않습니다.