Одной из наиболее распространенных проблем при написании сложных запросов в системе «1С:Предприятие 8» является получение дублирующихся (одинаковых) строк. Эта ситуация не только искажает итоговые отчеты, но и существенно увеличивает нагрузку на сервер базы данных. В данной статье мы подробно разберем, почему возникают дубли, как локализовать проблемный участок и какими методами лучше всего оптимизировать запрос.
Когда мы видим в консоли запросов или в отчете повторяющиеся записи, первое, что необходимо сделать — это понять, какое именно соединение приводит к такому результату. Проанализируем типичную ситуацию: у нас есть основная таблица (например, заказы) и несколько левых соединений с регистрами (статусы, оплаты, остатки).
Рассмотрим пошагово процесс поиска «виновника»:
ЛЕВОЕ СОЕДИНЕНИЕ в тексте запроса. Если после удаления очередного соединения дубли исчезли — значит, проблема именно в этой таблице.В представленном в теме примере автор столкнулся с тем, что задвоение строк происходило из-за регистра РегистрСведений.СтатусыОбеспеченияЗаказов. Если этот регистр является периодическим, то обычное ЛЕВОЕ СОЕДИНЕНИЕ выберет все когда-либо установленные статусы для данного заказа.
Чтобы получить только актуальное состояние и избежать дублей, проанализируем правильный подход. Вместо обращения к физической таблице РегистрСведений.СтатусыОбеспеченияЗаказов следует использовать виртуальную таблицу СрезПоследних. Это гарантирует, что для каждого заказа будет выбрана ровно одна, самая свежая запись.
Пример правильного обращения:
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтатусыОбеспеченияЗаказов.СрезПоследних(&Период, ) КАК СтатусыОбеспеченияЗаказов
ПО ТаблЗаказы.ДокЗаказПокупателя = СтатусыОбеспеченияЗаказов.Заказ
Если специфика задачи не позволяет использовать СрезПоследних или если данные задваиваются в результате оборотов, мы можем применить механизм группировки с использованием агрегатных функций, таких как МАКСИМУМ или СУММА. Посмотрим на фрагмент кода, который помог автору темы:
ВЫБРАТЬ
ЗаказПокупателя.Ссылка КАК ДокЗаказПокупателя,
ТаблЗаказы.ПодходитПоУсловиямОтбора КАК ПодходитПоУсловиямОтбора,
ЕСТЬNULL(ДолгиОбороты.СуммаВалРасход, 0) КАК СуммаОплаты,
СтатусыВыполненияЗаказов.Статус КАК СтатусВыполнения,
МАКСИМУМ(СтатусыОбеспеченияЗаказов.Статус) КАК ПС_СтатусОбеспечения
ИЗ
ТаблЗаказы КАК ТаблЗаказы
// ... другие соединения ...
СГРУППИРОВАТЬ ПО
ЗаказПокупателя.Ссылка,
ТаблЗаказы.ПодходитПоУсловиямОтбора,
СтатусыВыполненияЗаказов.Статус,
ЕСТЬNULL(ДолгиОбороты.СуммаВалРасход, 0)
Важный момент: Использование ключевого слова РАЗЛИЧНЫЕ (DISTINCT) часто является «костылем». Оно просто скрывает симптомы, заставляя СУБД фильтровать уже полученный огромный массив данных. Использование СГРУППИРОВАТЬ ПО дает более предсказуемый результат и позволяет корректно обрабатывать числовые показатели (суммировать их, а не просто выбирать уникальные строки).
Как справедливо заметили участники обсуждения, сложные запросы с большим количеством соединений (особенно с регистрами) могут работать крайне медленно или даже приводить к зависанию системы. Проанализируем, как можно улучшить структуру запроса с помощью временных таблиц (ВТ).
Разберем алгоритм оптимизации:
ВТ_СписокЗаказов.ИНДЕКСИРОВАТЬ ПО для поля ДокЗаказПокупателя. Без индекса последующие соединения на больших объемах данных будут неэффективны.Пример структуры с использованием ВТ:
ВЫБРАТЬ
ТаблЗаказы.ДокЗаказПокупателя КАК ДокЗаказПокупателя
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
&ТаблЗаказы КАК ТаблЗаказы
ИНДЕКСИРОВАТЬ ПО
ДокЗаказПокупателя;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Статусы.Заказ КАК Заказ,
МАКСИМУМ(Статусы.Статус) КАК Статус
ПОМЕСТИТЬ ВТ_Статусы
ИЗ
РегистрСведений.СтатусыОбеспеченияЗаказов КАК Статусы
ГДЕ
Статусы.Заказ В (ВЫБРАТЬ ДокЗаказПокупателя ИЗ ВТ_Заказы)
СГРУППИРОВАТЬ ПО
Статусы.Заказ;
В исходном запросе автора часто встречается обращение вида СписокЗаказов.ДокЗаказПокупателя.Номер, СписокЗаказов.ДокЗаказПокупателя.Дата и так далее. Выясним, почему это плохо.
В языке запросов 1С каждое обращение через точку к реквизиту ссылки (если этот реквизит не находится в текущей таблице) порождает неявное левое соединение. Если в списке выбора 15 таких полей, платформа добавит к вашему запросу еще 15 соединений с таблицей документа. Это критически снижает производительность.
Рекомендация: Если вам нужно много полей из документа, лучше один раз явно соединиться с таблицей Документ.ЗаказПокупателя и выбрать все необходимые данные напрямую.
Проанализируем ситуацию, когда один и тот же код выдает разные результаты на разных копиях базы. Причина кроется исключительно в состоянии данных:
Следовательно, при написании запросов всегда нужно исходить из архитектуры метаданных (может ли в этой таблице быть более одной записи на объект?), а не из текущего наполнения конкретной базы данных.
Подводя итог, можно сказать: для устранения дублей всегда используйте виртуальные таблицы там, где это возможно, не пренебрегайте группировкой и старайтесь переходить к структуре с временными таблицами для повышения читаемости и скорости работы кода.