Как исправить задвоение строк и некорректное суммирование в запросах 1С при работе с регистрами и документами?

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

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

Анализ проблемы задвоения строк и включения данных из некорректного периода

Начнем с исходного запроса пользователя, который столкнулся с двумя основными вопросами: почему его запрос задваивает строки и почему в результат попадают данные за март, хотя в параметрах указывался февраль.

Исходный запрос, вызывающий проблемы:


ВЫБРАТЬ
    ЗаданияНаРаботу.Контрагент КАК Контрагент,
    ЗаданияНаРаботу.Регистратор КАК Регистратор
ИЗ
    РегистрНакопления.ЗаданияНаРаботу КАК ЗаданияНаРаботу
ГДЕ
    ЗаданияНаРаботу.Период МЕЖДУ &ДатаНачала И &ДатаОкончания
    И ВЫБОР
        КОГДА &Контрагент <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
            ТОГДА ЗаданияНаРаботу.Контрагент = &Контрагент
        ИНАЧЕ ИСТИНА
    КОНЕЦ
АВТОУПОРЯДОЧИВАНИЕ

Нам предстоит выяснить, почему этот запрос ведет себя таким образом. Давайте проведем пошаговую диагностику.

Шаги диагностики:

  1. Проверяем сами записи регистра накопления:

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

    
    ВЫБРАТЬ
        *
    ИЗ
        РегистрНакопления.ЗаданияНаРаботу КАК ЗаданияНаРаботу
    ГДЕ
        ЗаданияНаРаботу.Период МЕЖДУ &ДатаНачала И &ДатаОкончания
        И ВЫБОР
            КОГДА &Контрагент <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
                ТОГДА ЗаданияНаРаботу.Контрагент = &Контрагент
            ИНАЧЕ ИСТИНА
        КОНЕЦ
    АВТОУПОРЯДОЧИВАНИЕ
    

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

  2. Анализируем поле Период регистра и Дату документа:

    Критически важно для понимания проблем с периодами является четкое различение между Датой документа-регистратора и полем Период в регистре накопления. В системе 1С документ может быть проведен, например, 2 марта, но формировать движения в регистре с полем Период, относящимся к 28 февраля. Такая ситуация типична, когда документ закрывает или корректирует данные за предыдущий месяц. Если запрос фильтрует данные по полю ЗаданияНаРаботу.Период, а мы ожидаем фильтрацию по дате самого документа, это неизбежно приведет к расхождениям.

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

    
    ВЫБРАТЬ
        ЗаданияНаРаботу.Контрагент КАК Контрагент,
        ЗаданияНаРаботу.Регистратор.Дата КАК ДатаРегистратора,
        ЗаданияНаРаботу.Период КАК ПериодЗаписиРегистра
    ИЗ
        РегистрНакопления.ЗаданияНаРаботу КАК ЗаданияНаРаботу
    ГДЕ
        ЗаданияНаРаботу.Период МЕЖДУ &ДатаНачала И &ДатаОкончания
        И ВЫБОР
            КОГДА &Контрагент <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
                ТОГДА ЗаданияНаРаботу.Контрагент = &Контрагент
            ИНАЧЕ ИСТИНА
        КОНЕЦ
    АВТОУПОРЯДОЧИВАНИЕ
    

    В ходе анализа результатов такого запроса выяснилось, что в обсуждаемой ситуации документ, датированный 02.03.2021, формировал движения в регистре с полем Период, относящимся к февралю (например, 28.02.2021). Таким образом, если в параметрах запроса мы указываем интервал за февраль (например, &ДатаНачала = 01.02.2021 и &ДатаОкончания = 28.02.2021), то записи, сформированные "мартовским" документом, но имеющие "февральский" Период, будут совершенно правомерно включены в результат. Это полностью объясняет, почему данные за "март" оказались в выборке за "февраль".

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

Решение проблемы завышенной суммы при соединении таблиц

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

Пример проблемного запроса:


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

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

Причина проблемы "размножения" данных:

Проблема завышения суммы при использовании ЛЕВОЕ СОЕДИНЕНИЕ (или любого другого типа соединения) с функцией СУММА называется "размножением" данных. Это происходит, когда одна строка из "левой" таблицы (в нашем случае, из регистра ЗаданияНаРаботу) может быть соединена с несколькими строками из "правой" таблицы (Документ.ПоступлениеВКассу) по условию соединения. Например, если для одного и того же Контрагента в регистре ЗаданияНаРаботу имеется несколько записей (например, 2 записи), и при этом для этого же Контрагента существует несколько документов ПоступлениеВКассу (например, 2 документа) за указанный период, то произойдет следующее:

В результате, каждая сумма из ПоступлениеВКассу будет добавлена не один раз, а столько раз, сколько уникальных строк регистра ЗаданияНаРаботу по этому контрагенту, умноженное на количество документов ПоступлениеВКассу. В нашем примере 2 записи регистра * 2 документа оплаты = 4 соединения. Таким образом, каждая сумма оплаты будет учтена четырежды, что и приведет к четырехкратному завышению итоговой суммы.

Эффективное решение: Использование временных таблиц и предварительной агрегации

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

  1. Создаем временную таблицу с уникальными контрагентами из регистра:

    Начнем с того, что выберем уникальные комбинации Контрагент и Организация из регистра ЗаданияНаРаботу, которые соответствуют нашим условиям по периоду. Важно использовать ключевое слово РАЗЛИЧНЫЕ или СГРУППИРОВАТЬ ПО, чтобы избежать дублирования контрагентов уже на этом первом шаге.

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

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

  2. Агрегируем суммы из документов оплаты во временную таблицу:

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

    
    ВЫБРАТЬ
        ПоступлениеВКассу.Контрагент КАК Контрагент,
        СУММА(ПоступлениеВКассу.СуммаДокумента) КАК СуммаОплаты
    ПОМЕСТИТЬ ВТ_Оплаты
    ИЗ
        Документ.ПоступлениеВКассу КАК ПоступлениеВКассу
    ГДЕ
        ПоступлениеВКассу.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
        И ПоступлениеВКассу.Организация = &Организация
    СГРУППИРОВАТЬ ПО
        ПоступлениеВКассу.Контрагент
    ;
    

    Теперь ВТ_Оплаты будет содержать уникальные контрагенты с уже корректно просуммированными по ним оплатами.

  3. Соединяем результаты из временных таблиц:

    На заключительном этапе мы соединяем нашу первую временную таблицу ВТ_Задания, содержащую уникальные комбинации контрагентов и организаций из регистра заданий, со второй временной таблицей ВТ_Оплаты, содержащей агрегированные суммы оплат.

    
    ВЫБРАТЬ
        ВТ_Задания.Контрагент КАК Контрагент,
        ВТ_Задания.Организация КАК Организация,
        ЕСТЬNULL(ВТ_Оплаты.СуммаОплаты, 0) КАК Сумма
    ИЗ
        ВТ_Задания КАК ВТ_Задания
            ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Оплаты КАК ВТ_Оплаты
            ПО ВТ_Задания.Контрагент = ВТ_Оплаты.Контрагент
    АВТОУПОРЯДОЧИВАНИЕ
    

    В этом итоговом запросе мы получаем нужные нам Контрагент и Организация из первой временной таблицы и присоединяем к ним уже корректно просуммированные оплаты. Мы используем функцию ЕСТЬNULL(ВТ_Оплаты.СуммаОплаты, 0), чтобы в случае отсутствия оплат по конкретному контрагенту (поскольку это ЛЕВОЕ СОЕДИНЕНИЕ), сумма отображалась как 0, а не NULL.

Важные замечания по фильтрации периодов:

В ходе обсуждения подобных запросов часто возникает важный вопрос о логике фильтрации по периодам: "почему мы отбираем и задания по периоду, и даты оплаты по периоду? А если задания были выполнены в феврале, а оплачены в марте?". Это критический аспект, который требует четкого определения бизнес-логики отчета:

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

Заключение

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

Мы рекомендуем всегда тщательно анализировать структуру ваших данных, понимать логику их формирования документами и пошагово отлаживать свои запросы в консоли или конструкторе запросов 1С. Такой подход позволит вам выявить корневые причины ошибок и построить точные и эффективные запросы, которые будут возвращать ожидаемый результат.

← На главную