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