JOIN и операции множеств: вопросы для собеседования (часть 2)
JOIN-ы — фундамент аналитического SQL. INNER, LEFT, RIGHT, FULL OUTER, CROSS JOIN, а также UNION, INTERSECT, EXCEPT — на собеседовании нужно не просто знать синтаксис, но и понимать, что происходит при дубликатах ключей и NULL-значениях. Задачи на JOIN встречаются на каждом собеседовании без исключения.
Вопросы 6–10 из 40
6Нужно получить уникальный список идентификаторов пользователей из двух каналов: `campaign_a(user_id)` и `campaign_b(user_id)`. Как корректнее объединить списки, чтобы убрать дубликаты?
A`UNION` объединяет списки и автоматически удаляет повторяющиеся идентификаторы
B`UNION ALL` объединяет списки и сохраняет все дубликаты между двумя каналами
C`INNER JOIN` по `user_id` возвращает только пользователей, присутствующих в обеих таблицах
D`FULL JOIN` по `user_id` возвращает строки даже там, где совпадений по ключу нет
Ответ: `UNION` возвращает множество без дублей, а `UNION ALL` просто склеивает строки и сохраняет дубликаты.
Если один и тот же `user_id` есть в обеих таблицах, `UNION` вернёт его один раз. `UNION ALL` вернёт две строки и может раздуть метрики, если просто посчитать количество строк. `INNER JOIN` оставит только пересечение каналов, а `FULL JOIN` решает совсем другую задачу — соединение данных по ключу с сохранением несовпавших строк.
Подробный разбор → 7Есть две таблицы с одинаковой схемой: `events_web(user_id, event_name, created_at)` и `events_app(user_id, event_name, created_at)`. Нужно получить общий поток событий для дальнейшей агрегации. Что использовать?
A`INNER JOIN` по `user_id` между `events_web` и `events_app`
B`LEFT JOIN` по `user_id` от `events_web` к `events_app`
C`FULL JOIN` по `user_id` между `events_web` и `events_app`
D`UNION ALL` между двумя выборками из `events_web` и `events_app`
Ответ: `UNION ALL` складывает строки из двух источников вертикально, а `JOIN` соединяет колонки горизонтально по ключу.
Когда нужно объединить записи одинакового формата из разных источников (web и app), применяют операции над наборами: `UNION` или `UNION ALL`. `JOIN` используется для добавления атрибутов по ключу и не предназначен для «склейки» двух логов событий в один поток. Обычно берут `UNION ALL`, чтобы не терять повторяющиеся события и не тратить время на удаление дублей.
Подробный разбор → 8Нужно вывести пользователей, у которых есть хотя бы один заказ. У каждого пользователя может быть много заказов. Какой способ не создаст дублирование строк?
AНаписать `INNER JOIN orders o ON o.user_id = u.id`: соединение вернёт по строке на каждого пользователя
BНаписать `WHERE u.id IN (SELECT DISTINCT user_id FROM orders)`: `DISTINCT` ускорит поиск по индексу
CНаписать `INNER JOIN (SELECT user_id FROM orders LIMIT 1) o ON o.user_id = u.id`: `LIMIT` исключит дубликаты
DНаписать `WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)`: поиск остановится на первом совпадении
Ответ: `EXISTS` проверяет наличие хотя бы одной строки и не дублирует результат, тогда как `INNER JOIN` создаст по строке на каждый заказ.
`EXISTS (подзапрос)` — это полусоединение: для каждого пользователя проверяется, существует ли хотя бы одна строка в `orders`, и поиск прекращается при первом совпадении. Результат — ровно одна строка на пользователя. `INNER JOIN` без предварительной дедупликации `orders` создаст столько строк, сколько заказов у пользователя. `IN` тоже работает корректно, но `DISTINCT` внутри не «быстрее индекса».
Подробный разбор → 9В таблицах `orders` и `users` есть одноимённые столбцы: `user_id`, `updated_at`, `status`. Аналитик пишет `SELECT * FROM orders NATURAL JOIN users`. Что произойдёт?
AСоединение пройдёт по `user_id`: `NATURAL JOIN` берёт первый общий столбец и игнорирует остальные
BЗапрос вернёт ошибку: `NATURAL JOIN` не поддерживается в `PostgreSQL` для пользовательских таблиц
CСоединение пройдёт по столбцу `id` обеих таблиц: `NATURAL JOIN` выбирает столбец с именем `id`
DСоединение пройдёт по всем одноимённым столбцам сразу: `user_id`, `updated_at` и `status` войдут в `ON`
Ответ: `NATURAL JOIN` автоматически соединяет по всем столбцам с одинаковыми именами — здесь по `user_id`, `updated_at` и `status`, что почти наверняка не то, что нужно.
`NATURAL JOIN` находит все пары одноимённых столбцов и использует их как условие `ON`. Если помимо `user_id` совпадают `updated_at` и `status`, соединение потребует совпадения всех трёх — это резко сократит результат или вернёт пустую таблицу. Добавление нового столбца с тем же именем в любую таблицу молча сломает запрос. Поэтому `NATURAL JOIN` считается опасным в продакшне, и обычно используют явный `JOIN ... ON` с перечислением ключей.
Подробный разбор → 10Вы объединяете логи событий из двух источников `events_web` и `events_app`, чтобы посчитать количество событий. Важно не потерять повторяющиеся события (например, два одинаковых клика). Что лучше использовать для объединения?
A`UNION` убирает дубликаты строк между источниками и занижает счёт событий
B`INTERSECT` оставляет только строки, попавшие в оба источника одновременно
C`EXCEPT` вычитает строки второго источника из первого по совпадению значений
D`UNION ALL` сохраняет все строки обоих источников вместе с повторениями
Ответ: `UNION ALL` сохраняет все строки, а `UNION` удаляет дубликаты и может занизить количество событий.
В событийных логах одинаковые строки могут быть валидными повторениями (два клика, две покупки и т.д.). Если использовать `UNION`, такие строки будут схлопнуты, и метрика количества событий станет заниженной. `INTERSECT` оставит только пересечение, `EXCEPT` — разность, оба сценария теряют события. Поэтому для подсчётов по логам обычно применяют `UNION ALL`.
Подробный разбор →