WHERE — это часть SQL-запроса к базе данных, которая задает условие отбора строк: в результат или под изменение попадают только те строки таблицы, для которых условие истинно. WHERE пишут в SELECT, UPDATE и DELETE после имени таблицы.
Содержание
Строго говоря, WHERE — это предложение (clause) запроса, а операторами называют то, что стоит внутри условия: =, >, AND, IN, LIKE. В обиходе все вместе зовут «оператор WHERE», так будем говорить и здесь. Ниже — рабочий пример, разбор каждого вида условий и три места, где чаще всего ошибаются: приоритет AND и OR, сравнение с NULL и WHERE без условия в UPDATE/DELETE.
Примеры проверены 30.09.2026 в SQLite 3.51.0 и PostgreSQL 18.6, отличия диалектов — еще и в MySQL 8.4. Вывод показан в формате консоли sqlite3; где результат в двух СУБД различается, это сказано отдельно.
Минимальный пример: таблица и первый запрос с WHERE
Учебная таблица заказов. Скрипт целиком выполняется в sqlite3 и в psql.
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer VARCHAR(50) NOT NULL,
city VARCHAR(50) NOT NULL,
amount INTEGER NOT NULL,
status VARCHAR(20) NOT NULL CHECK (status IN ('new', 'paid', 'cancelled')),
promo VARCHAR(20)
);
INSERT INTO orders (id, customer, city, amount, status, promo) VALUES
(1, 'Anna', 'Moscow', 1200, 'paid', 'SALE10'),
(2, 'Boris', 'Kazan', 800, 'new', NULL),
(3, 'Vera', 'Moscow', 4500, 'paid', NULL),
(4, 'Gleb', 'Perm', 300, 'cancelled', 'SALE_5'),
(5, 'Darya', 'Kazan', 2600, 'paid', 'WELCOME'),
(6, 'Egor', 'Moscow', 950, 'new', 'SALE10');
SELECT id, customer, amount FROM orders WHERE amount > 1000;
Запрос вернет три заказа дороже 1000:
1|Anna|1200
3|Vera|4500
5|Darya|2600
СУБД проверяет условие для каждой строки. У условия три возможных результата: истина, ложь и «неизвестно» (так бывает при сравнении с NULL). В результат проходят только строки с истиной — это правило объясняет почти все неожиданности ниже.
Операторы сравнения
| Оператор | Значение | Пример |
|---|---|---|
= |
равно | status = 'paid' |
<> |
не равно (!= понимают распространенные СУБД, но в стандарте именно <>) |
city <> 'Perm' |
> и < |
больше, меньше | amount > 1000 |
>= и <= |
не меньше, не больше | amount <= 800 |
Строки пишут в одинарных кавычках. Двойные кавычки в стандарте SQL и в PostgreSQL обозначают имя столбца или таблицы, а не текст, поэтому city = "Moscow" в PostgreSQL даст ошибку column "Moscow" does not exist. SQLite и MySQL в настройках по умолчанию такую запись молча принимают за строку, но переносимым запрос от этого не становится.
AND, OR, NOT и приоритет
AND требует истинности обоих условий, OR — хотя бы одного, NOT переворачивает результат. Приоритет такой: сначала NOT, затем AND, в конце OR. Отсюда типовая ошибка.
Задача: оплаченные заказы из Москвы или Казани. Неверный запрос:
SELECT id, city, status, amount FROM orders
WHERE city = 'Moscow' OR city = 'Kazan' AND status = 'paid';
1|Moscow|paid|1200
3|Moscow|paid|4500
5|Kazan|paid|2600
6|Moscow|new|950
В результат попал неоплаченный заказ 6. AND выполнился раньше, и условие прочиталось как «Москва с любым статусом ИЛИ оплаченная Казань». Исправление — скобки:
SELECT id, city, status, amount FROM orders
WHERE (city = 'Moscow' OR city = 'Kazan') AND status = 'paid';
1|Moscow|paid|1200
3|Moscow|paid|4500
5|Kazan|paid|2600
Практическое правило: если в одном условии встречаются и AND, и OR, ставьте скобки всегда, даже когда приоритет и так дает нужный результат. Запрос читают чаще, чем пишут.
IN, BETWEEN и LIKE
Эти три оператора сокращают запись типовых условий.
| Оператор | Что проверяет | Чему равносилен |
|---|---|---|
IN (...) |
значение есть в списке | цепочке = через OR |
BETWEEN a AND b |
значение в диапазоне, обе границы включены | >= a AND <= b |
LIKE 'шаблон' |
строка подходит под шаблон | — |
SELECT id, city FROM orders WHERE city IN ('Kazan', 'Perm');
SELECT id, amount FROM orders WHERE amount BETWEEN 800 AND 1200;
Первый запрос вернет заказы 2, 4 и 5. Второй — заказы 1, 2 и 6: суммы 800 и 1200 входят, потому что границы включены. Для дат со временем это ловушка: BETWEEN '2026-09-01' AND '2026-09-30' отрежет почти все 30 сентября, надежнее полуинтервал >= '2026-09-01' AND < '2026-10-01'.
В шаблоне LIKE два спецсимвола: % — любая последовательность символов, включая пустую, _ — ровно один любой символ.
SELECT id, promo FROM orders WHERE promo LIKE 'SALE%';
1|SALE10
4|SALE_5
6|SALE10
Чтобы найти сам знак подчеркивания или процента, его экранируют символом из ESCAPE:
SELECT id, promo FROM orders WHERE promo LIKE 'SALE!_%' ESCAPE '!';
4|SALE_5
Без экранирования шаблон 'SALE_%' вернул бы все три промокода: _ совпал бы с цифрой 1 в SALE10.
Регистр в LIKE зависит от СУБД. Шаблон 'sale%' в SQLite находит те же три строки (для латиницы ASCII LIKE там по умолчанию нечувствителен к регистру), в PostgreSQL не находит ни одной (там для этого есть ILIKE), а в MySQL результат определяется сортировкой (collation) столбца: с сортировкой по умолчанию в MySQL 8.4 (utf8mb4_0900_ai_ci) регистр не учитывается, и строк снова три.
NULL и трехзначная логика
NULL означает «значение неизвестно», это не пустая строка и не ноль. Любое сравнение с ним через =, <>, > дает «неизвестно», а не истину, поэтому строка в результат не проходит.
Задача: найти заказы без промокода. Неверно:
SELECT id FROM orders WHERE promo = NULL;
Запрос выполнится без ошибки и вернет ноль строк — даже для заказов 2 и 3, где промокода нет. Правильно:
SELECT id, customer FROM orders WHERE promo IS NULL;
2|Boris
3|Vera
Вторая ловушка — отрицание. Запрос «все, кроме SALE10» теряет строки с NULL:
SELECT id, promo FROM orders WHERE promo <> 'SALE10';
4|SALE_5
5|WELCOME
Заказов 2 и 3 нет, хотя их промокод точно не SALE10. Если они нужны, условие дописывают: promo <> 'SALE10' OR promo IS NULL — тогда вернутся четыре строки.
Третья — NOT IN со списком, где есть NULL. Запрос WHERE city NOT IN ('Moscow', NULL) не вернет ни одной строки: для Казани сравнение с NULL дает «неизвестно», и все условие истинным не становится. Чаще всего так случается, когда список приходит из подзапроса по столбцу с пропусками; в этом случае берут NOT EXISTS или отфильтровывают NULL в подзапросе.
WHERE и HAVING: в чем разница
Оба задают условие, но работают на разных этапах запроса.
| WHERE | HAVING | |
|---|---|---|
| Что фильтрует | отдельные строки | группы после GROUP BY |
| Когда выполняется | до группировки | после группировки |
Агрегатные функции (SUM, COUNT) |
нельзя | можно |
SELECT city, COUNT(*) AS cnt, SUM(amount) AS total FROM orders
WHERE status = 'paid'
GROUP BY city
HAVING SUM(amount) > 3000
ORDER BY city;
Moscow|2|5700
WHERE сначала оставил только оплаченные заказы, затем строки сгруппировались по городам, и HAVING убрал Казань с суммой 2600. Если написать агрегат в WHERE, запрос не выполнится. PostgreSQL отвечает так:
ERROR: aggregate functions are not allowed in WHERE
SQLite на тот же запрос пишет misuse of aggregate: SUM(). Условие, которому агрегат не нужен, лучше ставить в WHERE: СУБД отбросит лишние строки до группировки.
WHERE в UPDATE и DELETE
В командах изменения WHERE определяет, какие строки будут затронуты. Без него UPDATE перепишет, а DELETE удалит все строки таблицы, и в настройках по умолчанию СУБД об этом не предупредит (страховка вроде режима --safe-updates в клиенте mysql включается отдельно). Поэтому порядок работы такой.
Шаг 1. Выполнить SELECT с тем же условием и посмотреть, что попадет под изменение:
SELECT id, status FROM orders WHERE status = 'new' AND amount < 900;
2|new
Шаг 2. Выполнить изменение в транзакции, сверить число затронутых строк и только потом фиксировать:
BEGIN;
UPDATE orders SET status = 'cancelled' WHERE status = 'new' AND amount < 900;
SELECT id, status FROM orders WHERE id = 2;
COMMIT;
psql после UPDATE печатает UPDATE 1 — одна строка, как и ожидалось; проверочный запрос показывает 2|cancelled. Если число строк не совпало с ожидаемым, вместо COMMIT пишут ROLLBACK.
Что произойдет без WHERE, видно в транзакции с откатом:
BEGIN;
DELETE FROM orders;
SELECT COUNT(*) FROM orders;
ROLLBACK;
SELECT COUNT(*) FROM orders;
Первый подсчет вернет 0, второй после отката — 6. Откат спасает только до COMMIT и только если транзакция была открыта явно: в режиме автофиксации, который включен по умолчанию в большинстве клиентов, удаление сохранится сразу. На рабочей базе к этому добавляют резервную копию и ограниченные права учетной записи.
И еще одно требование безопасности: значения, пришедшие от пользователя, в условие WHERE передают параметрами запроса (WHERE id = ? или $1), а не склейкой строк. Склейка открывает путь SQL-инъекции, когда вместо значения в запрос попадает чужое условие вроде OR 1=1.
Выводы
- WHERE задает условие отбора строк в
SELECT,UPDATEиDELETE; проходят только строки, для которых условие истинно. ANDвыполняется раньшеOR, поэтому смешанные условия пишут со скобками.- Сравнение с
NULLчерез=и<>не дает истину: пропуски ищут черезIS NULL, а при отрицании иNOT INо них помнят отдельно. - WHERE фильтрует строки до группировки, HAVING — группы после нее; условие с агрегатом (
SUM,COUNT) пишут в HAVING, в WHERE оно вызывает ошибку. - Перед
UPDATEиDELETEусловие проверяют тем жеSELECT, а само изменение делают в транзакции.
Где применяется / связь с практикой
Условия отбора — основа почти любого запроса: отчет аналитика, выборка для страницы сайта, чистка данных, миграция. От того, как написан WHERE, зависит и скорость: по условию СУБД решает, можно ли воспользоваться индексом или придется читать таблицу целиком.
Освойте тему на практике
Системно эти темы — от условий и соединений таблиц до индексов и планов запросов — разбирают на курсе «SQL для разработчиков и аналитиков». Познакомиться с форматом можно на открытых уроках Otus.
Смежные темы: команда SELECT в SQL, UPDATE в SQL.
FAQ
Можно ли использовать в WHERE псевдоним столбца из SELECT?
По стандарту нет: WHERE выполняется раньше, чем вычисляется список SELECT. PostgreSQL на SELECT amount * 2 AS dbl ... WHERE dbl > 5000 отвечает column "dbl" does not exist. SQLite такой запрос выполняет, но это его расширение, и переносимым оно не является — повторите выражение в условии или вынесите его в подзапрос.
Можно ли писать WHERE после GROUP BY или без FROM?
Порядок частей запроса фиксирован: SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY. WHERE после GROUP BY — синтаксическая ошибка, для условий на группы предназначен HAVING. Без FROM запрос вида SELECT 1 WHERE 1 = 1 в SQLite, PostgreSQL и MySQL 8.4 выполняется, но это расширение этих СУБД, а не общее правило.
Влияет ли порядок условий в WHERE на скорость?
Обычно нет: оптимизатор сам выбирает порядок проверки. Сильнее влияет то, обернут ли столбец в функцию: условие вида UPPER(city) = 'MOSCOW', как правило, не дает использовать обычный индекс по city. Точный ответ для своей базы показывает план запроса (EXPLAIN).



