При разработке отчетов и написании запросов к регистрам накопления разработчики нередко сталкиваются с ситуацией, когда виртуальная таблица Остатки и виртуальная таблица ОстаткиИОбороты возвращают разное значение остатка на одну и ту же дату (например, на конец дня 31.01.2017 23:59:59). Кроме того, при детальном выводе движений с периодичностью Регистратор в результатах могут появляться строки с пустым регистратором. Разберем по шагам, почему возникает такая разница и как правильно составлять запросы для получения корректных данных.
Главная причина расхождения между таблицами заключается в том, как платформа 1С обрабатывает момент времени, переданный в качестве параметра даты:
Дата (например, результат функции КОНЕЦПЕРИОДА(&Дата, ДЕНЬ), равный 31.01.2017 23:59:59), платформа по умолчанию рассчитывает остаток на начало указанной секунды. То есть все движения, записанные ровно в 23:59:59, исключаются из расчета (действует поведение ВидГраницы.Исключая).Дата включает всю указанную секунду целиком. Таким образом, движения, проведенные в 23:59:59, попадают в обороты периода и изменяют значение поля ВНаличииКонечныйОстаток.Если в базе присутствует хотя бы один документ с датой 31.01.2017 23:59:59 (например, регламентный документ или оприходование излишков, записанное на последнюю секунду месяца), таблица Остатки его проигнорирует, а ОстаткиИОбороты учтет, что и дает разницу ровно на величину движений этого документа.
Для устранения этой проблемы рассмотрим три возможных варианта решения:
Вариант 1. Смещение даты на начало следующего дня в тексте запроса. Чтобы получить остаток строго на конец суток с учетом всех документов последней секунды, достаточно запросить остаток на начало следующего дня:
ДОБАВИТЬКДАТЕ(КОНЕЦПЕРИОДА(&Дата, ДЕНЬ), СЕКУНДА, 1)
// или передать параметр &ПериодОстатков = НАЧАЛОПЕРИОДА(&ДатаСледДня, ДЕНЬ)
Вариант 2. Использование объекта Граница в коде 1С:Предприятия. Если запрос формируется программно, вместо типа Дата рекомендуется передавать объект Граница с явным указанием включения граничного момента:
ГраницаПериода = Новый Граница(КонецДня(ВыбраннаяДата), ВидГраницы.Включая);
Запрос.УстановитьПараметр("ПериодОстатков", ГраницаПериода);
При обращении к виртуальной таблице ОстаткиИОбороты с периодичностью Регистратор и методом дополнения ДвиженияИГраницыПериода платформа формирует не только строки самих движений, но и специальные граничные строки:
Регистратор всегда пустой (NULL или пустая ссылка), а заполнены поля начального остатка и даты начала периода.Если требуется получить исключительно реальные движения документов с досчетом остатков без пустых служебных строк границ, используйте режим дополнения Движения вместо ДвиженияИГраницыПериода:
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
НАЧАЛОПЕРИОДА(&ПараметрДатаСтарт, ДЕНЬ),
КОНЕЦПЕРИОДА(&ПараметрДатаКон, ДЕНЬ),
Регистратор,
Движения,
Номенклатура = &ПараметрНоменклатура И Склад = &ПараметрСклад
) КАК ТоварыНаСкладахОстаткиИОбороты
Проанализируем структуру исходного запроса. В нем отборы по номенклатуре и складу вынесены в секцию ГДЕ, а обращение к полям происходит через точку: ТоварыНаСкладахОстатки.Номенклатура.Ссылка.
Такой подход приводит к двум серьезным проблемам:
Номенклатура.Ссылка заставляет СУБД выполнять неявное левое соединение (LEFT JOIN) с таблицей справочника, хотя измерение регистра само по себе уже содержит готовую ссылку.ГДЕ применяется уже после того, как платформа рассчитает виртуальную таблицу по всему регистру. Все условия по измерениям регистра необходимо передавать во второй параметр виртуальной таблицы (параметр Отбор).Правильный вариант запроса к таблице остатков выглядит следующим образом:
ВЫБРАТЬ
ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстатки.ВНаличииОстаток КАК ВНаличииОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
ДОБАВИТЬКДАТЕ(КОНЕЦПЕРИОДА(&Дата, ДЕНЬ), СЕКУНДА, 1),
Номенклатура = &ПараметрНоменклатура И Склад = &ПараметрСклад
) КАК ТоварыНаСкладахОстатки
Если разница между таблицами сохраняется даже с учетом границы периода, выясним причину в состоянии физических таблиц СУБД. Таблица Остатки считывает данные из таблицы рассчитанных итогов регистра, а таблица ОстаткиИОбороты с детализацией по регистратору вычисляет данные динамически по таблице движений. При повреждении или отставании итогов результаты разойдутся.
Для исправления и диагностики выполним следующие действия:
РегистрыНакопления.ТоварыНаСкладах.ПересчитатьИтоги();
ВЫБРАТЬ
СУММА(ВЫБОР
КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
ТОГДА Движения.ВНаличии
ИНАЧЕ -Движения.ВНаличии
КОНЕЦ) КАК ФактОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах КАК Движения
ГДЕ
Движения.Период <= КОНЕЦПЕРИОДА(&Дата, ДЕНЬ)
И Движения.Активность = ИСТИНА
И Движения.Номенклатура = &ПараметрНоменклатура
И Движения.Склад = &ПараметрСклад
Сравнив полученную контрольную сумму с выборками из виртуальных таблиц, можно точно определить, какой механизм дал сбой: срез итогов или некорректная граница переданного периода.