В практике разработчика 1С иногда встречается парадоксальная ситуация: при выполнении запроса к виртуальной таблице ОстаткиИОбороты регистра накопления значение конечного остатка изменяется в зависимости от того, какую начальную дату мы указываем в параметрах. На первый взгляд это кажется невозможным, ведь остаток на конкретный момент времени — величина фиксированная и не должна зависеть от того, за какой предыдущий период мы смотрим обороты. Разберем подробно, почему это происходит и как привести данные в порядок.
Рассмотрим классический пример запроса к регистру накопления (например, ПартииТоваровНаСкладахБухгалтерскийУчет), с которым столкнулся автор темы:
ВЫБРАТЬ
ПартииТоваров.Номенклатура,
ПартииТоваров.Склад,
ПартииТоваров.КоличествоНачальныйОстаток,
ПартииТоваров.КоличествоПриход,
ПартииТоваров.КоличествоРасход,
ПартииТоваров.КоличествоКонечныйОстаток
ИЗ
РегистрНакопления.ПартииТоваровНаСкладахБухгалтерскийУчет.ОстаткиИОбороты(
&ДатаНач,
&ДатаКон,
,
,
Номенклатура = &Номенклатура И Склад = &Склад
) КАК ПартииТоваров
Проблема проявляется следующим образом: если установить &ДатаНач на 01.02.2018 00:00:00, конечный остаток на 02.02.2018 23:59:59 равен 98. Однако, если изменить &ДатаНач всего на одну секунду или на следующий день (02.02.2018 00:00:00), конечный остаток внезапно превращается в 106. При этом &ДатаКон в обоих случаях остается неизменной.
Чтобы понять причину, проанализируем, как платформа 1С работает с итогами. Физически данные в регистре накопления хранятся в двух основных таблицах: таблице движений и таблице итогов. Таблица итогов содержит рассчитанные значения остатков на начало каждого месяца (01.01, 01.02, 01.03 и т.д.).
Когда мы обращаемся к виртуальной таблице ОстаткиИОбороты, система не всегда считает всё с нуля по движениям. Она пытается оптимизировать запрос:
Если в базе произошло рассогласование итогов (когда сумма движений за январь не соответствует разнице между итогами на 01.01 и 01.02), мы получаем разные результаты. В первом случае (с 1-го числа) запрос «доверился» битым итогам на 01.02. Во втором случае (со 2-го числа) запрос начал расчет от другой точки (например, от итогов на 01.03 назад или от актуальных текущих итогов), и ошибка исчезла.
Это самый надежный метод решения проблемы. Простой вызов метода ПересчитатьТекущиеИтоги() часто оказывается бесполезным, так как он затрагивает только актуальный период, а проблема может крыться в итогах многолетней давности. Рассмотрим пошагово, как выполнить полное исправление:
Важно: Данная операция требует монопольного доступа к базе и может занять значительное время (от нескольких часов до суток на очень больших базах). Поэтому выполнять её рекомендуется в периоды минимальной нагрузки (например, в выходные).
Если вы не можете остановить работу всей компании для Тестирования и исправления, попробуйте выполнить выборочный пересчет для конкретного регистра. Проанализируем, как это сделать через стандартный интерфейс:
ПартииТоваровНаСкладахБухгалтерскийУчет).Иногда штатные средства пересчета «вылетают» по таймауту или из-за блокировок. В таком случае можно прибегнуть к радикальному, но эффективному методу: полному отключению и включению использования итогов для конкретного регистра. Рассмотрим пример кода:
// Отключаем использование итогов - это физически очистит таблицу итогов регистра
РегистрыНакопления.ПартииТоваровНаСкладахБухгалтерскийУчет.УстановитьИспользованиеИтогов(Ложь);
// Включаем обратно - система начнет полный пересчет таблицы итогов на основании движений
РегистрыНакопления.ПартииТоваровНаСкладахБухгалтерскийУчет.УстановитьИспользованиеИтогов(Истина);
После выполнения этого кода записи в таблице итогов будут гарантированно соответствовать записям в таблице движений. Выясним причину расхождений заранее: чаще всего они возникают при массовой загрузке данных в режиме ОбменДанными.Загрузка = Истина или при аварийных завершениях работы сервера SQL во время записи транзакций.
При написании запросов к виртуальным таблицам важно помнить о типе данных параметров ДатаНач и ДатаКон. Рассмотрим, как правильно передавать параметры, чтобы избежать захвата лишних секунд или потери документов, записанных в ту же секунду:
Вместо передачи простого значения Дата, рекомендуется использовать объект Граница. Это особенно критично для ДатаКон, так как документ может иметь время 23:59:59, но при этом располагаться в «конце» этой секунды.
ГраницаКон = Новый Граница(КонецДня(ПараметрДатаКон), ВидГраницы.Включая);
Запрос.УстановитьПараметр("ДатаКон", ГраницаКон);
Если вы обнаружили, что остатки «плывут» при изменении периода отчета — это диагноз: рассогласование итогов. Не пытайтесь исправить это программными фильтрами в запросе или изменением метода дополнения периодов. Единственный верный путь — полный пересчет итогов. Начните с обработки «Управление итогами», и если она не поможет, планируйте проведение «Тестирования и исправления» в Конфигураторе.