QA Interview Hub · База знань · Тести
SQL
SELECT і фільтрація
WHERE, порядок виконання, сортування
Як побудований запит SELECT?
Мінімальний запит — SELECT і FROM. Решта частин необов'язкові, але пишуться завжди в такому порядку:
SELECT email, country -- які стовпці
FROM users -- з якої таблиці
WHERE country = 'PL' -- фільтр рядків
GROUP BY ... -- групування
HAVING ... -- фільтр груп
ORDER BY created_at DESC -- сортування
LIMIT 10; -- обмеження кількості
-- SELECT * повертає всі стовпці: зручно для дослідження,
-- але в автотестах і звітах краще перелічувати стовпці явно
На співбесіді: Запит починається з SELECT і переліку стовпців, далі FROM з таблицею, а потім за потреби WHERE для фільтра, GROUP BY і HAVING для групування, ORDER BY для сортування і LIMIT для обмеження. Для швидкого дослідження я пишу SELECT зірочка, але в автотестах перелічую стовпці явно, щоб тест не залежав від зміни структури таблиці.
У якому порядку насправді виконується запит?
Логічний порядок відрізняється від порядку запису:
FROM і JOIN — зібрати рядки з таблиць
WHERE — відфільтрувати рядки
GROUP BY — згрупувати
HAVING — відфільтрувати групи
SELECT — обчислити стовпці й аліаси
DISTINCT — прибрати дублікати
ORDER BY — відсортувати
LIMIT / OFFSET — обрізати
Звідси практичні наслідки: аліас із SELECT не можна використати в WHERE — на момент WHERE його ще не існує. А в ORDER BY — можна, бо сортування йде після SELECT. І агрегатну функцію не можна поставити у WHERE — для цього є HAVING.
-- помилка: аліас amount_usd ще не існує на етапі WHERE
SELECT amount * 0.25 AS amount_usd FROM transactions WHERE amount_usd > 100;
-- правильно: повторити вираз
SELECT amount * 0.25 AS amount_usd FROM transactions WHERE amount * 0.25 > 100;
-- а в ORDER BY аліас працює
SELECT amount * 0.25 AS amount_usd FROM transactions ORDER BY amount_usd DESC;
На співбесіді: Запит виконується не в тому порядку, як написаний: спочатку FROM і JOIN, потім WHERE, GROUP BY, HAVING, і лише потім SELECT, ORDER BY і LIMIT. Тому аліас із SELECT не можна використати в WHERE, а в ORDER BY можна, і тому агрегати фільтрують через HAVING, а не через WHERE.
Які оператори використовують у WHERE?
- Порівняння:
=, <> або !=, >, <, >=, <=.
- Логічні:
AND, OR, NOT.
- Множини й діапазони:
IN, BETWEEN, LIKE, IS NULL.
Пастка — пріоритет: AND виконується раніше за OR. Без дужок запит може повернути зовсім не те.
-- задумано: невдалі транзакції типу deposit або withdrawal
-- насправді: (deposit) АБО (withdrawal І failed)
SELECT * FROM transactions
WHERE type = 'deposit' OR type = 'withdrawal' AND status = 'failed';
-- правильно
SELECT * FROM transactions
WHERE (type = 'deposit' OR type = 'withdrawal') AND status = 'failed';
На співбесіді: У WHERE я використовую оператори порівняння, логічні AND, OR, NOT, а також IN, BETWEEN, LIKE і IS NULL. Головна пастка — пріоритет: AND виконується раніше за OR, тому при їх поєднанні я завжди ставлю дужки, щоб запит робив саме те, що я задумав.
Як працюють IN, BETWEEN і LIKE?
- IN — значення входить у список:
status IN ('pending', 'failed').
- BETWEEN — діапазон включно з обома межами:
amount BETWEEN 100 AND 500 — це >= 100 AND <= 500.
- LIKE — пошук за шаблоном:
% — будь-яка кількість символів, _ — рівно один. У PostgreSQL ILIKE ігнорує регістр.
Пастка BETWEEN з датами: created_at BETWEEN '2026-09-01' AND '2026-09-30' для стовпця з часом відріже весь день 30 вересня після 00:00. Надійніше напіввідкритий діапазон.
-- надійно для TIMESTAMP: включно з 1-м, без 1 жовтня
SELECT * FROM transactions
WHERE created_at >= '2026-09-01' AND created_at < '2026-10-01';
-- усі gmail-адреси без урахування регістру
SELECT email FROM users WHERE email ILIKE '%@gmail.com';
На співбесіді: IN перевіряє входження в список, BETWEEN — діапазон включно з обома межами, а LIKE шукає за шаблоном, де відсоток — будь-яка кількість символів, а підкреслення — один символ. З датами я не використовую BETWEEN: якщо в стовпці є час, кінцевий день обріжеться. Замість цього пишу «більше або дорівнює початку і менше за наступний день».
Як знайти рядки, де значення NULL?
Лише через IS NULL і IS NOT NULL. Умова = NULL дає UNKNOWN для кожного рядка, тому запит поверне нуль рядків — без жодної помилки. Це особливо підступно: здається, що даних просто немає.
Друга пастка: WHERE status <> 'failed' не поверне рядки, де status — NULL. Якщо вони потрібні, умову треба доповнити.
SELECT * FROM users WHERE phone IS NULL; -- правильно
SELECT * FROM users WHERE phone = NULL; -- завжди 0 рядків
-- усі, крім failed, включно з NULL
SELECT * FROM transactions
WHERE status <> 'failed' OR status IS NULL;
-- або в PostgreSQL
SELECT * FROM transactions WHERE status IS DISTINCT FROM 'failed';
На співбесіді: Порожні значення шукаю тільки через IS NULL, бо порівняння «дорівнює NULL» завжди дає «невідомо» і повертає нуль рядків без помилки. І пам'ятаю зворотну пастку: умова «не дорівнює» теж пропускає рядки з NULL, тому якщо вони потрібні, додаю OR IS NULL.
Що робить DISTINCT?
DISTINCT прибирає повторювані рядки з результату. Унікальність рахується за всією комбінацією обраних стовпців, а не за першим.
SELECT DISTINCT country FROM users; -- список країн
SELECT DISTINCT currency, status FROM accounts; -- унікальні пари
SELECT COUNT(DISTINCT user_id) FROM accounts; -- скільки користувачів мають рахунок
На співбесіді: DISTINCT прибирає дублікати з результату, причому унікальність рахується за всією комбінацією стовпців. Я використовую його, щоб побачити набір можливих значень, наприклад усі статуси в таблиці, — це швидкий спосіб помітити неочікуване значення, яке не описане в документації.
Як працюють ORDER BY і LIMIT?
ORDER BY col ASC — за зростанням (за замовчуванням), DESC — за спаданням. Можна сортувати за кількома стовпцями.
LIMIT n — повернути n рядків, OFFSET k — пропустити k. У SQL Server замість LIMIT — TOP n або OFFSET … FETCH.
Дві пастки:
- LIMIT без ORDER BY повертає довільні рядки. Порядок без ORDER BY не гарантований ніде — тест на «перший запис» буде нестабільним.
- NULL у сортуванні: у PostgreSQL NULL вважається більшим за всі значення — в кінці при ASC і на початку при DESC. У MySQL навпаки. Явно:
NULLS LAST.
-- 10 останніх транзакцій; id як другий ключ робить порядок стабільним
SELECT * FROM transactions
ORDER BY created_at DESC, id DESC
LIMIT 10;
На співбесіді: ORDER BY сортує за зростанням або спаданням, LIMIT обмежує кількість рядків. Без ORDER BY порядок не гарантований, тому LIMIT без сортування дає випадкові рядки — і нестабільні тести. Для стабільності я додаю другий ключ сортування, наприклад id, і пам'ятаю, що різні СУБД по-різному ставлять NULL під час сортування.