Вопросы по SQL  —  одна из ключевых тем собеседования на должность аналитика данных или продукции, а также бизнес-аналитика. Крупные технологические компании, в том числе иностранные, такие как Amazon, Uber и Facebook, особенно тщательно проверяют знания претендента в этой области.

При подготовке к такому собеседованию могут возникнуть трудности с детальной проработкой всех возможных вариантов вопросов по SQL. Чтобы помочь вам справиться с этим, я подготовил краткий гайд на основе собственного опыта  —  мне довелось побывать как кандидатом на должность, так и работодателем. 

Задачи по SQL можно разделить на 4 уровня. В рамках гайда мы рассмотрим каждый из них вместе со стандартными примерами для практики. Если хотите лучше усвоить материал, не переходите сразу же к решению.

Уровень 1. Задачи, основанные на агрегатных функциях

Язык SQL отлично подходит для агрегации. Для этого есть множество функций, например SUM(), AVG(), MAX(), MIN(), COUNT() и т. д. Знание таких функций  —  это базовый уровень знаний, который ожидается от кандидата.

Рассмотрим следующую таблицу сотрудников. В каждой строке указаны данные о сотруднике  —  отдел, зарплата, руководитель и т. д.

-- Table: employees
-- | dept_id | employee_id | amount | manager_id |
-- |---------|-------------|--------|------------|
-- | 1 | 1 | 8000 | 3 |
-- | 1 | 2 | 5000 | 3 |
-- | 1 | 3 | 10000 | null |
-- | 2 | 4 | 15000 | null |
-- | 2 | 5 | 16000 | 4 |
-- | 3 | 6 | 8000 | null |

На основе этой таблицы напишите SQL-запрос, чтобы найти идентификаторы сотрудников, которые зарабатывают больше других в каждом из отделов.

Лучший способ решить любую задачу  —  это представить ее в виде пошаговой логики. В данном случае мы определяем наибольшую сумму по каждому отделу. Затем определяем формат вывода, для чего нужен только показатель employee_id.

-- Часть 1: Получаем самую высокую зарплату в каждом отделе
SELECT max(amount) AS salary
From employees
GROUP BY dept_id

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

SELECT e1.employee_id
FROM employees e1
WHERE e1.amount IN (
SELECT max(e2.amount) AS amount
From employees as e2
GROUP BY e2.dept_id
HAVING e1.dept_id = e2.dept_id )

Уровень 2. Задачи, основанные на операциях JOIN и SET

SQL предоставляет возможность объединять результаты из двух или более таблиц с помощью операций JOIN и SET. К популярным JOIN-операциям относятся inner join, left join, right join и cross join. Наиболее известные SET-операторы  —  UNION, UNION ALL, EXCEPT, INTERCEPT и др.

Вернемся к таблице сотрудников, представленной выше. Напишите SQL-запрос, чтобы определить сотрудников, которые зарабатывают больше, чем их руководитель.

-- Часть 1: Поставьте рядом зарплату менеджера и зарплату сотрудника с помощью self join

SELECT e1.employee_id
FROM employees as e1
LEFT JOIN employees as e2 ON e1.manager_id = e2.employee_id

-- Часть 2: Отфильтруйте сотрудников, которые зарабатывают больше, чем менеджер

SELECT e1.employee_id
FROM employees as e1
LEFT JOIN employees as e2 ON e1.manager_id = e2.employee_id
AND e1.amount > e2.amount

Уровень 3. Задачи на основе функции Windows

Функции Windows, также известные как аналитические функции,  —  это самая удивительная особенность языка SQL. Вот несколько популярных аналитических функций: RANK(), DENSE_RANK(), LEAD(), LAG() и т. д.

Вернемся к первой задаче. Мы использовали подзапрос, чтобы определить сотрудника, получающего самую высокую зарплату. Сделать то же самое можно и с помощью функции Windows. Попробуйте решить эту задачу, не глядя на решение.

-- Часть 1: Проранжируйте идентификаторы сотрудников по наибольшей зарплате для каждого отдела с помощью функции DENSE_RANK()

SELECT employee_id,
DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY amount desc) rnk
from employees

-- Часть 2: Отфильтруйте строки, в которых rnk = 1

SELECT employee_id
FROM
(SELECT employee_id,
DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY amount desc) rnk
from employees) a
WHERE rnk = 1

Уровень 4. Задачи, основанные на комбинации вышеупомянутых уровней

С первого взгляда некоторые задачи могут показаться сложными. Лучшей стратегией их решения признан пошаговый логический подход. Разбейте задачу на мелкие составляющие. 

Практика  —  путь к совершенству. Чем больше вы решаете подобных задач, тем легче будет разбивать их логически и находить решение.

Рассмотрим следующую таблицу посещаемости. Каждая строка содержит идентификатор сотрудника и дату посещения офиса.

Напишите SQL-запрос, чтобы найти самый длинный ряд событий по каждому из сотрудников. Вывод должен содержать имя сотрудника и его самый длинный ряд событий.

-- Table: attendance
-- | employee_id | attend_dt |
-- |-------------|-------------|
-- | 1 | 2022-01-01 |
-- | 1 | 2022-01-02 |
-- | 1 | 2022-01-05 |
-- | 2 | 2022-01-01 |
-- | 2 | 2022-01-02 |
-- | 2 | 2022-01-04 |
--`| 2 | 2022-01-05 |
-- | 2 | 2022-01-06 |
-- | 3 | 2022-01-02 |
-- | 3 | 2022-01-04 |

-- Table: employees
-- | employee_id | name |
-- |-------------|-------------|
-- | 1 | samuel |
-- | 2 | karthik |
-- | 3 | casey |

-- Часть 1: Присвойте идентификатор каждой строке в таблице
select * , row_number() over (partition by employee_id order by attend_dt asc) rn
from attendance

-- часть 2: Найдите показатель day из поля даты и разницу между rn и day
-- Это поможет нам в создании групп непрерывных рядов

select *, day(attend_at) - rn
from
(select *, row_number() over (partition by employee_id order by attend_at asc) rn
from attendance)

-- Теперь наша таблица будет выглядеть следующим образом
-- rn| employee_id | attend_dt |day|group_name(day-rn)|
-- --|-------------|-------------|---|------------------
-- 1 | 1 | 2022-01-01 |1 |0
-- 2 | 1 | 2022-01-02 |2 |0
-- 3 | 1 | 2022-01-05 |5 |2
-- 1 | 2 | 2022-01-01 |1 |0
-- 2 | 2 | 2022-01-02 |2 |0
-- 3 | 2 | 2022-01-04 |4 |1
-- 4 | 2 | 2022-01-05 |5 |1
-- 5 | 2 | 2022-01-06 |6 |1
-- 1 | 3 | 2022-01-02 |2 |1
-- 2 | 3 | 2022-01-04 |4 |2

-- Часть 3 : Находим количество для каждого показателя group_name и каждого сотрудника

select employee_id, group_name, count(*) streak
from
(select *, (day(attend_at) - rn ) group_name
from
(select *, row_number() over (partition by employee_id order by attend_at asc) rn
from attendance) a ) b
group by employee_id, group_name

-- Часть 4 : Находим самый длинный ряд событий

select employee_id, max(streak) longest_streak
from
(select employee_id, group_name, count(*) streak
from
(select *, (day(attend_at) - rn ) group_name
from
(select *, row_number() over (partition by employee_id order by attend_at asc) rn
from attendance) a ) b
group by employee_id, group_name ) c

-- Часть 5 : Упорядочиваем данные в желаемом выходном формате

select e.name, d.longest_streak
from
(select employee_id, max(streak) longest_streak
from
(select employee_id, group_name, count(*) streak
from
(select *, (day(attend_at) - rn ) group_name
from
(select *, row_number() over (partition by employee_id order by attend_at asc) rn
from attendance) a ) b
group by employee_id, group_name ) c ) d
join
(select *
from employees) e on d.employee_id = e.employee_id

Вот и все! Мы рассмотрели несколько стандартных задач по SQL, которые может потребоваться решить на собеседовании.

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

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Priyanka Meena: SQL Interview Preparation Guide.

Предыдущая статья5 весомых причин познакомиться с паттерном “Компоновщик”
Следующая статья7 бесплатных шаблонов React для разработки проектов