lesson ── postgres-labs ── ~22 мин ── 6 шагов
Худшие ошибки в SQL не падают, а молча врут. Ты воспроизведёшь четыре классические ловушки на маленькой таблице и перепишешь главную из них на корректный вариант. Для каждой сначала предскажи результат, потом проверь
интерактивный sandbox
Поднимется контейнер postgreslab/postgres-base с PostgreSQL 17 и psql. В браузере откроется терминал, база lab уже настроена. Каждый шаг проверяется автоматически. Сеть air-gapped, наружу контейнер не ходит.
stack ── PostgreSQL 17 · psql · 1 GB RAM · air-gapped · самоуничтожается через 45 мин простоя
Предскажи вывод, потом выполни:
SELECT 5 / 2;
Ожидал 2.5? В SQL int / int даёт int - дробь отброшена ещё до
всякого округления.
✓ 5/2 = 2, а не 2.5. Дробь отброшена.
Приведи хотя бы один операнд к numeric до деления:
SELECT 5::numeric / 2;
Предскажи: теперь будет 2.5 (с длинным хвостом нулей - это точность numeric).
✓ Приведение к numeric вернуло дробный результат.
Создай таблицу, где часть значений NULL:
CREATE TABLE ap_demo AS
SELECT g AS id, CASE WHEN g <= 3 THEN 'x' END AS note
FROM generate_series(1, 6) AS g;
Предскажи вывод SELECT COUNT(*), COUNT(note) FROM ap_demo;. COUNT(*)
считает строки, COUNT(note) - только где note НЕ NULL.
✓ 6 строк, но только 3 не-NULL note. Это разные числа.
Предскажи результат, потом проверь:
SELECT ('a'::char(3) = 'a ')::text;char(3) дополняет 'a' пробелами до длины 3, а при сравнении хвостовые пробелы усекаются - поэтому равенство истинно. Сюрприз на ровном месте.
✓ Сравнение истинно - char(n) ведёт себя неочевидно. Бери text/varchar.
Добавь в таблицу строку с NULL в id:
INSERT INTO ap_demo (id, note) VALUES (NULL, 'z');
Теперь предскажи: сколько вернёт запрос ниже? Кажется, что 4 (числа 7,8,9,10 не входят в id 1..6). Но один NULL в подзапросе всё ломает:
SELECT count(*) FROM generate_series(1, 10) AS g
WHERE g NOT IN (SELECT id FROM ap_demo);
✓ Из-за NULL результат 0, а не 4. NOT IN солгал.
Замени NOT IN на устойчивый NOT EXISTS:
SELECT count(*) FROM generate_series(1, 10) AS g
WHERE NOT EXISTS (SELECT 1 FROM ap_demo a WHERE a.id = g);
Предскажи: теперь честные 4 (числа 7,8,9,10). NOT EXISTS не страдает
от NULL. Когда закончишь, можешь убрать таблицу: DROP TABLE ap_demo;.
✓ NOT EXISTS вернул верные 4. Вот так и пишут «нет среди».
Анти-паттерны опасны тем, что не падают, а возвращают неверный результат. NOT IN с NULL даёт пусто - бери NOT EXISTS. int/int режет дробь - приводи к numeric. COUNT(col) считает не-NULL - для строк COUNT(*). char(n) усекает пробелы при сравнении - бери text.
команды
SELECT 5::numeric / 2;дробное деление вместо целочисленного 5/2SELECT COUNT(*), COUNT(col) FROM t;все строки против не-NULL значений... WHERE NOT EXISTS (SELECT 1 FROM t WHERE ...);устойчивая замена NOT INконцепции