Как убрать лишние строки и правильно рассчитать остатки при использовании метода дополнения «ДвиженияИГраницыПериода» в запросах 1С?

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

При работе с виртуальной таблицей ОстаткиИОбороты в платформе «1С:Предприятие» разработчики часто сталкиваются со специфическим поведением системы, когда в результирующей таблице запроса появляются «лишние» строки с пустым регистратором, но заполненными остатками. Особенно остро этот вопрос встает при использовании метода дополнения ДвиженияИГраницыПериода с периодичностью Авто или Регистратор. Начинающие разработчики нередко принимают такие строки за дубли или ошибки расчета остатков.

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

Суть проблемы: откуда берутся «лишние» строки?

Метод дополнения ДвиженияИГраницыПериода предназначен для того, чтобы разработчик мог гарантированно получить срез остатков на точные контрольные точки — дату начала (&ДатаНачала) и дату окончания (&ДатаОкончания) заданного периода. Это происходит даже в том случае, если в эти моменты (или вообще за весь выбранный период) по регистру не было никаких фактических движений (оборотов).

Когда мы выбираем детальную периодичность (например, Авто или Регистратор), платформа автоматически генерирует в результате запроса две дополнительные «виртуальные» строки на каждую уникальную комбинацию измерений (в нашем случае — комбинацию «Заказ» и «Товар»):

  1. Строка на начало периода (на дату &ДатаНачала). В ней поле Регистратор будет содержать значение NULL (или пустую ссылку/неопределено в зависимости от контекста). Обороты (приход и расход) в этой строке будут равны нулю, а в поле начального и конечного остатка отобразится состояние регистра на самую первую секунду выбранного периода.
  2. Строка на конец периода (на дату &ДатаОкончания). Здесь поле Регистратор также останется пустым, обороты будут нулевыми, а конечный остаток покажет актуальное состояние регистра на момент окончания периода.

Разбор логики: почему начальный остаток равен 6?

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

  1. На начало периода (например, на 01.05.2019 00:00:00) остаток товара равен 0. Платформа генерирует первую граничную строку: Начальный остаток = 0, Приход = 0, Расход = 0, Конечный остаток = 0.
  2. В середине периода происходит первое реальное движение — документ «Приходный ордер» приходует 1 единицу товара. Строка отчета: Начальный остаток = 0, Приход = 1, Расход = 0, Конечный остаток = 1.
  3. Затем проводится документ «Корректировка», который приходует еще 5 единиц товара. Строка отчета: Начальный остаток = 1, Приход = 5, Расход = 0, Конечный остаток = 6.
  4. Период завершается (например, 31.05.2019 23:59:59). Больше движений не было. Платформа обязана вывести состояние регистра на конечную границу периода. Она генерирует виртуальную строку без регистратора, где фиксирует текущий срез: Начальный остаток = 6, Приход = 0, Расход = 0, Конечный остаток = 6.

Для платформы эта строка имеет глубокий математический смысл — она фиксирует конечный статус остатка на дату &ДатаОкончания. Однако для конечного пользователя две строки подряд, в которых фигурирует цифра 6 (одна — с регистратором «Корректировка», а другая — пустая в самом конце), выглядят как дублирование информации. Нам нужно избавиться от этой избыточности.

Метод решения 1: Переход на метод дополнения «Движения»

Самый простой и очевидный способ убрать виртуальные строки на границах периода — изменить параметр метода дополнения в виртуальной таблице. Вместо ДвиженияИГраницыПериода мы укажем значение Движения.

Рассмотрим, как будет выглядеть текст запроса в этом случае:


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

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

Важное ограничение: если по какому-то товару в выбранном периоде вообще не было движений, но по нему в регистре числится активный остаток, этот товар полностью исчезнет из результата запроса. Если вам важно видеть такие «зависшие» остатки без движений, этот метод вам не подойдет.

Метод решения 2: Исключение пустых регистраторов через условия ГДЕ

Если нам обязательно нужно использовать метод дополнения ДвиженияИГраницыПериода (например, чтобы гарантированно видеть номенклатуру, по которой не было движений, но есть остатки), мы можем отсечь граничные строки непосредственно в секции ГДЕ нашего запроса.

Проанализируем следующий пример запроса с фильтрацией:


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

В зависимости от СУБД и особенностей версии платформы, пустой регистратор в этой таблице может возвращаться как значение NULL, так и иметь тип Неопределено. Для полной гарантии фильтрации можно использовать проверку на тип значения или комбинированное условие:


ГДЕ
    ЗаказыТоваровОстаткиИОбороты.Регистратор ССЫЛКА Документ.ЗаказПокупателя
    ИЛИ ЗаказыТоваровОстаткиИОбороты.Регистратор ССЫЛКА Документ.КорректировкаРегистров
    // Либо универсальный способ отсечения пустых значений:
    ИЛИ НЕ (ЗаказыТоваровОстаткиИОбороты.Регистратор ЕСТЬ NULL)

Этот подход позволяет нам сохранить гибкость расчетов, но при этом гарантированно избавиться от виртуальных записей на границах интервала.

Метод решения 3: Использование классического объединения таблиц (ОБЪЕДИНИТЬ ВСЕ)

Для достижения максимальной точности и полного контроля над выводом данных в профессиональной разработке часто отказываются от виртуальной таблицы ОстаткиИОбороты в пользу ручного объединения данных из таблиц Остатки и Обороты.

Давайте разберем этот алгоритм по шагам:

  1. В первой части запроса мы выбираем чистые начальные остатки на дату &ДатаНачала из таблицы .Остатки(). В поле Регистратор мы принудительно передаем пустое значение (например, NULL), а полученный остаток помещаем в поля начального и конечного остатка.
  2. Во второй части запроса мы выбираем только чистые обороты по документам из таблицы .Обороты() с периодичностью Регистратор. В этой таблице нет никаких виртуальных границ.
  3. Объединяем полученные таблицы с помощью конструкции ОБЪЕДИНИТЬ ВСЕ и упорядочиваем результат по периоду и регистратору.

Посмотрим на практическую реализацию такого запроса:


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

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

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

Этот метод исключает появление любых «лишних» конечных строк с остатками в конце отчета, предоставляя разработчику абсолютный контроль над структурой вывода данных и гарантируя высокую производительность запроса на больших объемах информации.

Решение проблемы средствами СКД (Системы компоновки данных)

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

Если ваш отчет строится на СКД, вам не нужно переписывать сложные запросы. Достаточно выполнить два простых шага:

  1. В тексте запроса обработать поле регистратора через конструкцию выбора:
    
    ВЫБОР 
        КОГДА ЗаказыТоваровОстаткиИОбороты.Регистратор = НЕОПРЕДЕЛЕНО 
            ТОГДА NULL 
        ИНАЧЕ ЗаказыТоваровОстаткиИОбороты.Регистратор 
    КОНЕЦ КАК Регистратор
    
  2. В настройках схемы компоновки данных перейдите на вкладку «Наборы данных». Найдите поле Регистратор, откройте его свойства в колонке «Роль» и установите флаг «Игнорировать NULL».

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

← На главную