Условие: сравнение со значением
До сих пор запрос возвращал всю таблицу целиком. Отбирает строки часть WHERE — она пишется после FROM и содержит условие.
SELECT name, city FROM customers WHERE city = 'Москва'
Четыре строки вместо двенадцати:
| name | city |
|---|---|
| Анна Смирнова | Москва |
| Глеб Орлов | Москва |
| Егор Соколов | Москва |
| Лев Никитин | Москва |
Устроено это просто: база берёт строку, подставляет её значения в условие и смотрит, что получилось. Условие проверяется для каждой строки отдельно и ничего не знает о соседних. Подошла — строка в ответе, не подошла — её нет.
Обратите внимание: строка проходит проверку целиком. Условие написано про город, но в ответе остаются все колонки строки — и имя, и возраст, и дата. WHERE не обрезает строку, он её пропускает или не пропускает.
Сравнений шесть, и все они привычные:
| знак | что значит | пример |
|---|---|---|
| = | равно | city = 'Москва' |
| <> | не равно | status <> 'cancelled' |
| > | больше | price > 30000 |
| < | меньше | age < 30 |
| >= | больше или равно | stock >= 10 |
| <= | меньше или равно | rating <= 3 |
Главная мелочь, на которой спотыкаются в первый день: текст пишется в одинарных кавычках, число — без них. city = 'Москва' и price > 30000. Число в кавычках база обычно поймёт, но это везение, а не правило; текст без кавычек она прочитает как имя колонки и честно скажет, что такой колонки нет.
Сравнивать можно не только с числом или текстом, но и с другой колонкой той же строки: WHERE price < stock * 100 — вполне законное условие. И выражение в условии тоже разрешено: WHERE price * 0.9 > 30000. Правила арифметики те же, что в списке колонок, включая целочисленное деление.
И маленькая, но важная деталь: WHERE не меняет данные. Строка, не прошедшая условие, никуда не девается — она просто не попала в этот ответ. Слово «отбор» иногда читается как «отсев», и у новичков возникает опаска: не испорчу ли я таблицу. Не испортите: SELECT только читает.
Полезно и обратное чтение условия. Запрос без WHERE — это запрос с условием «всегда истина»: подходят все строки. Поэтому WHERE 1 = 1 — законный запрос, возвращающий всё, и в сгенерированном коде такое встречается: программе проще дописывать AND … к заведомо истинному условию, чем решать, нужно ли слово WHERE вообще.
И самое частое условие на свете — отбор по ключу: WHERE id = 5. Такой запрос вернёт ровно одну строку или ни одной, потому что ключ уникален. С него начинается почти всякий разбор: «покажи мне этот заказ целиком», «что вообще записано в этой строке». Запомните эту форму — в следующих уроках она будет встречаться постоянно, уже как часть запросов побольше.
AND, OR и скобки
Условий редко бывает одно. Соединяют их двумя словами: AND — «и то, и другое», OR — «хотя бы одно».
SELECT name, age FROM customers WHERE city = 'Москва' AND age > 30
Из четырёх москвичей останутся двое: Глеб (41) и Лев (52). Каждое добавленное через AND условие сужает результат, каждое добавленное через OR — расширяет. Это полезно помнить, когда строк в ответе неожиданно много или неожиданно мало: посмотрите, каким словом соединены условия.
А теперь главное место урока. AND связывает сильнее, чем OR — ровно так же, как умножение сильнее сложения.
-- без скобок: Москва — или Казань и старше тридцати WHERE city = 'Москва' OR city = 'Казань' AND age > 30 -- со скобками: (Москва или Казань) — и старше тридцати WHERE (city = 'Москва' OR city = 'Казань') AND age > 30
Первый запрос вернёт шесть строк: всех четверых москвичей независимо от возраста плюс тех казанцев, кому за тридцать (таких нет вовсе). Второй — двоих. Разница в паре скобок, и никакой ошибки ни в одном из вариантов нет: база выполнит оба, а какой из них вы имели в виду, знаете только вы.
Третье слово — NOT, «не»: WHERE NOT city = 'Москва' оставит всех, кроме москвичей. Пишут его нечасто, потому что почти всегда есть способ короче: то же самое говорит city <> 'Москва'. Полезен NOT там, где отрицается не сравнение, а целое условие в скобках.
И ещё одно, что стоит знать про NOT: он сильнее и AND, и OR. NOT a AND b — это «(не а) и б», а не «не (а и б)». Список старшинства получается короткий: сначала NOT, потом AND, потом OR.
Скобок в условии может быть сколько угодно, и лишние базе не мешают. Единственное, что они делают, — определяют порядок; всё остальное решает читатель. Условие из двух строк со скобками читается, условие из одной строки без них — нет, и через полгода его открывать будете вы же.
Где стоит WHERE и что в нём можно
WHERE стоит в запросе на своём месте — после FROM и до всего остального:
SELECT name, city FROM customers WHERE age > 30
Порядок частей жёсткий: WHERE перед FROM база не примет и скажет об этом при разборе. А выполняется он, как мы помним из первого урока, вторым — сразу после FROM и раньше SELECT.
| часть | когда выполняется | что из этого следует |
|---|---|---|
| FROM | первой | есть все строки таблицы |
| WHERE | второй | лишние строки отсеяны |
| SELECT | почти последним | колонки и псевдонимы появляются только тут |
| ORDER BY | после SELECT | псевдонимом уже можно пользоваться |
Отсюда два правила, которые выглядят произволом, пока не вспомнишь порядок.
Псевдоним из SELECT в WHERE использовать нельзя. На момент проверки условия его ещё не существует:
-- так нельзя: имени skidka ещё нет SELECT price * 0.9 AS skidka FROM products WHERE skidka > 30000 -- так можно: выражение повторяется целиком SELECT price * 0.9 AS skidka FROM products WHERE price * 0.9 > 30000
Итог по группе в WHERE использовать нельзя. «Где покупателей больше пяти», «где сумма заказов выше средней» — всё это про группы строк, а групп на момент WHERE ещё нет: он работает с одиночной строкой и ничего не знает о соседних. Для отбора итогов есть отдельная часть, HAVING, и ей посвящён отдельный урок.
Что в WHERE можно: сравнения, выражения с арифметикой, склейку текста, обращение к любой колонке таблицы — в том числе к той, которой нет в списке SELECT. Последнее удивляет: отбирать по колонке, которую не показываешь, совершенно нормально. SELECT name FROM customers WHERE age > 30 — законный и очень частый запрос.
Из порядка выполнения следует и практический совет: отбор дешевле всего остального. WHERE выполняется сразу после чтения строк, до группировки, до вычисления колонок и до сортировки, — и всё, что он отсёк, дальше не обрабатывается вовсе. Запрос, отбирающий тысячу строк из миллиона, сортирует тысячу, а не миллион.
Отсюда же и частая ошибка новичка: посчитать всё, а потом отобрать нужное «глазами» в готовом ответе. Так работает выгрузка, но не запрос — и на большой таблице разница между «отобрать и посчитать» и «посчитать и отобрать» измеряется не процентами, а порядками.
И то, о чём стоит знать заранее, хотя до соответствующего урока ещё далеко. WHERE есть не только у SELECT. Команды, которые меняют данные, — UPDATE и DELETE — устроены так же: условие решает, какие строки тронуть. Разница в цене ошибки.
Когда отбор молча врёт
Отбор — самое простое место в языке и самое частое место ошибок. Не потому, что он сложный, а потому, что неверное условие не выглядит неверным: запрос выполняется, строки приходят, отчёт собирается. Три ловушки, в которые попадают все.
Первая: строка с пустой клеткой выпадает из любого сравнения. У одной покупательницы возраст не указан. Запрос «старше тридцати» вернёт шесть строк, запрос «тридцать и младше» — пять. Шесть плюс пять — одиннадцать, а покупателей двенадцать.
Пустота — не значение, и сравнить её не с чем: результат сравнения оказывается не «истина» и не «ложь», а тоже пустота, и строка не проходит. Хуже всего это в паре с отрицанием: «все, кто не из Москвы» тихо теряет тех, у кого город вообще не заполнен. Лечится это отдельной проверкой на пустоту, и ей посвящён следующий урок.
Вторая: текст сравнивается буквально. 'Москва' и 'москва' — в большинстве баз разные значения, а 'Москва ' с пробелом на конце не равно 'Москва'. Данные, собранные руками или пришедшие из разных систем, полны таких расхождений, и условие честно их различает.
Третья: сравнение не с тем типом. Число, взятое в кавычки, превращается в текст, а текст сравнивается не как число: '100' > '99' — ложь, потому что по алфавиту единица идёт раньше девятки. С датами то же самое: они хранятся текстом в формате «год-месяц-день» именно ради того, чтобы порядок текста совпадал с порядком времени.
Отдельная разновидность той же ошибки — сравнение с пустой строкой вместо пустоты. WHERE comment = '' и «комментарий не заполнен» — разные вещи: пустая строка это значение, а незаполненная клетка — отсутствие значения, и первое условие вторых не найдёт.
Общее у всех трёх ловушек одно — и это же делает их опасными: запрос выполнился без ошибки. Ни одна из них не даёт красной строчки, не останавливает работу и ничем себя не выдаёт — результат просто оказывается не тем, о котором вас спрашивали. Поэтому проверять отбор приходится не по факту «сработало», а по числу строк. Красная строчка об ошибке — это подарок: она означает, что запрос до данных не дошёл и ничего испортить не успел. Тихий неверный результат дороже любой ошибки разбора.