LeetCode SQL: топ-15 задач с подробными разборами

Зачем решать SQL на LeetCode

LeetCode — самая популярная платформа для подготовки к техническим собеседованиям. SQL-раздел там небольшой (около 200 задач), но задачи стандартизированы и покрывают типичные сценарии интервью в Amazon, Google, Meta и других тех-гигантах.

Для российских аналитиков LeetCode SQL тоже полезен: многие компании (Яндекс, Авито, Тинькофф) спрашивают похожие паттерны. Решив 50 задач, вы покроете 80% типов вопросов.

Ниже разберём 15 самых частых задач.

Задача 1: Second Highest Salary

Найти вторую по величине зарплату в таблице.

SELECT MAX(salary) AS SecondHighestSalary
FROM Employee
WHERE salary < (SELECT MAX(salary) FROM Employee);

Альтернатива через LIMIT:

SELECT (SELECT DISTINCT salary FROM Employee
        ORDER BY salary DESC
        LIMIT 1 OFFSET 1) AS SecondHighestSalary;

Ловушка: если второй зарплаты нет, должен вернуться NULL, а не пустой результат. Внешний SELECT с скобками гарантирует NULL.

Задача 2: Nth Highest Salary

Обобщение: n-я по величине зарплата.

CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT AS $$
BEGIN
    RETURN (
        SELECT DISTINCT salary
        FROM Employee
        ORDER BY salary DESC
        LIMIT 1 OFFSET N - 1
    );
END;
$$ LANGUAGE plpgsql;

Альтернатива через DENSE_RANK:

SELECT salary FROM (
    SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM Employee
) t WHERE rnk = N;

DENSE_RANK даёт одинаковый rank при одинаковой зарплате, что обычно и нужно.

Задача 3: Rank Scores

Дана таблица Scores. Нужно ранжировать, где одинаковые scores получают одинаковый rank, следующий rank — непрерывный.

SELECT
    score,
    DENSE_RANK() OVER (ORDER BY score DESC) AS rank
FROM Scores;

Классическая задача на понимание разницы ROW_NUMBER, RANK, DENSE_RANK:

  • ROW_NUMBER — 1, 2, 3, 4 (всегда уникальные).
  • RANK — 1, 1, 3, 4 (пропускает при дубликатах).
  • DENSE_RANK — 1, 1, 2, 3 (без пропусков).

Задача 4: Department Top Three Salaries

В каждом отделе найти сотрудников с топ-3 зарплатами.

WITH ranked AS (
    SELECT
        d.name AS department,
        e.name AS employee,
        e.salary,
        DENSE_RANK() OVER (
            PARTITION BY e.departmentId
            ORDER BY e.salary DESC
        ) AS rnk
    FROM Employee e
    JOIN Department d ON e.departmentId = d.id
)
SELECT department, employee, salary
FROM ranked
WHERE rnk <= 3;

DENSE_RANK, потому что «top 3 salaries», а не «top 3 employees». Если три человека получают одинаково (топ-1), все они включены плюс следующие топ-2.

Умение быстро различать ROW_NUMBER и DENSE_RANK — обязательный навык для собесов. В тренажёре Карьерник есть раздел с оконными функциями, где такие нюансы разобраны на примерах.

Задача 5: Consecutive Numbers

Найти числа, которые появлялись 3 или более раз подряд в логах.

SELECT DISTINCT l1.num AS ConsecutiveNums
FROM Logs l1
JOIN Logs l2 ON l1.id = l2.id - 1 AND l1.num = l2.num
JOIN Logs l3 ON l1.id = l3.id - 2 AND l1.num = l3.num;

Три self-join. Работает, но не масштабируется для «N consecutive».

Лучше через оконные функции:

WITH groups AS (
    SELECT
        num,
        id - ROW_NUMBER() OVER (PARTITION BY num ORDER BY id) AS grp
    FROM Logs
)
SELECT DISTINCT num AS ConsecutiveNums
FROM groups
GROUP BY num, grp
HAVING COUNT(*) >= 3;

Приём: если id и ROW_NUMBER идут вместе по num, то id - ROW_NUMBER постоянен для непрерывной последовательности. Классная задача.

Задача 6: Customers Who Never Order

Найти клиентов, у которых нет заказов.

SELECT c.name AS Customers
FROM Customers c
LEFT JOIN Orders o ON o.customerId = c.id
WHERE o.id IS NULL;

Anti-join через LEFT JOIN + IS NULL. Альтернатива:

SELECT name AS Customers
FROM Customers
WHERE id NOT IN (SELECT customerId FROM Orders);

Осторожно: если в Orders.customerId есть NULL, NOT IN даст 0 строк. Лучше LEFT JOIN.

Задача 7: Duplicate Emails

Найти дублирующиеся email-ы.

SELECT email AS Email
FROM Person
GROUP BY email
HAVING COUNT(*) > 1;

Простая задача на HAVING.

Задача 8: Delete Duplicate Emails

Удалить дубликаты, оставив только запись с минимальным id.

DELETE FROM Person
WHERE id NOT IN (
    SELECT MIN(id)
    FROM Person
    GROUP BY email
);

В MySQL это не работает напрямую (нельзя SELECT из той же таблицы, что DELETE). Приходится через temp table или CTE.

В PostgreSQL работает.

Задача 9: Combine Two Tables

LEFT JOIN двух таблиц:

SELECT
    p.firstName,
    p.lastName,
    a.city,
    a.state
FROM Person p
LEFT JOIN Address a ON p.personId = a.personId;

Простая, но проверяет понимание LEFT vs INNER JOIN. Люди без адреса должны всё равно появиться в результате.

Задача 10: Exchange Seats

Поменять местами студентов с соседними ID (1-2, 3-4, и т.д.).

SELECT
    id,
    CASE
        WHEN id % 2 = 0 THEN LAG(student) OVER (ORDER BY id)
        WHEN id % 2 = 1 AND id < (SELECT MAX(id) FROM Seat) THEN LEAD(student) OVER (ORDER BY id)
        ELSE student
    END AS student
FROM Seat
ORDER BY id;

Классная задача на CASE + window functions. Чётные берут от предыдущего, нечётные — от следующего. Последнее нечётное остаётся как есть.

Задача 11: Trips and Users

Нетривиальная задача: посчитать cancellation rate поездок за период, исключая banned users.

SELECT
    request_at AS "Day",
    ROUND(
        SUM(CASE WHEN status != 'completed' THEN 1 ELSE 0 END)::numeric
        / COUNT(*)
    , 2) AS "Cancellation Rate"
FROM Trips
WHERE client_id NOT IN (SELECT users_id FROM Users WHERE banned = 'Yes')
  AND driver_id NOT IN (SELECT users_id FROM Users WHERE banned = 'Yes')
  AND request_at BETWEEN '2013-10-01' AND '2013-10-03'
GROUP BY request_at;

Множественные фильтры, агрегация с FILTER или CASE, округление. Типичная задача среднего уровня.

Задача 12: Human Traffic of Stadium

Найти дни, когда посещаемость была 100+ три дня подряд.

WITH consecutive AS (
    SELECT
        id,
        visit_date,
        people,
        id - ROW_NUMBER() OVER (ORDER BY id) AS grp
    FROM Stadium
    WHERE people >= 100
)
SELECT id, visit_date, people
FROM consecutive
WHERE grp IN (
    SELECT grp FROM consecutive GROUP BY grp HAVING COUNT(*) >= 3
)
ORDER BY visit_date;

Тот же приём, что в Consecutive Numbers — группировка непрерывных последовательностей через id - ROW_NUMBER.

Задача 13: Game Play Analysis

Для каждого игрока найти первый день, когда он играл.

SELECT
    player_id,
    MIN(event_date) AS first_login
FROM Activity
GROUP BY player_id;

Подзадача: найти число игроков, которые вернулись на следующий день после первого.

WITH first_login AS (
    SELECT player_id, MIN(event_date) AS first_date
    FROM Activity
    GROUP BY player_id
),
next_day_login AS (
    SELECT DISTINCT a.player_id
    FROM Activity a
    JOIN first_login f
        ON a.player_id = f.player_id
        AND a.event_date = f.first_date + INTERVAL '1 day'
)
SELECT COUNT(*) * 1.0 / (SELECT COUNT(*) FROM first_login) AS retention
FROM next_day_login;

Типичная retention-задача.

Задача 14: Sales Analysis III

Найти товары, которые продавались только в определённый период.

SELECT p.product_id, p.product_name
FROM Product p
JOIN Sales s ON p.product_id = s.product_id
GROUP BY p.product_id, p.product_name
HAVING MIN(s.sale_date) >= '2019-01-01'
   AND MAX(s.sale_date) <= '2019-03-31';

MIN/MAX + HAVING — элегантное решение вместо сложных NOT EXISTS.

Задача 15: Market Analysis I

Количество заказов от каждого пользователя за конкретный год.

SELECT
    u.user_id AS buyer_id,
    u.join_date,
    COUNT(o.order_id) AS orders_in_2019
FROM Users u
LEFT JOIN Orders o
    ON u.user_id = o.buyer_id
    AND EXTRACT(YEAR FROM o.order_date) = 2019
GROUP BY u.user_id, u.join_date;

Фильтр по году в JOIN (не в WHERE), чтобы сохранить пользователей с нулём заказов.

Как учиться на LeetCode

Стратегия:

Начните с Easy задач. Решите все 30-40. Проверите базовые паттерны.

Переходите к Medium. Там встречаются оконные функции, сложные JOIN-ы, consecutive numbers паттерны.

Hard — для подготовки к FAANG. 20 задач этого уровня дадут уверенность в самых сложных сценариях.

Не решайте сразу по подсказке. Думайте 15-20 минут. Если не получается — смотрите discussion, но пытайтесь понять, а не копировать.

Через день перерешивайте недавние задачи. Spaced repetition работает.

Альтернативы LeetCode

LeetCode — не единственная платформа для подготовки. У каждой альтернативы свои сильные стороны.

StrataScratch собирает задачи с реальных собеседований в FAANG и крупных стартапах. Каждая задача приходит с контекстом — какая компания, какой уровень, какие похожие задачи встречаются. Хороший выбор, если целитесь в конкретные компании и хотите понимать, какие именно паттерны там проверяют.

HackerRank SQL — другой популярный ресурс. Задачи в среднем проще, чем на LeetCode, и больше внимания базовым паттернам (JOIN, GROUP BY, простые подзапросы). Удобно для первого знакомства с форматом SQL-интервью и для junior-уровня.

DataLemur — относительно новая платформа, специализируется именно на продуктовой аналитике. Задачи ориентированы на типовые сценарии: когорты, воронки конверсии, retention, unit-экономика. Стоит посмотреть, если LeetCode кажется слишком абстрактным и хочется упражнений ближе к реальной работе.

SQLZoo — старый интерактивный туториал с упражнениями. Хорош для новичков, которые ещё плохо помнят синтаксис. Менее имитирует реальные собесы, но отлично учит базе — JOIN-ы, подзапросы, агрегации.

На практике лучше комбинировать. Начать с SQLZoo, если SQL совсем свежий. Перейти на HackerRank для закрепления базы. Потом LeetCode для средних и сложных паттернов. Под конец — StrataScratch или DataLemur, чтобы набить руку на компанейских задачах.

Читайте также

FAQ

Сколько задач нужно решить?

Минимум 50 разных. 100+ — уверенный уровень для middle собеседований.

Медленно решаю — это плохо?

На первых порах нормально. Скорость приходит с опытом. Главное — понимать логику, не искать готовые решения.

LeetCode Premium нужен?

Для SQL — не обязательно. Бесплатных 200+ задач хватает. Premium даёт доступ к задачам конкретных компаний, но большинство и так доступно.

Что делать, если совсем не понимаю?

Вернитесь на ступень ниже. Easy задача не решается — значит, проблема в базовых CTE, JOIN, GROUP BY. Перечитайте теорию, потом возвращайтесь.