Documentation
📚 Вопросы с собеседований
SQL задачи

📘 Банк задач для собеседований по 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;