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 зірочка, але в автотестах перелічую стовпці явно, щоб тест не залежав від зміни структури таблиці.

У якому порядку насправді виконується запит?

Логічний порядок відрізняється від порядку запису:

  1. FROM і JOIN — зібрати рядки з таблиць
  2. WHERE — відфільтрувати рядки
  3. GROUP BY — згрупувати
  4. HAVING — відфільтрувати групи
  5. SELECT — обчислити стовпці й аліаси
  6. DISTINCT — прибрати дублікати
  7. ORDER BY — відсортувати
  8. 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 під час сортування.