Представьте проекцию отношения только на один атрибут: почему в реляционной алгебре повторяющиеся значения ...

Представьте проекцию отношения только на один атрибут: почему в реляционной алгебре повторяющиеся значения исчезают, а в обычном результате SQL могут сохраниться?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

В классической реляционной алгебре отношение является множеством кортежей, поэтому проекция автоматически удаляет дубликаты, возникшие после исключения остальных атрибутов. В SQL по умолчанию действует семантика мультимножества: обычный выбор столбцов сохраняет повторяющиеся строки, а для их удаления требуется явное указание DISTINCT.

Исторический контекст

Реляционная модель строится на математическом понятии множества: один и тот же кортеж не может присутствовать в отношении более одного раза. Поэтому операция проекции не просто отбрасывает столбцы, но и нормализует результат обратно к множеству.

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

Постановка проблемы

Допустим, в таблице заказов один клиент оформил несколько заказов. После выбора только идентификатора клиента один и тот же идентификатор появится столько раз, сколько у клиента заказов, если используется обычный запрос SQL без устранения дубликатов.

Если разработчик ожидает чисто реляционную семантику, он может ошибочно посчитать количество клиентов по числу строк заказов. Обратная ошибка тоже опасна: бездумное удаление дубликатов может уничтожить информацию о кратности, необходимую для подсчёта заказов или анализа повторных событий.

Подробное решение

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

В SQL аналогичный список столбцов не означает автоматическое устранение повторов. Для получения множества значений используется DISTINCT:

SELECT customer_id FROM orders; SELECT DISTINCT customer_id FROM orders;

Первый запрос возвращает строку для каждого заказа. Второй возвращает каждого клиента не более одного раза. Оптимизатор может реализовать устранение дубликатов сортировкой, хешированием или другим способом; конкретный алгоритм не меняет логическую семантику.

DISTINCT имеет стоимость: системе может понадобиться память, сортировка или хеш-структура, а результат часто нельзя начать выдавать полностью, пока не выполнена существенная часть обработки. Поэтому его не следует добавлять автоматически вместо понимания требуемой кратности.

Если нужно сохранить повторы, но одновременно вычислить их количество, правильнее использовать агрегирование. Если нужна только проверка существования клиента, часто лучше выразить именно проверку существования, а не получать все строки заказов и затем удалять дубликаты.

При устранении дубликатов SQL учитывает значения всей результирующей строки. Строки с одинаковыми значениями выбранных столбцов считаются дубликатами; специальная логика NULL в операциях сравнения не превращает такие строки в разные при операции устранения дублей.

Ситуация из практики

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

Второй вариант добавляет DISTINCT. Он выражает требование явно и возвращает компактный результат, но может потребовать дополнительной операции сортировки или хеширования. Третий вариант группирует строки по идентификатору клиента; по смыслу он подходит, если одновременно нужны агрегаты, но для одного списка обычно избыточен.

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

Что кандидаты часто упускают

  1. Может ли оптимизатор убрать DISTINCT, если выбранный столбец уникален?

Да, если из ограничений схемы и условий запроса доказано, что повторяющиеся результирующие строки невозможны. Например, проекция уникального ключа таблицы уже не создаёт дубликатов. Удаление логически избыточного шага сохраняет результат, но зависит от того, какую информацию об уникальности способен вывести оптимизатор.

  1. Почему результат проекции может стать меньше исходного даже без фильтрации строк?

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

  1. Почему добавление столбца в проекцию может вернуть дубликаты, исчезнувшие раньше?

Дубликаты определяются по всей результирующей строке. Если два исходных кортежа совпадают по customer_id, но различаются по order_date, проекция только на customer_id объединит их, а проекция на пару customer_id и order_date сохранит обе строки, если даты различаются. Следовательно, изменение списка выбранных атрибутов меняет не только форму результата, но и возможность устранения совпадающих кортежей.