Задачи на SQL с собеседований
↑ 1.1 Реляционная модель и SQL · ← Предыдущая
Зачем это на собесе
Практический блок: типовые задачи, которые дают на живом кодинге. Решайте сначала сами, потом сверяйтесь.
Тестовая схема
customers(id, name, email, city, created_at)
orders(id, customer_id, total, status, created_at)
order_items(order_id, product_id, qty, price)
products(id, name, category_id, price)
employees(id, name, salary, department_id, manager_id)Классика
1. Второй по величине оклад
SELECT max(salary) FROM employees WHERE salary < (SELECT max(salary) FROM employees);
-- устойчивый вариант с окном
SELECT DISTINCT salary FROM (
SELECT salary, dense_rank() OVER (ORDER BY salary DESC) r FROM employees
) t WHERE r = 2;2. Дубликаты email
SELECT email, count(*) FROM customers GROUP BY email HAVING count(*) > 1;3. Клиенты без заказов
SELECT c.* FROM customers c WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);4. Топ-3 клиента по выручке в каждом городе
SELECT * FROM (
SELECT c.city, c.id, sum(o.total) AS revenue,
row_number() OVER (PARTITION BY c.city ORDER BY sum(o.total) DESC) AS rn
FROM customers c JOIN orders o ON o.customer_id = c.id
GROUP BY c.city, c.id
) t WHERE rn <= 3;5. Сотрудники с зарплатой выше средней по отделу
SELECT * FROM (
SELECT e.*, avg(salary) OVER (PARTITION BY department_id) AS avg_dep FROM employees e
) t WHERE salary > avg_dep;6. Накопительная выручка по дням и рост к предыдущему дню
SELECT day, revenue,
sum(revenue) OVER (ORDER BY day) AS running,
revenue - lag(revenue) OVER (ORDER BY day) AS delta
FROM (SELECT created_at::date AS day, sum(total) AS revenue FROM orders GROUP BY 1) d;7. Клиенты, сделавшие заказ в 3 месяца подряд (gaps and islands)
WITH m AS (
SELECT DISTINCT customer_id, date_trunc('month', created_at)::date AS month FROM orders
), g AS (
SELECT customer_id, month,
month - (row_number() OVER (PARTITION BY customer_id ORDER BY month) * interval '1 month') AS grp
FROM m
)
SELECT customer_id FROM g GROUP BY customer_id, grp HAVING count(*) >= 3;8. Иерархия: все подчинённые руководителя (рекурсивный CTE)
WITH RECURSIVE t AS (
SELECT id, name, manager_id FROM employees WHERE id = 1
UNION ALL
SELECT e.id, e.name, e.manager_id FROM employees e JOIN t ON e.manager_id = t.id
) SELECT * FROM t;9. Конверсия воронки (просмотр → корзина → покупка)
SELECT count(DISTINCT user_id) FILTER (WHERE event = 'view') AS viewed,
count(DISTINCT user_id) FILTER (WHERE event = 'cart') AS carted,
count(DISTINCT user_id) FILTER (WHERE event = 'purchase') AS bought
FROM events;10. Удалить дубликаты, оставив самую раннюю запись
DELETE FROM customers c USING customers d
WHERE c.email = d.email AND c.id > d.id;11. Медиана
SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY total) FROM orders;12. Пропущенные id в последовательности
SELECT g.i FROM generate_series((SELECT min(id) FROM t), (SELECT max(id) FROM t)) g(i)
LEFT JOIN t ON t.id = g.i WHERE t.id IS NULL;Как отвечать
- Уточните схему, дубликаты, NULL, ожидаемый объём.
- Опишите идею словами (JOIN + агрегат, окно, антиджоин).
- Напишите простое решение, затем оптимизацию.
- Проверьте граничные случаи: пустые группы, равные значения, NULL.
- Скажите про индексы и план запроса.
Вопросы с ответами
Как быстро проверить корректность запроса?
На малом наборе данных вручную посчитать ожидаемый результат, проверить крайние случаи (пусто, дубли, NULL) и сравнить с альтернативным способом решения.
Когда использовать оконную функцию, а когда GROUP BY?
Если нужно сохранить детальные строки и добавить агрегат/ранг — окно. Если нужна одна строка на группу — GROUP BY.