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