Раздел 35 · SQLite с нуля

WHERE и операторы сравнения

junior~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке ты выбирал столбцы: что показать на витрине. Но запрос по-прежнему возвращал все девять напитков. Гость спрашивает не «покажи всё меню», а «что у вас из кофе дешевле двухсот». Этот урок про то, как отобрать строки.

Сито для строк

Представь бариста, который перебирает карточки напитков. На каждую он задаёт себе один и тот же вопрос: «это кофе?». Если да, карточка идёт в стопку для гостя, если нет, откладывается. Ответ на вопрос только «да» или «нет».

WHERE и есть такой вопрос. Ты пишешь условие, база проверяет его для каждой строки и оставляет только те, где ответ «да». Порядок слов в запросе такой: что показать, откуда, какие строки:

sqlite> SELECT name, price FROM drinks WHERE category = 'кофе';
╭───────────┬───────╮
│   name    │ price │
╞═══════════╪═══════╡
│ Эспрессо  │   120 │
│ Американо │   150 │
│ Капучино  │   190 │
│ Латте     │   210 │
│ Раф       │   240 │
╰───────────┴───────╯

Строка 'кофе' в одинарных кавычках, как любое текстовое значение. В условии участвует столбец category, хотя в ответе его нет: фильтровать можно по чему угодно, а показывать что-то другое. В теории баз такую операцию называют селекцией, о ней теоретический урок.

Операторы сравнения

ОператорЗначитПример
=равноcategory = 'чай'
<> или !=не равноcategory <> 'кофе'
<, <=меньше, меньше или равноprice <= 150
>, >=больше, больше или равноprice > 200

В SQLite можно написать и ==, но это местная вольность. В других базах работает только одинарное =, привыкай к нему.

sqlite> SELECT name, price FROM drinks WHERE price <= 150;
╭────────────────┬───────╮
│      name      │ price │
╞════════════════╪═══════╡
│ Эспрессо       │   120 │
│ Американо      │   150 │
│ Чай с чабрецом │   130 │
╰────────────────┴───────╯

Американо стоит ровно 150, и <= его пропустил. С < он бы выпал. Граница это частое место ошибок, проверяй её глазами.

BETWEEN и IN: короче записать

Когда нужен диапазон, вместо price >= 150 AND price <= 200 пишут BETWEEN. Обе границы входят в диапазон:

sqlite> SELECT name, price FROM drinks WHERE price BETWEEN 150 AND 200;
╭───────────┬───────╮
│   name    │ price │
╞═══════════╪═══════╡
│ Американо │   150 │
│ Капучино  │   190 │
│ Какао     │   180 │
│ Тархун    │   200 │
╰───────────┴───────╯

Когда нужно одно из нескольких значений, помогает IN со списком в скобках. Это то же, что несколько сравнений через «или»:

sqlite> SELECT name, category FROM drinks WHERE category IN ('чай', 'холодное');
╭────────────────┬──────────╮
│      name      │ category │
╞════════════════╪══════════╡
│ Чай с чабрецом │ чай      │
│ Матча-латте    │ чай      │
│ Тархун         │ холодное │
╰────────────────┴──────────╯

LIKE: поиск по шаблону

Иногда точного значения нет, а есть кусок. LIKE сравнивает строку с шаблоном: знак % заменяет любое число символов, знак _ ровно один символ.

sqlite> SELECT name FROM drinks WHERE name LIKE '%латте%';
╭─────────────╮
│    name     │
╞═════════════╡
│ Матча-латте │
╰─────────────╯

А где обычный Латте? Он пишется с заглавной буквы, а шаблон со строчной. Документация обещает, что LIKE не различает регистр, но мелким шрифтом уточняет: только для латинских букв. Проверим напрямую, в списке SELECT сравнение даёт 1 (истина) или 0 (ложь):

sqlite> SELECT 'Latte' LIKE 'latte', 'Латте' LIKE 'латте', 'Латте' = 'Латте';
╭──────────────────────┬──────────────────────┬───────────────────╮
│ 'Latte' LIKE 'latte' │ 'Латте' LIKE 'латте' │ 'Латте' = 'Латте' │
╞══════════════════════╪══════════════════════╪═══════════════════╡
│                    1 │                    0 │                 1 │
╰──────────────────────┴──────────────────────┴───────────────────╯

Для латиницы регистр не важен, для кириллицы важен. SQLite маленькая и не таскает с собой таблицы регистров всех алфавитов мира. Для русских слов надёжнее пару шаблонов: name LIKE '%латте%' OR name LIKE '%Латте%'.

AND, OR, NOT и скобки

Условия соединяют логическими операторами. AND требует, чтобы выполнялись обе части:

sqlite> SELECT name, price FROM drinks WHERE category = 'кофе' AND price < 200;
╭───────────┬───────╮
│   name    │ price │
╞═══════════╪═══════╡
│ Эспрессо  │   120 │
│ Американо │   150 │
│ Капучино  │   190 │
╰───────────┴───────╯

OR достаточно одной части, NOT переворачивает ответ: NOT category = 'кофе' то же, что category <> 'кофе'.

Теперь ловушка. Хотим чай или кофе, но только дороже 200 рублей:

sqlite> SELECT name, category, price FROM drinks WHERE category = 'чай' OR category = 'кофе' AND price > 200;
╭────────────────┬──────────┬───────╮
│      name      │ category │ price │
╞════════════════╪══════════╪═══════╡
│ Латте          │ кофе     │   210 │
│ Раф            │ кофе     │   240 │
│ Чай с чабрецом │ чай      │   130 │
│ Матча-латте    │ чай      │ NULL  │
╰────────────────┴──────────┴───────╯

Чай за 130 рублей в ответе? AND связывает сильнее, чем OR, как умножение сильнее сложения. База прочитала условие так: «чай, или (кофе дороже 200)». Скобки возвращают задуманный смысл:

sqlite> SELECT name, category, price FROM drinks WHERE (category = 'чай' OR category = 'кофе') AND price > 200;
╭───────┬──────────┬───────╮
│ name  │ category │ price │
╞═══════╪══════════╪═══════╡
│ Латте │ кофе     │   210 │
│ Раф   │ кофе     │   240 │
╰───────┴──────────┴───────╯

Правило: если в условии встречаются и AND, и OR, ставь скобки, даже когда уверен в порядке. Читателю так тоже проще.

Текст против чисел

В столбце volume лежат числа, а у Какао строка '300 мл'. Что вернёт фильтр по объёму?

sqlite> SELECT name, volume FROM drinks WHERE volume > 300;
╭────────────────┬────────╮
│      name      │ volume │
╞════════════════╪════════╡
│ Латте          │    400 │
│ Раф            │    350 │
│ Какао          │ 300 мл │
│ Чай с чабрецом │    500 │
│ Матча-латте    │    350 │
╰────────────────┴────────╯

Какао попало в ответ. Когда SQLite сравнивает значения разных классов, у неё строгий порядок: любое число меньше любого текста. Поэтому '300 мл' больше трёхсот, больше миллиона и вообще больше всех чисел. Внутри текста сравнение идёт по символам, как в словаре:

sqlite> SELECT 10 < 9, '10' < '9', 10 < '9';
╭────────┬────────────┬──────────╮
│ 10 < 9 │ '10' < '9' │ 10 < '9' │
╞════════╪════════════╪══════════╡
│      0 │          1 │        1 │
╰────────┴────────────┴──────────╯

Как числа 10 больше 9, а как строки '10' меньше '9', потому что первый символ 1 идёт раньше 9. Это прямое следствие динамической типизации: если в столбце смешаны классы, фильтр ведёт себя неожиданно. Лечится это не хитрым условием, а чистыми данными.

Попробуй сам

Песочница отбирает кофе в диапазоне цен и ищет напитки по куску названия. Поменяй границы, добавь NOT, убери скобки и посмотри, как меняется ответ.

Шаблон 'ч%' найдёт Чай с чабрецом? Подумай до запуска, вспомни про регистр.

Проверь себя

Задание

Дальше

Отобранные строки пока идут в том порядке, в каком база их нашла. В следующем уроке сортируем ответ, режем его на страницы и убираем повторы.

домашка

Домашка