При разработке отчетов в 1С программисты часто сталкиваются с классической задачей: объединить в одной таблице оборотные показатели (например, продажи или выручку) и остатки товаров на складах. Ситуация усложняется, когда отчет нужно детализировать по различным периодам — дням, неделям, декадам или месяцам. Если решать эту задачу неоптимально (например, через левое соединение таблиц по датам в «тяжелом» запросе), отчет может перестать работать даже при среднем объеме данных.
В этой статье мы подробно разберем три проверенных и надежных способа реализации этой задачи. Мы пройдем путь от самого быстрого и технологичного метода средствами Системы компоновки данных (СКД) до классического программного метода с использованием нарастающего итога в запросе.
Платформа 1С содержит мощный встроенный функционал расчета остатков и дополнения периодов в СКД. Этот метод работает быстрее всего, так как расчет и сопоставление данных происходят на уровне платформы в оперативной памяти, минуя избыточные тяжелые запросы к базе данных СУБД.
Рассмотрим по шагам, как настроить СКД для автоматического расчета остатков по периодам:
Для реализации этого метода нам понадобится обратиться к виртуальной таблице ОстаткиИОбороты соответствующего регистра накопления (например, ТоварыНаСкладах.ОстаткиИОбороты). В параметрах виртуальной таблицы обязательно укажем периодичность Авто.
Пример структуры запроса:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
Таблица.Номенклатура КАК Номенклатура,
Таблица.Регистратор КАК Регистратор,
Таблица.ПериодСекунда КАК ПериодСекунда,
Таблица.ПериодДень КАК ПериодДень,
Таблица.ПериодНеделя КАК ПериодНеделя,
Таблица.ПериодМесяц КАК ПериодМесяц,
Таблица.В выручкаОборот КАК Выручка,
Таблица.КоличествоКонечныйОстаток КАК КонечныйОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Авто, , ) КАК Таблица
Это ключевой шаг, без которого СКД не сможет корректно рассчитать остатки при группировке. Нам необходимо явно указать роли для всех полей периодов:
Номенклатура устанавливаем роль Измерение.КонечныйОстаток задаем роль Остаток, указываем тип остатка «Конечный» и имя группы (например, «Количество»).Регистратор — роль Период, Номер 1.ПериодСекунда — роль Период, Номер 2.ПериодДень — роль Период, Номер 3.ПериодНеделя — роль Период, Номер 4.ПериодМесяц — роль Период, Номер 5.Если в какой-то из дней продаж не было, стандартный отчет не выведет эту строку. Но остаток товара на складе в этот день все равно существовал! Чтобы СКД автоматически добавила «пустые» дни и показала на них актуальный остаток, настроим дополнение периодов.
Вкладка «Настройки» -> Нажимаем на группировку по периоду (например, ПериодДень) -> В свойствах группировки находим параметр Тип дополнения и устанавливаем значение День. Также указываем начальную и конечную даты.
Используя этот подход, мы перекладываем всю сложную математику расчета остатков на платформу 1С. Отчет будет формироваться быстро и не перегрузит сервер СУБД.
Бывают ситуации, когда данные о выручке и остатках физически находятся в абсолютно разных регистрах (например, выручка — в оборотном регистре ВыручкаИСебестоимостьПродаж, а остатки — в регистре остатков ТоварыНаСкладах). Напрямую соединить их в одном запросе с периодичностью крайне трудно. В этом случае на помощь приходит механизм связей наборов данных в СКД.
Проанализируем этот алгоритм:
Пишем простой запрос для получения выручки с детализацией по периодам.
ВЫБРАТЬ
Продажи.Номенклатура КАК Номенклатура,
Продажи.ПериодДень КАК ПериодДень,
Продажи.СуммаВыручкиОборот КАК Выручка
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК Продажи
Этот набор данных будет отвечать исключительно за получение остатков товаров на конкретную дату. В параметрах виртуальной таблицы укажем параметр &П_ДатаОстатков.
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.В наличииОстаток КАК ОстатокНаДату
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&П_ДатаОстатков, ) КАК Остатки
Переходим на вкладку «Связи наборов данных» и добавляем новую связь:
Номенклатура = Номенклатура.П_ДатаОстатков, Выражение источника — ПериодДень (или ПериодНеделя/ПериодМесяц в зависимости от выбранной пользователем детализации).Важное предупреждение: Данный метод очень удобен в реализации, но имеет ограничение по производительности. Для каждой уникальной даты из первого набора СКД выполнит отдельный запрос к базе данных для получения остатков. На огромных объемах данных это может привести к замедлению работы отчета.
Если перед вами стоит задача разработать отчет без использования механизмов компоновки данных (например, вывод в самописную форму или текстовый табличный документ) либо нужно получить строго плоскую таблицу программным путем, то задачу придется решать математически в самом запросе с помощью вычисления нарастающего итога.
Разберем по шагам логику такого запроса:
Сначала мы получаем остаток на самую начальную дату отчета, а также собираем все обороты (приходы и расходы) в разрезе дней.
Чтобы рассчитать конечный остаток на каждый день, нам нужно просуммировать все движения, которые произошли от начала периода до этого конкретного дня включительно.
Посмотрим на пример практической реализации такого запроса:
// Шаг 1: Получаем все уникальные даты, в которые были движения, или используем производственный календарь
ВЫБРАТЬ РАЗРЕШЕННЫЕ
Продажи.Номенклатура КАК Номенклатура,
Продажи.ПериодДень КАК ПериодДень,
Продажи.КоличествоОборот КАК КоличествоПродаж
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
РегистрНакопления.КнигаПродаж.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК Продажи
;
// Шаг 2: Получаем таблицу всех движений (приходов и расходов) для расчета нарастающего итога
ВЫБРАТЬ
Движения.Номенклатура КАК Номенклатура,
Движения.Период КАК ПериодДень,
СУММА(ВЫБОР
КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
ТОГДА Движения.Количество
ИНАЧЕ -Движения.Количество
КОНЕЦ) КАК РасходПриходОборот
ПОМЕСТИТЬ ВТ_ВсеДвижения
ИЗ
РегистрНакопления.ТоварыНаСкладах КАК Движения
ГДЕ
Движения.Период МЕЖДУ &НачалоПериода И &КонецПериода
ГРУППИРОВАТЬ ПО
Движения.Номенклатура,
Движения.Период
;
// Шаг 3: Соединяем таблицы и рассчитываем нарастающий итог на каждую дату
ВЫБРАТЬ
ВТ_Продажи.Номенклатура КАК Номенклатура,
ВТ_Продажи.ПериодДень КАК ПериодДень,
ВТ_Продажи.КоличествоПродаж КАК Продано,
// Получаем остаток на начало отчета + сумму всех движений до текущей даты
&НачальныйОстатокВсего + СУММА(ЕСТЬNULL(ВТ_ВсеДвиженияДубль.РасходПриходОборот, 0)) КАК ОстатокНаДень
ИЗ
ВТ_Продажи КАК ВТ_Продажи
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВсеДвижения КАК ВТ_ВсеДвиженияДубль
ПО ВТ_Продажи.Номенклатура = ВТ_ВсеДвиженияДубль.Номенклатура
И ВТ_Продажи.ПериодДень >= ВТ_ВсеДвиженияДубль.ПериодДень
ГРУППИРОВАТЬ ПО
ВТ_Продажи.Номенклатура,
ВТ_Продажи.ПериодДень,
ВТ_Продажи.КоличествоПродаж
Анализ метода: Использование неявного соединения таблиц по условию >= (нарастающий итог) создает большую нагрузку на процессор сервера СУБД. Применять этот метод в «чистом» виде рекомендуется только при работе с относительно небольшими выборками товаров или когда построение отчета через СКД по каким-то причинам невозможно.
Мы проанализировали три основных подхода к решению этой непростой архитектурной задачи. Какой же из них выбрать в реальной работе?