Как исправить ошибку, когда «плывут» итоги по остаткам и оборотам в СКД?

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

Разработка отчетов в Системе компоновки данных (СКД) на основе виртуальных таблиц остатков и оборотов — это стандартная задача для любого программиста 1С. Однако очень часто разработчики сталкиваются с крайне неприятной проблемой: начальный и конечный остатки отображаются абсолютно корректно до тех пор, пока в отчет не добавляются оборотные поля (приход, расход или вычисляемые на их основе показатели). Как только в выборку попадает оборотное поле или детальный период, общие итоги по остаткам начинают «плыть» — удваиваться, суммироваться или превращаться в астрономические числа.

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

Выясняем причину: почему «плывут» итоги?

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


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

Проблема кроется в использовании поля Регистратор в условии ВЫБОР КОГДА. Даже если мы не выводим само поле Регистратор в структуру отчета, СКД анализирует схему запроса и видит зависимость вычисляемого поля КоличествоВозврат от регистратора. Чтобы рассчитать это поле, СКД вынуждена получить из СУБД детальные записи на уровне документов движения.

Когда платформа получает детальные записи (на уровне регистратора), для каждой такой строки СУБД возвращает значения начального и конечного остатков на момент проведения этого документа. Когда СКД пытается свернуть эти детальные записи до уровня группировки ПериодНеделя, встроенный компоновщик итогов без дополнительных указаний не понимает, в какой хронологической последовательности шли документы внутри недели. Вместо того чтобы взять начальный остаток самого первого документа недели и конечный остаток самого последнего, СКД просто складывает остатки по всем строкам детальных записей. Итоги «плывут».

Рассмотрим подробнее три основных способа решения этой проблемы.

---

Способ 1. Настройка непрерывной шкалы и ролей периодов

Если нам обязательно нужно получать детальные данные (например, регистратор или секунды) и при этом корректно сворачивать их до более крупных периодов (неделя, месяц), мы должны явно объяснить СКД иерархию и порядок наших периодов. Это делается на вкладке «Наборы данных» в конструкторе схемы компоновки данных.

Для того чтобы СКД правильно рассчитывала остатки, необходимо соблюдать золотые правила настройки ролей:

  1. Добавьте вспомогательные периоды в запрос. Если вы используете поле Регистратор, в выборке обязательно должно присутствовать поле самого точного периода на временной шкале — ПериодСекунда (или просто Период для некоторых регистров). Без секундной точности СКД не сможет однозначно выстроить документы, проведенные в один день, по хронологической шкале.
  2. Настройте роль «Период» для всех полей дат. Перейдите на вкладку «Наборы данных», найдите ваши поля периодов и откройте настройки их роли. Всем полям-периодам (Регистратор, ПериодСекунда, ПериодНеделя) необходимо назначить тип роли Период.
  3. Задайте правильный порядок периодов. В настройках роли каждого периода есть числовое поле «Порядок». Этот порядок должен быть непрерывным и распределяться от самого точного периода к самому крупному:
    • Для поля Регистратор укажите Порядок: 1.
    • Для поля ПериодСекунда укажите Порядок: 2.
    • Для поля ПериодНеделя укажите Порядок: 3.
  4. Используйте флаг «Дополнительный». Это ключевой момент. Те поля-периоды, которые не используются в финальной структуре группировок отчета напрямую (например, Регистратор и ПериодСекунда нужны нам только для внутренних расчетов, а в самом отчете мы группируем данные исключительно по ПериодНеделя), должны быть отмечены флагом Дополнительный. Это подскажет СКД, что данные поля служат только для упорядочивания хронологии «под капотом» и не должны принудительно раздувать группировки отчета.

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

---

Способ 2. Разделение запроса через объединение (ОБЪЕДИНИТЬ ВСЕ)

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

Мы можем разделить получение данных на две логические ветки с помощью конструкции ОБЪЕДИНИТЬ ВСЕ:

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

Рассмотрим пример такого запроса:


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

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

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

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

---

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

Еще одна распространенная ошибка программистов — это соединение виртуальных таблиц остатков с другими таблицами (например, справочниками или регистрами сведений) через INNER JOIN или LEFT JOIN непосредственно в тексте запроса. СУБД при соединении таблиц может дублировать строки остатков, из-за чего СКД также не сможет правильно рассчитать итоги.

Правильным архитектурным решением в данном случае будет разделение логики на два набора данных внутри схемы компоновки данных:

  1. НаборДанныхЗапрос1 (Остатки и Обороты): Выбирает чистые данные из регистра накопления (без соединений с аналитикой). Мы получаем только КлючАналитики, остатки, приход и расход.
  2. НаборДанныхЗапрос2 (Аналитика): Выбирает информацию из регистра сведений АналитикаУчетаНоменклатуры, сопоставляя КлючАналитики со Складом и Номенклатурой.

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

---

Борьба с «пустыми строками» при объединении наборов

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

Это происходит из-за того, что СКД пытается сопоставить данные разных веток объединения, но у нее не хватает инструкций для однозначной идентификации строк. Чтобы этого избежать, выполним следующие действия:

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

← На главную