Полезные конструкции PostgreSQL: CASE, LIKE, DISTINCT, UNION, даты и тесты
В этой лекции — набор конструкций, которые очень часто встречаются в реальных запросах. Для каждого примера: данные → запрос → результат.
CASE
CASE — это условный оператор в SQL. Он позволяет вычислять новое значение по условиям.
Бывает два вида:
- searched CASE:
CASE WHEN условие THEN ...— самый частый - simple CASE:
CASE expr WHEN value THEN ...— когда сравниваем с конкретными значениями
Пример 1 — searched CASE: классификация по сумме
Данные:
| id | customer | total |
|---|---|---|
| 1 | Alice | 45 |
| 2 | Bob | 120 |
| 3 | Carol | 260 |
| 4 | Dave | 120 |
Запрос:
SELECT
id,
customer,
total,
CASE
WHEN total < 100 THEN 'small'
WHEN total < 200 THEN 'medium'
ELSE 'large'
END AS bucket
FROM orders
ORDER BY id;
| id | customer | total | bucket |
|---|---|---|---|
| 1 | Alice | 45 | small |
| 2 | Bob | 120 | medium |
| 3 | Carol | 260 | large |
| 4 | Dave | 120 | medium |
Пример 2 — simple CASE: маппинг статуса
Данные:
| id | status |
|---|---|
| 1 | new |
| 2 | in_progress |
| 3 | done |
| 4 | blocked |
Запрос:
SELECT
id,
status,
CASE status
WHEN 'new' THEN 'Новая'
WHEN 'in_progress' THEN 'В работе'
WHEN 'done' THEN 'Готово'
ELSE 'Другое'
END AS status_ru
FROM tasks
ORDER BY id;
| id | status | status_ru |
|---|---|---|
| 1 | new | Новая |
| 2 | in_progress | В работе |
| 3 | done | Готово |
| 4 | blocked | Другое |
Например, “посчитать сколько done/new” можно через
SUM(CASE WHEN ... THEN 1 ELSE 0 END)
или через FILTER
LIKE и ILIKE
LIKE — сопоставление со строковым шаблоном.
%— любая последовательность символов_— ровно один символLIKEв PostgreSQL обычно чувствителен к региструILIKE— нечувствителен к регистру (PostgreSQL-специфика)
Пример 3 — LIKE: начинается с 'Al'
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Alex |
| 3 | Bob |
| 4 | ALBERT |
Запрос:
SELECT id, name
FROM users
WHERE name LIKE 'Al%'
ORDER BY id;
| id | name |
|---|---|
| 1 | Alice |
| 2 | Alex |
Пример 4 — ILIKE: без учёта регистра
Запрос:
SELECT id, name
FROM users
WHERE name ILIKE 'al%'
ORDER BY id;
| id | name |
|---|---|
| 1 | Alice |
| 2 | Alex |
| 4 | ALBERT |
Пример 5 — LIKE с '_' (ровно один символ)
Данные:
| code |
|---|
| A1 |
| A2 |
| AA |
| A10 |
Запрос:
SELECT code
FROM codes
WHERE code LIKE 'A_'
ORDER BY code;
| code |
|---|
| A1 |
| A2 |
| AA |
LIKE '%text%' обычно не может эффективно использовать btree-индекс.
В реальном PostgreSQL для поиска по подстроке используют trigram-индексы (pg_trgm),
но в учебном тренажёре достаточно понимать базовую механику.
DISTINCT
DISTINCT убирает дубликаты строк в результате.
Можно применять к одной колонке или к набору колонок.
Пример 6 — DISTINCT по одной колонке
Данные:
| user_id | page |
|---|---|
| 1 | / |
| 1 | / |
| 1 | /pricing |
| 2 | / |
| 2 | /pricing |
Запрос:
SELECT DISTINCT page
FROM visits
ORDER BY page;
| page |
|---|
| / |
| /pricing |
Пример 7 — DISTINCT по паре колонок
Запрос:
SELECT DISTINCT user_id, page
FROM visits
ORDER BY user_id, page;
| user_id | page |
|---|---|
| 1 | / |
| 1 | /pricing |
| 2 | / |
| 2 | /pricing |
В PostgreSQL есть мощная фича DISTINCT ON (col), чтобы взять “первую строку” по группе.
Её лучше вынести в отдельную лекцию — там важно понять взаимодействие с ORDER BY.
UNION и UNION ALL
UNION объединяет результаты двух запросов и убирает дубликаты (как DISTINCT).
UNION ALL объединяет результаты без удаления дубликатов (обычно быстрее).
- В обоих SELECT должно быть одинаковое количество колонок
- Типы колонок должны быть совместимы
- Имена колонок берутся из первого SELECT
Пример 8 — UNION (дубликаты убираются)
Данные:
| a@example.com |
| b@example.com |
| c@example.com |
| b@example.com |
| c@example.com |
| d@example.com |
Запрос:
SELECT email FROM emails_marketing
UNION
SELECT email FROM emails_support
ORDER BY email;
| a@example.com |
| b@example.com |
| c@example.com |
| d@example.com |
Пример 9 — UNION ALL (дубликаты остаются)
Запрос:
SELECT email FROM emails_marketing
UNION ALL
SELECT email FROM emails_support
ORDER BY email;
| a@example.com |
| b@example.com |
| b@example.com |
| c@example.com |
| c@example.com |
| d@example.com |
Форматирование дат и времени (PostgreSQL)
В PostgreSQL для форматирования дат/времени чаще всего используют to_char.
Для извлечения частей даты — EXTRACT.
Для округления по периодам — date_trunc.
Пример 10 — to_char: форматирование даты
Данные:
| id | created_at |
|---|---|
| 1 | 2026-01-02 10:15:00 |
| 2 | 2026-01-02 18:05:00 |
| 3 | 2026-02-10 09:00:00 |
Запрос:
SELECT
id,
created_at,
to_char(created_at, 'YYYY-MM-DD') AS day,
to_char(created_at, 'DD Mon YYYY, HH24:MI') AS pretty
FROM events
ORDER BY id;
| id | created_at | day | pretty |
|---|---|---|---|
| 1 | 2026-01-02 10:15:00 | 2026-01-02 | 02 Jan 2026, 10:15 |
| 2 | 2026-01-02 18:05:00 | 2026-01-02 | 02 Jan 2026, 18:05 |
| 3 | 2026-02-10 09:00:00 | 2026-02-10 | 10 Feb 2026, 09:00 |
Пример 11 — EXTRACT: части даты (год/месяц/день)
Запрос:
SELECT
id,
created_at,
EXTRACT(YEAR FROM created_at) AS year,
EXTRACT(MONTH FROM created_at) AS month,
EXTRACT(DAY FROM created_at) AS day
FROM events
ORDER BY id;
| id | created_at | year | month | day |
|---|---|---|---|---|
| 1 | 2026-01-02 10:15:00 | 2026 | 1 | 2 |
| 2 | 2026-01-02 18:05:00 | 2026 | 1 | 2 |
| 3 | 2026-02-10 09:00:00 | 2026 | 2 | 10 |
Пример 12 — date_trunc: группировка по месяцу
Запрос:
SELECT
date_trunc('month', created_at) AS month_start,
COUNT(*) AS events_count
FROM events
GROUP BY month_start
ORDER BY month_start;
| month_start | events_count |
|---|---|
| 2026-01-01 00:00:00 | 2 |
| 2026-02-01 00:00:00 | 1 |
Тесты в PostgreSQL: IS NULL, EXISTS, IN, BETWEEN
“Тесты” — это проверки и условия, которые часто используются в WHERE:
IS NULL, EXISTS, IN, BETWEEN.
Пример 13 — IS NULL / IS NOT NULL
Данные:
| id | |
|---|---|
| 1 | a@example.com |
| 2 | NULL |
| 3 | c@example.com |
Запрос (пользователи без email):
SELECT id, email
FROM users
WHERE email IS NULL;
| id | |
|---|---|
| 2 | NULL |
Пример 14 — IN (список значений)
Данные:
| id | name | category |
|---|---|---|
| 1 | Keyboard | electronics |
| 2 | Chair | furniture |
| 3 | Mouse | electronics |
| 4 | Cup | kitchen |
Запрос (две категории):
SELECT id, name, category
FROM products
WHERE category IN ('electronics', 'kitchen')
ORDER BY id;
| id | name | category |
|---|---|---|
| 1 | Keyboard | electronics |
| 3 | Mouse | electronics |
| 4 | Cup | kitchen |
Пример 15 — BETWEEN (включая границы)
Данные:
| id | total |
|---|---|
| 1 | 45 |
| 2 | 120 |
| 3 | 260 |
| 4 | 200 |
Запрос (от 100 до 200 включительно):
SELECT id, total
FROM orders
WHERE total BETWEEN 100 AND 200
ORDER BY id;
| id | total |
|---|---|
| 2 | 120 |
| 4 | 200 |
Пример 16 — EXISTS (проверка существования)
Данные:
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
| id | user_id |
|---|---|
| 10 | 1 |
| 11 | 1 |
| 12 | 2 |
Запрос (пользователи, у которых есть хотя бы один заказ):
SELECT u.id, u.name
FROM users u
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
)
ORDER BY u.id;
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
Итого
CASE— условная логика (классификация, маппинг).LIKE/ILIKE— поиск по шаблону,ILIKE— без учёта регистра (Postgres).DISTINCT— убрать дубликаты строк.UNIONубирает дубликаты,UNION ALL— нет (обычно быстрее).to_char,EXTRACT,date_trunc— работа с датой/временем.IS NULL,IN,BETWEEN,EXISTS— базовые “тесты” для фильтрации.