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

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

Каждый разработчик на платформе 1С рано или поздно сталкивается с неприятной ситуацией: написанный запрос вместо ожидаемого уникального списка строк возвращает дублирующиеся данные. Например, вместо одной строки «Иванов 1324» мы получаем две абсолютно идентичные строки подряд. При этом стандартные проверки итогов и соединений на первый взгляд не дают результатов.

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

Выясняем причину: почему запрос начинает «множить» строки?

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

  1. Связь «один ко многим» при соединениях (JOIN). Если мы соединяем уникальную таблицу (например, справочник контрагентов или срез последних регистра сведений) с таблицей, которая содержит несколько записей по ключевому полю связи (например, табличная часть документа или обороты регистра), то строка из первой таблицы продублируется ровно столько раз, сколько соответствий будет найдено во второй таблице.
  2. Неполное условие связи в секции ПО. При соединении таблиц по составному ключу (например, когда в регистре измерением является не только договор, но и организация или спецификация) отсутствие одного из измерений в условии связи неминуемо приведет к декартову произведению строк по неуказанным полям.
  3. Наличие нескольких регистраторов. Если в срезе последних регистра сведений или в периодическом регистре по одному объекту (например, по основному средству) существует несколько разных документов-регистраторов, простое соединение по объекту без учета актуального регистратора также размножит строки в финальной выборке.

Анализируем исходный запрос

Давайте подробно рассмотрим пакет запросов, с которым возникла проблема у автора темы. В пакете последовательно формируются временные таблицы:

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


ВЫБРАТЬ
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка КАК Ссылка,
    СУММА(ЛК_ГрафикДоговораЛизингОсновныеДанные.СуммаПлатежа) КАК СуммаПлатежа,
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка.МесяцевДоговора КАК МесяцевДоговора,
    СУММА(ВЫБОР
            КОГДА ЛК_ГрафикДоговораЛизингОсновныеДанные.НомерСтроки = 1
                ТОГДА ЛК_ГрафикДоговораЛизингОсновныеДанные.СуммаПлатежа
            ИНАЧЕ ЛК_ГрафикДоговораЛизингОсновныеДанные.СуммаПлатежа
        КОНЕЦ) КАК АвансРуб,
    ЛК_ГрафикДоговораЛизингОсновныеДанные.ДатаПлатежа КАК ДатаПлатежа
ПОМЕСТИТЬ вр_доп
ИЗ
    Документ.ЛК_ГрафикДоговораЛизинг.ОсновныеДанные КАК ЛК_ГрафикДоговораЛизингОсновныеДанные
ГДЕ
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка В
            (ВЫБРАТЬ
                вр_Осн.Регистратор
            ИЗ
                вр_Осн КАК вр_Осн)
    И ЛК_ГрафикДоговораЛизингОсновныеДанные.ДатаПлатежа <= &Дата
    И ЛК_ГрафикДоговораЛизингОсновныеДанные.НомерСтроки > 0
 СГРУППИРОВАТЬ ПО
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка,
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка.МесяцевДоговора,
    ЛК_ГрафикДоговораЛизингОсновныеДанные.ДатаПлатежа

Обратите внимание на секцию СГРУППИРОВАТЬ ПО. Запрос группирует данные по полям Ссылка, МесяцевДоговора и ДатаПлатежа. Из-за наличия поля ДатаПлатежа в группировке, для одного документа (для одной Ссылка) во временной таблице вр_доп будет создано столько строк, сколько уникальных дат платежей содержится в его табличной части на выбранный период.

Теперь посмотрим, как эта таблица соединяется в финальном запросе пакета:


ИЗ
    вр_Осн КАК вр_Осн
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ вр_доп КАК вр_доп
        ПО вр_Осн.Регистратор.Ссылка = вр_доп.Ссылка

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

Дополнительным фактором дублирования (как справедливо замечено в сообщении 21) может выступать ситуация, когда у одного основного средства в системе зарегистрировано несколько документов-регистраторов (например, несколько версий графиков лизинга). В этом случае левое соединение без явного отбора актуального среза по регистратору также порождает дубли.

Решение 1: Правильная агрегация данных (свертка временной таблицы)

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

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


ВЫБРАТЬ
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка КАК Ссылка,
    СУММА(ЛК_ГрафикДоговораЛизингОсновныеДанные.СуммаПлатежа) КАК СуммаПлатежа,
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка.МесяцевДоговора КАК МесяцевДоговора,
    СУММА(ЛК_ГрафикДоговораЛизингОсновныеДанные.СуммаПлатежа) КАК АвансРуб,
    МАКСИМУМ(ЛК_ГрафикДоговораЛизингОсновныеДанные.ДатаПлатежа) КАК ДатаПлатежа
ПОМЕСТИТЬ вр_доп
ИЗ
    Документ.ЛК_ГрафикДоговораЛизинг.ОсновныеДанные КАК ЛК_ГрафикДоговораЛизингОсновныеДанные
ГДЕ
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка В
            (ВЫБРАТЬ
                вр_Осн.Регистратор
            ИЗ
                вр_Осн КАК вр_Осн)
    И ЛК_ГрафикДоговораЛизингОсновныеДанные.ДатаПлатежа <= &Дата
    И ЛК_ГрафикДоговораЛизингОсновныеДанные.НомерСтроки > 0
СГРУППИРОВАТЬ ПО
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка,
    ЛК_ГрафикДоговораЛизингОсновныеДанные.Ссылка.МесяцевДоговора

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

Решение 2: Соединение по полному набору измерений

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

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


ВЫБРАТЬ
    вр_Осн.ОсновноеСредство КАК ОсновноеСредство,
    вр_Осн.Регистратор КАК Регистратор,
    ...
    вр_доп.СуммаПлатежа КАК СуммаПлатежа,
    вр_доп.ДатаПлатежа КАК ДатаПлатежа
ИЗ
    вр_Осн КАК вр_Осн
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ вр_доп КАК вр_доп
        ПО вр_Осн.Регистратор.Ссылка = вр_доп.Ссылка

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

Решение 3: Очистка промежуточных дублей с помощью РАЗЛИЧНЫЕ

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

Рекомендуется использовать ключевое слово РАЗЛИЧНЫЕ (в SQL-эквиваленте — DISTINCT) для получения уникального набора ключей связи:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ЛК_ЛизингополучательОС.ДоговорКонтрагента КАК ДоговорКонтрагента,
    ЛК_ЛизингополучательОС.Лизингополучатель КАК Лизингополучатель,
    ЛК_ЛизингополучательОС.ОсновноеСредство КАК ОсновноеСредство
ПОМЕСТИТЬ вр_Договора
ИЗ
    РегистрСведений.ЛК_ЛизингополучательОС КАК ЛК_ЛизингополучательОС
...

Использование РАЗЛИЧНЫЕ гарантирует, что при последующих соединениях с таблицей вр_Договора мы не получим лавинообразного размножения результатов из-за повторяющихся строк-связок.

Решение 4: Постобработка результата (удаление дублей в Таблице Значений)

Иногда, в силу сложности унаследованного кода или высокой стоимости переписывания тяжелых динамических запросов, разработчики прибегают к постобработке полученного результата программными методами платформы 1С. Этот метод считается менее предпочтительным с точки зрения производительности СУБД, но крайне прост в реализации.

Для удаления дублей достаточно выгрузить результат запроса в объект типа ТаблицаЗначений и применить метод Свернуть():


// Выполняем запрос и выгружаем результат
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();

// Сворачиваем таблицу по колонкам, определяющим уникальность строки,
// с указанием суммируемых колонок
ТаблицаРезультата.Свернуть(
    "ОсновноеСредство, Регистратор, Контрагент, ДоговорНаименование", // Колонки группировки
    "СуммаПлатежа, Остаток, Всего"                                  // Колонки суммирования
);

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

Рекомендации по отладке сложных запросов

Чтобы быстро локализовать место возникновения дублей в пакете запросов, используйте следующий алгоритм:

  1. Откройте исследуемый запрос в специализированной Консоли запросов 1С.
  2. Вместо выполнения всего пакета целиком, поочередно выгружайте содержимое каждой временной таблицы. Для этого временно заменяйте финальный запрос на выборку из исследуемой таблицы: например, ВЫБРАТЬ * ИЗ вр_Договора.
  3. Анализируйте количество строк на каждом шаге. Если на шаге А у вас было 50 строк, а после соединения на шаге Б стало 150 — дублирование происходит именно в месте соединения этих двух таблиц. Проверьте условия связи ПО и уникальность данных в присоединяемой таблице.
← На главную