📘 Банк задач для собеседований по SQL
В этом разделе собраны практические задачи, которые часто встречаются на собеседованиях.
Задача Конкуренты
Есть таблица competitors_prices со следующими данными:
- sku_id_competitor — идентификатор SKU конкурента
- competitor_id — идентификатор конкурента
- promo_price_competitor — промо-цена конкурента
- regular_price_competitor — регулярная цена конкурента
- parsing_date — дата парсинга
Нужно получить витрину, где для каждой комбинации (sku_id_competitor, competitor_id, parsing_date) остаётся только одно значение цены:
- если есть
promo_price_competitor→ берём её, - если промо отсутствует (
NULL) → берёмregular_price_competitor.
DDL и наполнение таблицы
CREATE TABLE IF NOT EXISTS public.competitors_prices (
sku_id_competitor INT,
competitor_id INT,
promo_price_competitor DECIMAL,
regular_price_competitor DECIMAL,
parsing_date DATE
);
INSERT INTO competitors_prices VALUES
(15, 200, 147.0, 174.0, DATE '2025-08-30'),
(16, 200, NULL, 222.0, DATE '2025-08-27'),
(15, 200, NULL , 174.0, DATE '2025-08-30');Показать результат
| sku_id_competitor | competitor_id | price | parsing_date |
|---|---|---|---|
| 15 | 200 | 147 | 2025-08-30 |
| 16 | 200 | 222 | 2025-08-27 |
Показать решение
WITH ranked AS (
SELECT
sku_id_competitor,
competitor_id,
promo_price_competitor,
regular_price_competitor,
parsing_date,
COALESCE(promo_price_competitor, regular_price_competitor) AS price,
ROW_NUMBER() OVER (
PARTITION BY sku_id_competitor, competitor_id, parsing_date
ORDER BY CASE WHEN promo_price_competitor IS NOT NULL THEN 1 ELSE 0 END DESC
) AS rn
FROM public.competitors_prices
)
SELECT
sku_id_competitor,
competitor_id,
price,
parsing_date
FROM ranked
WHERE rn = 1;Задача: Запрос с использованием SELECT
Есть таблица employees с полями:
- id
- name
- salary
- department_id
Нужно написать SQL-запрос, который возвращает имена сотрудников и их зарплаты для всех сотрудников с зарплатой выше средней по всей таблице.
Показать решение
SELECT
name,
salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);Задача: Оконные функции и подзапросы
В таблице employees напишите запрос, который возвращает:
- имя сотрудника
- зарплату
- ранг по зарплате внутри своего
department_id(нумерация по убыванию зарплаты).
Показать решение
SELECT
name,
salary,
department_id,
RANK() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS salary_rank
FROM employees;Задача Последняя загрузка по таблице за день
Есть таблица public.load_status со следующими данными: • dt — дата/время загрузки • table_name — имя таблицы/вьюхи • status — статус загрузки
Нужно построить выборку/витрину, где для каждой таблицы в рамках каждого календарного дня остаётся только одна строка — с максимальной dt (если в день было несколько загрузок). Отфильтровать последние 14 дней.
DROP TABLE IF EXISTS public.load_status;
CREATE TABLE public.load_status (
id bigserial PRIMARY KEY, -- уникальный ключ
dt timestamp with time zone NOT NULL, -- время загрузки
table_name text NOT NULL, -- имя таблицы/вьюхи
status text NOT NULL -- статус загрузки
);
INSERT INTO public.load_status (dt, table_name, status) VALUES
-- Таблица alpha — два прогона в один день
('2025-09-15 09:15:00+03', 'alpha.orders_daily', 'OK'),
('2025-09-15 11:45:00+03', 'alpha.orders_daily', 'OK'),
-- Таблица beta — три прогона в один день
('2025-09-15 07:20:00+03', 'beta.customer_snapshot', 'OK'),
('2025-09-15 09:55:00+03', 'beta.customer_snapshot', 'OK'),
('2025-09-15 14:10:00+03', 'beta.customer_snapshot', 'OK'),
-- Таблица gamma — один прогон сегодня, два вчера
('2025-09-15 08:30:00+03', 'gamma.product_catalog', 'OK'),
('2025-09-14 10:05:00+03', 'gamma.product_catalog', 'OK'),
('2025-09-14 16:25:00+03', 'gamma.product_catalog', 'OK'),
-- Таблица delta — разные даты
('2025-09-13 06:40:00+03', 'delta.pricing_rules', 'OK'),
('2025-09-14 07:55:00+03', 'delta.pricing_rules', 'OK'),
('2025-09-15 12:20:00+03', 'delta.pricing_rules', 'OK'),
-- Таблица epsilon — только один прогон
('2025-09-15 10:10:00+03', 'epsilon.stock_levels', 'OK');Показать результат
| dt | table_name | status | day |
|---|---|---|---|
| 2025-09-15 14:10:00+03 | beta.customer_snapshot | OK | 2025-09-15 |
| 2025-09-15 12:20:00+03 | delta.pricing_rules | OK | 2025-09-15 |
| 2025-09-15 11:45:00+03 | alpha.orders_daily | OK | 2025-09-15 |
| 2025-09-15 10:10:00+03 | epsilon.stock_levels | OK | 2025-09-15 |
| 2025-09-15 08:30:00+03 | gamma.product_catalog | OK | 2025-09-15 |
| 2025-09-14 16:25:00+03 | gamma.product_catalog | OK | 2025-09-14 |
| 2025-09-14 07:55:00+03 | delta.pricing_rules | OK | 2025-09-14 |
| 2025-09-13 06:40:00+03 | delta.pricing_rules | OK | 2025-09-13 |
Показать решение
WITH ranked AS (
SELECT
ls.dt,
ls.table_name,
ls.status,
ls.dt::date AS day,
ROW_NUMBER() OVER (
PARTITION BY ls.table_name, ls.dt::date
ORDER BY ls.dt DESC
) AS rn
FROM public.load_status AS ls
WHERE ls.dt >= current_date - INTERVAL '14 days'
)
SELECT
dt,
table_name,
status,
day
FROM ranked
WHERE rn = 1
ORDER BY dt DESC;Задача: Создание функции
Создайте функцию get_department_salary_summary(dept_id INT), которая принимает ID отдела и возвращает таблицу с:
- общим количеством сотрудников
- средней зарплатой в этом отделе.
Показать решение
CREATE OR REPLACE FUNCTION get_department_salary_summary(dept_id INT)
RETURNS TABLE (
employees_count INT,
avg_salary NUMERIC
)
AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(*) AS employees_count,
AVG(salary) AS avg_salary
FROM employees
WHERE department_id = dept_id;
END;
$$ LANGUAGE plpgsql;Задача: Поставщики в Лемана Про
Напишите запрос, который для всех поставщиков с типом (supplier_type) отличным от "WAREHOUSE" выдаст идентификатор поставщика и сумму заказов у этого поставщика. В запросе не использовать оконные функции.
order_id | store_id | item_id | supplier_id | price | quantity | dt
---------+----------+---------+-------------+-------+----------+------------------------
1 | 1 | 10001 | 1 | 20 | 1 | 2023-10-11 13:54:00.000
2 | 2 | 10001 | 2 | 30 | 2 | 2023-11-11 17:14:05.000
2 | 3 | 10002 | 2 | 100 | 3 | 2023-09-01 09:32:50.000
3 | 3 | 10002 | 3 | 130 | 1 | 2023-10-21 14:51:45.000
supplier_id | supplier_name | supplier_type
------------+---------------+--------------
1 | Supplier 1 | WAREHOUSE
2 | Supplier 2 | NULL
3 | Supplier 3 | SUPPLIER
4 | Supplier 4 | SUPPLIERПоказать решение
SELECT
o.supplier_id,
SUM(o.price * o.quantity) AS total_order_amount
FROM orders o
JOIN suppliers s
ON o.supplier_id = s.supplier_id
WHERE s.supplier_type <> 'WAREHOUSE'
OR s.supplier_type IS NULL
GROUP BY o.supplier_id;Задача: Яндекс Такси
Имеем таблицу RIDE с поездками пользователей на такси. Найти всех пользователей, которые совершали поездки два (или более) дня подряд.
| ride_id | user_id | tariff | dt | cost |
|--------:|--------:|---------|------------|-----:|
| 2305001 | 1001 | econom | 2023-05-02 | 340 |
| 2305002 | 1001 | comfort | 2023-05-03 | 500 |
| 2305003 | 2001 | econom | 2023-05-01 | 120 | 2023-05-01
| 2305004 | 2001 | econom | 2023-05-01 | 150 |
| 2305007 | 2001 | econom | 2023-05-02 | 999 |
| 2305097 | 2001 | econom | 2023-05-03 | 999 |
Показать решение
SELECT DISTINCT r1.user_id
FROM ride r1
JOIN ride r2
ON r1.user_id = r2.user_id
AND r2.dt = r1.dt + INTERVAL '1 day';Задача: Средняя оценка по языкам программирования
Необходимо посчитать среднюю оценку по каждому языку программирования.
Входные данные
arr = [
{'name': 'Petr', 'subject': 'Python', 'score': 97},
{'name': 'Max', 'subject': 'C++', 'score': 70},
{'name': 'Petr', 'subject': 'C++', 'score': 45},
{'name': 'Max', 'subject': 'Python', 'score': 60},
{'name': 'Petr', 'subject': 'SQL', 'score': 65},
{'name': 'Max', 'subject': 'SQL', 'score': 100}
]Ожидаемый результат:
Python - 78.5
C++ - 57.5
SQL - 82.5Показать решение
scores = {}
for item in arr:
subject = item['subject']
score = item['score']
if subject not in scores:
scores[subject] = {'sum': 0, 'count': 0}
scores[subject]['sum'] += score
scores[subject]['count'] += 1
for subject, data in scores.items():
avg_score = data['sum'] / data['count']
print(f"{subject} - {avg_score}")🧾 Задача: Средний чек покупателя за последнюю неделю его активности
Даны таблицы магазинов и чеков.
Схема БД
CREATE TABLE shop (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
city VARCHAR(255) NOT NULL
);
CREATE TABLE cheque (
uid UUID NOT NULL DEFAULT gen_random_uuid(),
created_at TIMESTAMP NOT NULL DEFAULT now(),
"sum" DECIMAL(10, 2) NOT NULL,
shop_id INT NOT NULL,
customer_id BIGINT NOT NULL,
PRIMARY KEY (uid),
FOREIGN KEY (shop_id) REFERENCES shop (id)
);Показать решение
WITH last_purchase AS (
SELECT
customer_id,
MAX(created_at) AS last_purchase_at
FROM cheque
GROUP BY customer_id
),
weekly_avg AS (
SELECT
c.customer_id,
AVG(c."sum") AS avg_sum,
lp.last_purchase_at
FROM cheque c
JOIN last_purchase lp
ON lp.customer_id = c.customer_id
WHERE c.created_at >= lp.last_purchase_at - INTERVAL '7 days'
AND c.created_at <= lp.last_purchase_at
GROUP BY c.customer_id, lp.last_purchase_at
)
SELECT
customer_id,
avg_sum,
last_purchase_at::date AS last_purchase_date
FROM weekly_avg
ORDER BY customer_id;