Почему в результате запроса 1С появляются одинаковые строки и как это исправить?

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

Одной из наиболее распространенных проблем при написании сложных запросов в системе «1С:Предприятие 8» является получение дублирующихся (одинаковых) строк. Эта ситуация не только искажает итоговые отчеты, но и существенно увеличивает нагрузку на сервер базы данных. В данной статье мы подробно разберем, почему возникают дубли, как локализовать проблемный участок и какими методами лучше всего оптимизировать запрос.

Выясняем причину задвоения: метод исключения

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

Рассмотрим пошагово процесс поиска «виновника»:

  1. Последовательное отключение таблиц. Попробуем поочередно комментировать разделы ЛЕВОЕ СОЕДИНЕНИЕ в тексте запроса. Если после удаления очередного соединения дубли исчезли — значит, проблема именно в этой таблице.
  2. Анализ данных в проблемной таблице. Чаще всего задвоение происходит из-за того, что в таблице, к которой мы присоединяемся, содержится более одной записи на одно ключевое поле (например, на один документ заказа).
  3. Проверка типов таблиц. Если мы соединяемся с физической таблицей периодического регистра сведений, то на одну ссылку объекта может приходиться множество записей за разные даты (история изменений).

Типичная ошибка: соединение с физической таблицей регистра

В представленном в теме примере автор столкнулся с тем, что задвоение строк происходило из-за регистра РегистрСведений.СтатусыОбеспеченияЗаказов. Если этот регистр является периодическим, то обычное ЛЕВОЕ СОЕДИНЕНИЕ выберет все когда-либо установленные статусы для данного заказа.

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

Пример правильного обращения:


ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтатусыОбеспеченияЗаказов.СрезПоследних(&Период, ) КАК СтатусыОбеспеченияЗаказов
ПО ТаблЗаказы.ДокЗаказПокупателя = СтатусыОбеспеченияЗаказов.Заказ

Использование агрегатных функций и группировки

Если специфика задачи не позволяет использовать СрезПоследних или если данные задваиваются в результате оборотов, мы можем применить механизм группировки с использованием агрегатных функций, таких как МАКСИМУМ или СУММА. Посмотрим на фрагмент кода, который помог автору темы:


ВЫБРАТЬ
    ЗаказПокупателя.Ссылка КАК ДокЗаказПокупателя,
    ТаблЗаказы.ПодходитПоУсловиямОтбора КАК ПодходитПоУсловиямОтбора,
    ЕСТЬNULL(ДолгиОбороты.СуммаВалРасход, 0) КАК СуммаОплаты,
    СтатусыВыполненияЗаказов.Статус КАК СтатусВыполнения,
    МАКСИМУМ(СтатусыОбеспеченияЗаказов.Статус) КАК ПС_СтатусОбеспечения
ИЗ
    ТаблЗаказы КАК ТаблЗаказы
    // ... другие соединения ...
СГРУППИРОВАТЬ ПО
    ЗаказПокупателя.Ссылка,
    ТаблЗаказы.ПодходитПоУсловиямОтбора,
    СтатусыВыполненияЗаказов.Статус,
    ЕСТЬNULL(ДолгиОбороты.СуммаВалРасход, 0)

Важный момент: Использование ключевого слова РАЗЛИЧНЫЕ (DISTINCT) часто является «костылем». Оно просто скрывает симптомы, заставляя СУБД фильтровать уже полученный огромный массив данных. Использование СГРУППИРОВАТЬ ПО дает более предсказуемый результат и позволяет корректно обрабатывать числовые показатели (суммировать их, а не просто выбирать уникальные строки).

Оптимизация через временные таблицы

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

Разберем алгоритм оптимизации:

  1. Предварительный отбор. Поместим основной список заказов во временную таблицу ВТ_СписокЗаказов.
  2. Индексация. Обязательно укажем ИНДЕКСИРОВАТЬ ПО для поля ДокЗаказПокупателя. Без индекса последующие соединения на больших объемах данных будут неэффективны.
  3. Получение данных из регистров. Выберем данные из каждого регистра в отдельные временные таблицы, сразу применяя там группировку или отбор по актуальным данным.
  4. Финальное соединение. В итоговом запросе соединим «легкие» временные таблицы.

Пример структуры с использованием ВТ:


ВЫБРАТЬ
    ТаблЗаказы.ДокЗаказПокупателя КАК ДокЗаказПокупателя
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
    &ТаблЗаказы КАК ТаблЗаказы
ИНДЕКСИРОВАТЬ ПО
    ДокЗаказПокупателя;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Статусы.Заказ КАК Заказ,
    МАКСИМУМ(Статусы.Статус) КАК Статус
ПОМЕСТИТЬ ВТ_Статусы
ИЗ
    РегистрСведений.СтатусыОбеспеченияЗаказов КАК Статусы
ГДЕ
    Статусы.Заказ В (ВЫБРАТЬ ДокЗаказПокупателя ИЗ ВТ_Заказы)
СГРУППИРОВАТЬ ПО
    Статусы.Заказ;

Проблема «разыменования» полей через точку

В исходном запросе автора часто встречается обращение вида СписокЗаказов.ДокЗаказПокупателя.Номер, СписокЗаказов.ДокЗаказПокупателя.Дата и так далее. Выясним, почему это плохо.

В языке запросов 1С каждое обращение через точку к реквизиту ссылки (если этот реквизит не находится в текущей таблице) порождает неявное левое соединение. Если в списке выбора 15 таких полей, платформа добавит к вашему запросу еще 15 соединений с таблицей документа. Это критически снижает производительность.

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

Почему на одной базе запрос работает, а на другой — нет?

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

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

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

← На главную