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

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

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

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

Анализ проблемы: почему происходит задвоение

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


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

Выясним причину некорректного поведения:

  1. Отсутствие связи по периодам между таблицами. Обе таблицы ОстаткиИОбороты возвращают строки, разбитые по месяцам. Если выбран интервал в 2 месяца, то таблица амортизации вернет 2 строки на одно основное средство, и таблица стоимости вернет 2 строки. Так как соединение выполняется только по полям ОсновноеСредство и Организация без условия равенства периодов, происходит декартово произведение строк: 2 строки соединяются с 2 строками, давая на выходе 4 записи вместо двух.
  2. Специфика расчета ресурсов в СКД. Для полей остатков в схеме компоновки данных настраиваются специальные роли (НачальныйОстаток и КонечныйОстаток). СКД берет остаток на начало минимального периода и на конец максимального, нивелируя дубли строк. Однако для оборотов роли остатка не существует — к ним применяется функция Сумма(), которая суммирует размноженные декартовым произведением строки.

Разберем способы устранения данной проблемы.

Способ 1. Получение данных из одной виртуальной таблицы с разделением через ВЫБОР (рекомендуемый)

Так как счет стоимости (01) и счет амортизации (02) находятся в рамках одного регистра бухгалтерии, наиболее оптимальным решением является отказ от соединения двух одинаковых таблиц. Мы можем получить все данные одним обращением к таблице ОстаткиИОбороты, передав в параметрах массив счетов и разделив показатели с помощью конструкции ВЫБОР.

Посмотрим на пример запроса:


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

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

Способ 2. Объединение выборок через ОБЪЕДИНИТЬ ВСЕ

Если по логике задачи необходимо разделить выборки по разным счетам или регистрам, используем объединение строк вместо соединения:


ВЫБРАТЬ
    ВЫРАЗИТЬ(СтоимостьОС.Субконто1 КАК Справочник.ОсновныеСредства) КАК ОсновноеСредство,
    СтоимостьОС.Организация КАК Организация,
    СтоимостьОС.Период КАК Период,
    СтоимостьОС.СуммаНачальныйОстатокДт КАК СтоимостьНач,
    СтоимостьОС.СуммаОборотДт КАК СтоимостьДт,
    СтоимостьОС.СуммаКонечныйОстатокДт КАК СтоимостьКон,
    0 КАК АмортизацияНач,
    0 КАК АмортизацияКт,
    0 КАК АмортизацияКон
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(&ПериодНач, &ПериодКон, Месяц, , Счет В (&СчетСтоимости), &СубконтоОС, ) КАК СтоимостьОС

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

ВЫБРАТЬ
    ВЫРАЗИТЬ(Амортизация.Субконто1 КАК Справочник.ОсновныеСредства),
    Амортизация.Организация,
    Амортизация.Период,
    0,
    0,
    0,
    Амортизация.СуммаНачальныйОстатокКт,
    Амортизация.СуммаОборотКт,
    Амортизация.СуммаКонечныйОстатокКт
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(&ПериодНач, &ПериодКон, Месяц, , Счет В (&СчетАморт), &СубконтоОС, ) КАК Амортизация

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

Способ 3. Использование нескольких наборов данных в СКД

Система компоновки данных позволяет разделить получение данных на отдельные независимые наборы-запросы:

  1. НаборДанных1 (Объекты): срез последних регистра сведений ПервоначальныеСведенияОСБухгалтерскийУчет.
  2. НаборДанных2 (Стоимость): таблица ОстаткиИОбороты по счету учета стоимости.
  3. НаборДанных3 (Амортизация): таблица ОстаткиИОбороты по счету амортизации.

На вкладке «Связи наборов данных» связываем НаборДанных1 с остальными наборами по полям ОсновноеСредство и Организация. В таком сценарии СКД сама свяжет показатели и сгруппирует их по колонкам периодов без декартова умножения.

Настройка ролей полей в СКД

Разберем по шагам правила настройки ролей на вкладке «Наборы данных» для корректного отображения в отчете-таблице (разворот колонок по периодам):

  1. Для поля Период устанавливаем флажок Период и задаем порядковый номер (например, 1). Это позволит СКД распознавать поле как дополнительный период отчета.
  2. Для полей начальных остатков задаем роль остатка НачальныйОстаток с указанием имени группы (например, Стоимость для стоимости и Амортизация для амортизации).
  3. Для полей конечных остатков задаем роль остатка КонечныйОстаток с тем же именем группы.
  4. Для полей оборотов СтоимостьДт и АмортизацияКт роль остатка не указывается.
  5. На вкладке «Ресурсы» для всех числовых полей указываем агрегатные функции: для оборотов — Сумма(СтоимостьДт), а для полей остатков СКД автоматически сформирует выражения расчета с учетом ролей.
← На главную