Реальные задачи с собеседований по SQL

Перевод статьи «SQL Interview Questions».

Ниже примеры двух тестовых заданий, которые мне попались на техническом собеседовании. Несмотря на то, что в сети полно похожих постов на тему «Как решать задачи с Leetcode», я всё-таки добавлю свои пять копеек — чем больше примеров, тем легче готовиться.

🔥 Важное для QA-специалистов! 🔥
В QaRocks ты найдешь туториалы, задачи и полезные книги, которых нет в открытом доступе. Уже более 15.000 подписчиков – будь среди нас! Заходи к нам в телеграм канал QaRocks

Сложность заключалась в том, что задачи нужно было решить без компилятора — приходилось мысленно представлять поведение кода. А с этим у меня, честно говоря, не очень 😀 Вот сами задачи:

1. Есть таблица с колонками Id и email. Необходимо вывести дублирующиеся адреса электронной почты:

таблица id + email

Нужны именно дубли! Не уникальные записи, а именно те, что повторяются!
Первое, что пришло в голову:

SELECT NOT DISTINCT (id)
FROM clients

Разумеется, это не сработало, так как функции NOT DISTINCT() просто не существует, и технически невозможно “отрицать” функцию 🙂

Тогда я попробовала сделать так:

SELECT email
FROM clients
WHERE COUNT(id) != 1
GROUP BY email

Читайте также: Самые популярные вопросы на собеседованиях по SQL

Теплее, но не совсем то!
Сначала я даже подумала, что ошибка связана с условием COUNT(id) != 1, но дело оказалось не в этом. Проблема заключалась в том, что агрегатные функции нельзя использовать в WHERE — вместо них нужен HAVING().
Важно помнить, что HAVING всегда идёт после GROUP BY, а WHERE — до:

SELECT email 
FROM clients
GROUP BY email
HAVING COUNT(id) != 1

Позже мне пришло более хитрое решение. Поскольку задача связана с дубликатами, почему бы не выполнить self join — соединить таблицу саму с собой по полю id?

Если мы выполним следующий запрос:

SELECT a.id, a.email
FROM clients a
JOIN clients b ON a.email = b.email

то мы получим следующее:

Только одна единственная строка с уникальным email (строка 5) не будет соединена ни с одной другой. Но нам как раз нужно отфильтровать такие уникальные адреса и оставить только те, что повторяются — то есть найти совпадающие email’ы, но с разными id. Например, строки 1 и 6.
Значит, надо добавить условие: id из первой таблицы не должен совпадать с id из второй.

----
JOIN clients b ON a.email = b.email
WHERE a.id <> b.id;

Главное помнить, что нам нужен только столбец email:

И, конечно же, не забыть про DISTINCT, чтобы выкинуть дубли:

SELECT a.email
FROM clients a
JOIN clients b ON a.email = b.email
WHERE a.id <> b.id;

Попробуйте выполнить этот запрос — должно сработать:

результат для столбца email, где есть дубликаты

2. Есть таблица с полями id | name | salary. Необходимо найти id сотрудника, у которого вторая по величине зарплата.

таблица с id и зарплатой сотрудников

Итак, надо, чтобы запрос вернул id Ады (5) или Элайджи (3). Стоит отметить, что в условии задачи не сказано, что делать в случае с одинаковыми зарплатами, поэтому мы оставляем это на наше усмотрение.

Изначально я предполагала отсортировать сотрудников по зарплате:

SELECT id, name, MAX(salary)
FROM employees
GROUP BY id, name
ORDER BY MAX(salary) DESC

что привело бы к следующему результату:

и затем просто извлечь вторую строку.
Один из способов — присвоить зарплатам порядковые номера по убыванию значений, вычесть единицу из нужного порядкового номера и получить id, соответствующий этой позиции. Это вариант с использованием оконных функций (что не является моей сильной стороной 😀 )

… или можно воспользоваться OFFSET! Вот такой запрос как раз делает то, что надо — просто сдвигается на одну строку:

SELECT id, name, MAX(salary)
FROM employees
GROUP BY id, name
ORDER BY MAX(salary) DESC
OFFSET 1 ROWS

— этот запрос просто пропустит первую строку и начнёт со второй:

А чтобы получить только вторую строку, необходимо указать, что нам нужна всего одна строка:

SELECT id, name, MAX(salary)
FROM employees
GROUP BY id, name
ORDER BY MAX(salary) DESC
OFFSET 1 ROWS   
FETCH NEXT 1 ROWS ONLY

И в результате запрос вернёт только одну строку:

Но здесь возникает вопрос: как система определит, кого из сотрудников вернуть — Аду или Элайджу? 😀

Альтернативный способ решения задачи — использовать конструкцию LIMIT. Если после сортировки ограничить выборку одной строкой (LIMIT 1), то мы получим максимальную зарплату. Но так как нам нужна вторая по величине, используем LIMIT 2:

SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 2

И в результате получим:

Почему бы не воспользоваться в данном случае функцией MIN? Если взять минимум из двух наибольших зарплат — как раз получится вторая по величине:

SELECT MIN(salary) AS second_max_salary
FROM (
    SELECT DISTINCT salary
    FROM employees
    ORDER BY salary DESC
    LIMIT 2
) AS top_salaries;

Третий способ — использовать вместе LIMIT и OFFSET:

SELECT salary
FROM employees
LIMIT 1
OFFSET 2

Это довольно просто и понятно.

Четвёртый, заключительный способ, основан на работе с множествами — как в математике.
Допустим, MAX(salary) — это множество, состоящее из одного значения. Это множество является подмножеством всех зарплат. Остальными элементами этого множества являются все другие зарплаты:

Если перевести вышесказанное на язык SQL, получаем вот такой запрос:

SELECT MAX(salary)
FROM employees
WHERE salary NOT IN (SELECT MAX(salary) 
                  FROM employees) 
🔥 Какой была ваша первая зарплата в QA и как вы искали первую работу? 

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Читать в телеграм

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *