В нескольких базах данных СУБД, включая MySQL, подзапросы часто являются одной из причин проблем с производительностью. Поэтому у нас есть стимул избегать их всякий раз, когда мы можем, и находить альтернативные способы реализации наших требований.
Одно из самых популярных применений для подвыборов в SQL — это когда нужно выбрать первую, последнюю, максимальную или минимальную строку для каждой группы в таблице. Например, как бы вы реализовали SQL-запрос, который должен получать сотрудников с максимальной зарплатой для каждого отдела из таблицы сотрудников? На самом деле, получение самой зарплаты довольно просто, но это становится более сложным, когда вы хотите получить имя сотрудника (данные строки) вместе с максимальной зарплатой.
Давайте посмотрим на таблицу:
Name | Salary | Role |
David | 130,000 | Software Engineer |
AndreyEx | 175,000 | DevOps Engineer |
Marina | 120,000 | Software Engineer |
Anton | 130,000 | DevOps Engineer |
Alice | 110,000 | Software Engineer |
Maxim | 95,000 | DevOps Engineer |
Обычной практикой будет написание решения этой проблемы с помощью подбора. Чтобы избежать этой практики, нам нужно перефразировать проблему и углубиться в нее.
Новая проблема: найти всех сотрудников там, где есть не менее доходный сотрудник той же роли.
Теперь давайте рассмотрим меньшие части этого требования:
Найти всех сотрудников:
select * from employees
Для каждого сотрудника найдите всех менее прибыльных людей с одинаковой ролью — здесь нам нужно выполнить два действия:
SELECT empl1.*, empl2.salary FROM employees AS empl1 LEFT OUTER JOIN employees AS empl2 ON empl2.role = empl1.role AND empl2.salary > empl1.salary
Теперь давайте посмотрим на результат второго шага. Вы заметите, что строки с максимальным окладом содержат запись NULL для объединенного столбца окладов.
Name | Salary | Role | Salary (joint) |
David | 130,000 | Software Engineer | NULL |
AndreyEx | 175,000 | DevOps Engineer | NULL |
Marina | 120,000 | Software Engineer | 130,000 |
Anton | 130,000 | DevOps Engineer | 175,000 |
Alice | 110,000 | Software Engineer | 120,000 |
Alice | 110,000 | Software Engineer | 130,000 |
Maxim | 95,000 | DevOps Engineer | 130,000 |
Maxim | 95,000 | DevOps Engineer | 175,000 |
Поэтому следующим простым шагом будет просто отфильтровать все остальные строки, добавив еще одно условие:
SELECT empl1.*, empl2.salary FROM employees AS empl1 LEFT OUTER JOIN employees AS empl2 ON empl2.role = empl1.role AND empl2.salary > empl1.salary WHERE empl2.salary IS NULL;
И конечный результат:
David | 130,000 | Software Engineer | NULL |
AndreyEx | 175,000 | DevOps Engineer | NULL |
Обратите внимание — чтобы это решение работало, вам нужно убедиться, что у вас есть правильный индекс. В этом примере вам нужно будет создать индекс, включающий столбцы role и salary, чтобы избежать полного сканирования таблиц.