Почему не совпадают остатки в виртуальных таблицах Остатки и ОстаткиИОбороты в 1С?

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

При разработке отчетов и написании запросов к регистрам накопления разработчики нередко сталкиваются с ситуацией, когда виртуальная таблица Остатки и виртуальная таблица ОстаткиИОбороты возвращают разное значение остатка на одну и ту же дату (например, на конец дня 31.01.2017 23:59:59). Кроме того, при детальном выводе движений с периодичностью Регистратор в результатах могут появляться строки с пустым регистратором. Разберем по шагам, почему возникает такая разница и как правильно составлять запросы для получения корректных данных.

Причина 1. Разница в логике включения граничной секунды периода

Главная причина расхождения между таблицами заключается в том, как платформа 1С обрабатывает момент времени, переданный в качестве параметра даты:

  1. Виртуальная таблица Остатки: если в качестве параметра передается обычное значение типа Дата (например, результат функции КОНЕЦПЕРИОДА(&Дата, ДЕНЬ), равный 31.01.2017 23:59:59), платформа по умолчанию рассчитывает остаток на начало указанной секунды. То есть все движения, записанные ровно в 23:59:59, исключаются из расчета (действует поведение ВидГраницы.Исключая).
  2. Виртуальная таблица ОстаткиИОбороты: параметр конечной даты при передаче значения типа Дата включает всю указанную секунду целиком. Таким образом, движения, проведенные в 23:59:59, попадают в обороты периода и изменяют значение поля ВНаличииКонечныйОстаток.

Если в базе присутствует хотя бы один документ с датой 31.01.2017 23:59:59 (например, регламентный документ или оприходование излишков, записанное на последнюю секунду месяца), таблица Остатки его проигнорирует, а ОстаткиИОбороты учтет, что и дает разницу ровно на величину движений этого документа.

Для устранения этой проблемы рассмотрим три возможных варианта решения:

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


ДОБАВИТЬКДАТЕ(КОНЕЦПЕРИОДА(&Дата, ДЕНЬ), СЕКУНДА, 1)
// или передать параметр &ПериодОстатков = НАЧАЛОПЕРИОДА(&ДатаСледДня, ДЕНЬ)

Вариант 2. Использование объекта Граница в коде 1С:Предприятия. Если запрос формируется программно, вместо типа Дата рекомендуется передавать объект Граница с явным указанием включения граничного момента:


ГраницаПериода = Новый Граница(КонецДня(ВыбраннаяДата), ВидГраницы.Включая);
Запрос.УстановитьПараметр("ПериодОстатков", ГраницаПериода);

Причина 2. Появление строк без регистратора в ОстаткиИОбороты

При обращении к виртуальной таблице ОстаткиИОбороты с периодичностью Регистратор и методом дополнения ДвиженияИГраницыПериода платформа формирует не только строки самих движений, но и специальные граничные строки:

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

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


РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
    НАЧАЛОПЕРИОДА(&ПараметрДатаСтарт, ДЕНЬ),
    КОНЕЦПЕРИОДА(&ПараметрДатаКон, ДЕНЬ),
    Регистратор,
    Движения,
    Номенклатура = &ПараметрНоменклатура И Склад = &ПараметрСклад
) КАК ТоварыНаСкладахОстаткиИОбороты

Причина 3. Неоптимальные фильтры и неявные соединения

Проанализируем структуру исходного запроса. В нем отборы по номенклатуре и складу вынесены в секцию ГДЕ, а обращение к полям происходит через точку: ТоварыНаСкладахОстатки.Номенклатура.Ссылка.

Такой подход приводит к двум серьезным проблемам:

  1. Снижение производительности: обращение Номенклатура.Ссылка заставляет СУБД выполнять неявное левое соединение (LEFT JOIN) с таблицей справочника, хотя измерение регистра само по себе уже содержит готовую ссылку.
  2. Избыточный расчет итогов: фильтрация в блоке ГДЕ применяется уже после того, как платформа рассчитает виртуальную таблицу по всему регистру. Все условия по измерениям регистра необходимо передавать во второй параметр виртуальной таблицы (параметр Отбор).

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


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

Причина 4. Рассинхронизация таблицы итогов регистра

Если разница между таблицами сохраняется даже с учетом границы периода, выясним причину в состоянии физических таблиц СУБД. Таблица Остатки считывает данные из таблицы рассчитанных итогов регистра, а таблица ОстаткиИОбороты с детализацией по регистратору вычисляет данные динамически по таблице движений. При повреждении или отставании итогов результаты разойдутся.

Для исправления и диагностики выполним следующие действия:

  1. Пересчет итогов регистра: в режиме «1С:Предприятие» перейдем в меню «Все функции» (или «Функции для технического специалиста») → «Управление итогами» и проверим период рассчитанных итогов. Для программного пересчета конкретного регистра выполним код: РегистрыНакопления.ТоварыНаСкладах.ПересчитатьИтоги();
  2. Сверка с физической таблицей движений: для окончательной проверки рассчитаем остаток прямым суммированием активных движений:
    
    ВЫБРАТЬ
        СУММА(ВЫБОР
            КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
                ТОГДА Движения.ВНаличии
            ИНАЧЕ -Движения.ВНаличии
        КОНЕЦ) КАК ФактОстаток
    ИЗ
        РегистрНакопления.ТоварыНаСкладах КАК Движения
    ГДЕ
        Движения.Период <= КОНЕЦПЕРИОДА(&Дата, ДЕНЬ)
        И Движения.Активность = ИСТИНА
        И Движения.Номенклатура = &ПараметрНоменклатура
        И Движения.Склад = &ПараметрСклад
    

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

← На главную