Почему в запросе 1С не исключаются дубли при объединении таблиц?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей IT и автоматизация бизнеса
← На главную

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

Проанализируем исходный текст запроса, который вызвал появление одинаковых записей:


ВЫБРАТЬ
    ПартнерыКонтактнаяИнформация.Ссылка
ПОМЕСТИТЬ Партнеры
ИЗ
    Справочник.Партнеры.КонтактнаяИнформация КАК ПартнерыКонтактнаяИнформация
ГДЕ
    ПартнерыКонтактнаяИнформация.АдресЭП = &АдресЭП

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    КонтрагентыКонтактнаяИнформация.Ссылка.Партнер.Ссылка
ИЗ
    Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКонтактнаяИнформация
ГДЕ
    КонтрагентыКонтактнаяИнформация.АдресЭП = &АдресЭП
;

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

Причина поведения: разница между ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ

Главная ошибка кроется в использовании конструкции ОБЪЕДИНИТЬ ВСЕ. В языке запросов платформы 1С:Предприятие эти операторы работают следующим образом:

  1. ОБЪЕДИНИТЬ ВСЕ — транслируется в SQL-операцию UNION ALL. Она соединяет результаты двух или нескольких выборок «как есть», не выполняя проверку на уникальность строк. Если один и тот же партнер был найден в первой выборке и во второй выборке, обе строки попадают в результирующий набор.
  2. ОБЪЕДИНИТЬ — транслируется в SQL-операцию UNION. Эта конструкция выполняет неявную сортировку и фильтрацию повторяющихся строк по всем полям результирующего набора, оставляя только уникальные комбинации.

Таким образом, для автоматического исключения повторяющихся партнеров достаточно убрать слово ВСЕ.

Способ 1. Использование оператора ОБЪЕДИНИТЬ

Посмотрим на исправленный вариант запроса. Заменяем составную конструкцию на стандартное объединение:


ВЫБРАТЬ
    ПартнерыКонтактнаяИнформация.Ссылка КАК Партнер
ПОМЕСТИТЬ Партнеры
ИЗ
    Справочник.Партнеры.КонтактнаяИнформация КАК ПартнерыКонтактнаяИнформация
ГДЕ
    ПартнерыКонтактнаяИнформация.АдресЭП = &АдресЭП

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    КонтрагентыКонтактнаяИнформация.Ссылка.Партнер
ИЗ
    Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКонтактнаяИнформация
ГДЕ
    КонтрагентыКонтактнаяИнформация.АдресЭП = &АдресЭП
;

Обратим внимание на избыточное разыменование: в исходном коде присутствовала конструкция КонтрагентыКонтактнаяИнформация.Ссылка.Партнер.Ссылка. Реквизит Партнер справочника Контрагенты уже сам по себе является ссылкой типа СправочникСсылка.Партнеры, поэтому повторное обращение к полю Ссылка не требуется.

Способ 2. Использование РАЗЛИЧНЫЕ внутри отдельных выборок

В некоторых случаях у одного и того же партнера или контрагента может быть зарегистрировано несколько одинаковых адресов электронной почты (например, в разных видах контактной информации). Чтобы оптимизировать промежуточный объем данных до объединения, можно добавить ключевое слово РАЗЛИЧНЫЕ в секции ВЫБРАТЬ:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ПартнерыКонтактнаяИнформация.Ссылка КАК Партнер
ПОМЕСТИТЬ Партнеры
ИЗ
    Справочник.Партнеры.КонтактнаяИнформация КАК ПартнерыКонтактнаяИнформация
ГДЕ
    ПартнерыКонтактнаяИнформация.АдресЭП = &АдресЭП

ОБЪЕДИНИТЬ

ВЫБРАТЬ РАЗЛИЧНЫЕ
    КонтрагентыКонтактнаяИнформация.Ссылка.Партнер
ИЗ
    Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКонтактнаяИнформация
ГДЕ
    КонтрагентыКонтактнаяИнформация.АдресЭП = &АдресЭП
;

Способ 3. Группировка объединенных данных (оптимизация производительности)

На высоконагруженных базах данных или при обработке больших объемов записей операция ОБЪЕДИНИТЬ (SQL UNION) может выполняться медленнее, чем связка ОБЪЕДИНИТЬ ВСЕ с последующей группировкой. Оптимизатор СУБД часто строит более эффективные планы выполнения (через хеширование) для агрегатных операций.

Разберем, как реализовать этот подход в два шага через пакет запросов:


ВЫБРАТЬ
    ПартнерыКонтактнаяИнформация.Ссылка КАК Партнер
ПОМЕСТИТЬ ВТ_ВсеПартнеры
ИЗ
    Справочник.Партнеры.КонтактнаяИнформация КАК ПартнерыКонтактнаяИнформация
ГДЕ
    ПартнерыКонтактнаяИнформация.АдресЭП = &АдресЭП

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    КонтрагентыКонтактнаяИнформация.Ссылка.Партнер
ИЗ
    Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКонтактнаяИнформация
ГДЕ
    КонтрагентыКонтактнаяИнформация.АдресЭП = &АдресЭП
;

ВЫБРАТЬ
    ВТ_ВсеПартнеры.Партнер КАК Партнер
ПОМЕСТИТЬ Партнеры
ИЗ
    ВТ_ВсеПартнеры КАК ВТ_ВсеПартнеры
ГДЕ
    ВТ_ВсеПартнеры.Партнер ЕСТЬ НЕ NULL
    И ВТ_ВсеПартнеры.Партнер <> ЗНАЧЕНИЕ(Справочник.Партнеры.ПустаяСсылка)
СГРУППИРОВАТЬ ПО
    ВТ_ВсеПартнеры.Партнер
;

Важные нюансы при работе с контактной информацией

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

  1. Пустые значения и разыменование через точку: Конструкция КонтрагентыКонтактнаяИнформация.Ссылка.Партнер порождает неявное левое соединение (LEFT JOIN). Если контрагент не привязан к партнеру, поле вернет значение NULL. Рекомендуется отсекать пустые и неопределенные значения условием ЕСТЬ НЕ NULL.
  2. Регистр сведений «Контактная информация»: В современных типовых конфигурациях на базе БСП («1С:ERP», «1С:Комплексная автоматизация», «1С:Управление торговлей 11») данные хранятся не в табличных частях справочников, а в независимом регистре сведений КонтактнаяИнформация. При разработке в актуальных конфигурациях целесообразно строить запрос к этому регистру либо использовать функции программного интерфейса общего модуля УправлениеКонтактнойИнформацией.
  3. Регистрозависимость сравнения строк: Поиск по условию АдресЭП = &АдресЭП может не найти адреса, введенные с заглавных букв (например, на СУБД PostgreSQL с чувствительностью к регистру). Для надежности используем функцию приведения: ВРЕГ(АдресЭП) = ВРЕГ(&АдресЭП).
← На главную