// interview

Вопросы на собеседовании по SQL

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

Пойти в SQL-тренажёр первые 6 задач бесплатно, карта не нужна

Разогрев: базовые вопросы

Чему равно выражение NULL = NULL в PostgreSQL?

  1. TRUE
  2. NULL — «неизвестно»
  3. FALSE
  4. Ошибка выполнения
показать ответ

Ответ: NULL — «неизвестно». NULL означает «значение неизвестно», поэтому любое сравнение с ним даёт NULL: в WHERE такая строка не пройдёт, ведь условие не стало истинным.

sql-easy-006 · nulls

Чем ILIKE отличается от LIKE в PostgreSQL?

  1. ILIKE ищет только по началу строки
  2. ILIKE всегда использует индекс и потому быстрее
  3. ILIKE сравнивает без учёта регистра
  4. ILIKE поддерживает регулярные выражения
показать ответ

Ответ: ILIKE сравнивает без учёта регистра. ILIKE — это регистронезависимый LIKE (расширение PostgreSQL): шаблон «анна» совпадёт со значением «Анна».

sql-easy-008 · select

Какие строки вернёт INNER JOIN двух таблиц?

  1. Все строки левой таблицы и подошедшие строки правой
  2. Только те строки, для которых нашлась пара в обеих таблицах
  3. Все строки обеих таблиц
  4. Только те строки, для которых пары не нашлось
показать ответ

Ответ: Только те строки, для которых нашлась пара в обеих таблицах. INNER JOIN оставляет лишь совпавшие пары, а строки без пары отбрасывает — сохранить их умеет LEFT JOIN.

sql-easy-010 · joins

В каком порядке пишутся предложения запроса?

  1. SELECT → WHERE → FROM → ORDER BY
  2. FROM → SELECT → WHERE → LIMIT
  3. SELECT → FROM → ORDER BY → WHERE
  4. SELECT → FROM → WHERE → ORDER BY → LIMIT
показать ответ

Ответ: SELECT → FROM → WHERE → ORDER BY → LIMIT. Это обязательный синтаксический порядок: если поменять местами WHERE и ORDER BY, запрос не разберётся и упадёт с синтаксической ошибкой.

sql-easy-014 · select

Средний уровень: где чаще всего сыпятся

Почему этот запрос падает с ошибкой «aggregate functions are not allowed in WHERE»?

SELECT customer_id, count(*)
FROM orders
WHERE count(*) > 1
GROUP BY customer_id;
  1. Нужно писать count(id) вместо count(*)
  2. В WHERE нельзя обращаться к колонке, указанной в GROUP BY
  3. WHERE отбирает строки до группировки, когда агрегат ещё не посчитан: фильтр по агрегату ставят в HAVING
  4. Перед WHERE не хватает ключевого слова DISTINCT
показать ответ

Ответ: WHERE отбирает строки до группировки, когда агрегат ещё не посчитан: фильтр по агрегату ставят в HAVING. WHERE работает раньше GROUP BY, когда групп ещё не существует; условие на агрегат ставится в HAVING, которое выполняется уже после группировки.

sql-medium-001 · aggregates

Какое условие в этом анти-джойне вернёт клиентов, у которых нет ни одного заказа?

SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE ???;
  1. o.id = 0
  2. o.id NOT IN (c.id)
  3. count(o.id) = 0
  4. o.id IS NULL
показать ответ

Ответ: o.id IS NULL. После LEFT JOIN у клиента без пары все колонки orders равны NULL, поэтому проверка o.id IS NULL и есть анти-джойн; count в WHERE использовать нельзя.

sql-medium-004 · joins · nulls

В трёх строках колонка amount равна 1000, 2000 и NULL. Что вернёт avg(amount)?

  1. 1000, потому что NULL считается нулём
  2. 3000
  3. 1500: NULL не участвует в расчёте
  4. NULL, потому что среди данных есть NULL
показать ответ

Ответ: 1500: NULL не участвует в расчёте. Агрегатные функции пропускают NULL: avg делит сумму 3000 на количество НЕ-NULL значений (два), а не на общее число строк.

sql-medium-005 · aggregates · nulls

Заказ оформлен 30 июня в 23:30, но в июньский отчёт не попал. Почему?

SELECT count(*)
FROM orders
WHERE created_at BETWEEN '2026-06-01' AND '2026-06-30';
  1. BETWEEN не включает границы диапазона
  2. Для дат нужно использовать IN, а не BETWEEN
  3. created_at имеет тип timestamp, поэтому BETWEEN с датами не работает вовсе
  4. Дата '2026-06-30' превращается в полночь 2026-06-30 00:00:00, поэтому весь последний день отсекается
показать ответ

Ответ: Дата '2026-06-30' превращается в полночь 2026-06-30 00:00:00, поэтому весь последний день отсекается. Дата без времени трактуется как полночь, и всё, что произошло позже 00:00 последнего дня, выпадает из диапазона; надёжный приём — полуоткрытый интервал created_at >= '2026-06-01' AND created_at < '2026-07-01'.

sql-medium-008 · dates

Чем условие o.status = 'paid' в ON отличается от такого же условия в WHERE при LEFT JOIN?

  1. Ничем: оптимизатор переносит условие, результат одинаковый
  2. В ON оно фильтрует только правую таблицу, и клиенты без оплаченных заказов остаются в выдаче с NULL
  3. В ON условие применяется после соединения, а в WHERE — до него
  4. В ON можно сравнивать только колонки таблиц, литералы там запрещены
показать ответ

Ответ: В ON оно фильтрует только правую таблицу, и клиенты без оплаченных заказов остаются в выдаче с NULL. Условие в ON участвует в поиске пары и не убивает строки левой таблицы, а WHERE применяется уже к результату соединения и отбрасывает строки с NULL — именно поэтому LEFT JOIN «превращается» в INNER.

sql-medium-012 · joins

Чем UNION отличается от UNION ALL?

  1. UNION соединяет таблицы по ключу, а UNION ALL — по строкам
  2. UNION убирает дубликаты строк, а UNION ALL возвращает всё как есть
  3. UNION работает быстрее, потому что не сортирует данные
  4. UNION ALL требует одинакового числа колонок, а UNION — нет
показать ответ

Ответ: UNION убирает дубликаты строк, а UNION ALL возвращает всё как есть. UNION дедуплицирует результат и потому обычно дороже, а UNION ALL просто склеивает выдачи; одинаковое число колонок совместимых типов нужно обоим.

sql-medium-025 · select

Сложные: на мидла и выше

В чём разница между рамками ROWS и RANGE в оконной функции?

  1. ROWS работает только с числами, а RANGE — только с датами
  2. RANGE считает быстрее, потому что не сортирует строки
  3. Они эквивалентны: RANGE — устаревший синоним ROWS
  4. ROWS отсчитывает физические строки, а RANGE — логический диапазон значений ключа ORDER BY, поэтому строки с равным ключом попадают в рамку вместе
показать ответ

Ответ: ROWS отсчитывает физические строки, а RANGE — логический диапазон значений ключа ORDER BY, поэтому строки с равным ключом попадают в рамку вместе. ROWS оперирует позициями строк («две строки назад»), а RANGE — значениями ключа сортировки, из-за чего все ровесники с одинаковым ключом обрабатываются как единое целое.

sql-hard-003 · windows

Почему NOT EXISTS считают безопасной заменой NOT IN для анти-джойна?

  1. NOT EXISTS автоматически отбрасывает строки с NULL из подзапроса
  2. NOT EXISTS всегда быстрее, потому что не строит промежуточный список значений
  3. NOT EXISTS проверяет только факт наличия строки и не сравнивает значения с NULL, поэтому NULL в подзапросе не обнуляет результат
  4. NOT EXISTS не требует коррелирующего условия
показать ответ

Ответ: NOT EXISTS проверяет только факт наличия строки и не сравнивает значения с NULL, поэтому NULL в подзапросе не обнуляет результат. EXISTS отвечает на вопрос «нашлась ли строка», а не «равно ли значение», поэтому трёхзначная логика NULL не превращает выдачу в пустую, как это делает NOT IN.

sql-hard-005 · subqueries · nulls

Какой запрос вернёт последний заказ каждого клиента через DISTINCT ON?

  1. SELECT DISTINCT ON (customer_id) * FROM orders ORDER BY created_at DESC;
  2. SELECT DISTINCT ON (created_at) * FROM orders ORDER BY customer_id, created_at DESC;
  3. SELECT DISTINCT (customer_id) * FROM orders ORDER BY customer_id, created_at DESC;
  4. SELECT DISTINCT ON (customer_id) * FROM orders ORDER BY customer_id, created_at DESC;
показать ответ

Ответ: SELECT DISTINCT ON (customer_id) * FROM orders ORDER BY customer_id, created_at DESC;. DISTINCT ON оставляет первую строку каждой группы, а «первую» определяет ORDER BY — поэтому он обязан начинаться с тех же выражений, что стоят в DISTINCT ON (иначе PostgreSQL выдаст ошибку), и только потом идёт created_at DESC.

sql-hard-007 · select

Почему нельзя отфильтровать строки по оконной функции прямо в WHERE?

SELECT id
FROM orders
WHERE row_number() OVER (ORDER BY created_at DESC) = 1;
  1. row_number() нельзя использовать без PARTITION BY
  2. Оконные функции вычисляются после WHERE, поэтому в самом WHERE их ещё не существует — нужен подзапрос или CTE
  3. В WHERE запрещены любые функции, кроме агрегатных
  4. Нужно заменить = 1 на IS NOT NULL
показать ответ

Ответ: Оконные функции вычисляются после WHERE, поэтому в самом WHERE их ещё не существует — нужен подзапрос или CTE. Оконные функции считаются уже после WHERE, GROUP BY и HAVING, поэтому фильтр по ним выносят на уровень выше — в подзапрос или CTE, где колонка с номером строки уже посчитана.

sql-hard-008 · windows

В каком порядке PostgreSQL логически выполняет предложения запроса?

  1. SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY
  2. FROM → WHERE → SELECT → GROUP BY → HAVING → ORDER BY
  3. FROM → WHERE → GROUP BY → HAVING → оконные функции → SELECT → ORDER BY → LIMIT
  4. FROM → GROUP BY → WHERE → HAVING → SELECT → LIMIT → ORDER BY
показать ответ

Ответ: FROM → WHERE → GROUP BY → HAVING → оконные функции → SELECT → ORDER BY → LIMIT. Именно этот порядок объясняет три классические ошибки разом: агрегат нельзя фильтровать в WHERE, псевдоним из SELECT недоступен в WHERE, а оконную функцию нельзя положить ни в WHERE, ни в HAVING.

sql-hard-009 · select

Куда идти после вопросов?