Разработка отчетов и обработок в 1С часто преподносит сюрпризы: один и тот же запрос, выполненный в консоли запросов и в программном коде, может возвращать разные значения остатков. Особенно часто это проявляется, когда дата начала периода приходится на начало дня (00:00:00). В этой статье мы подробно разберем, почему возникает такая ситуация, как на это влияет объект Граница и почему использование таблицы ОстаткиИОбороты является более предпочтительным.
Проанализируем типичную ситуацию. Программист пишет запрос к регистру накопления, чтобы получить начальный и конечный остатки, а также цены. В консоли запросов ОстатокНаНачало показывает верное значение, а в обработке — 0 или число, отличное от ожидаемого. Рассмотрим исходный код установки параметров:
Запрос.УстановитьПараметр("НачальнаяДата", Новый Граница(НачалоДня(Период.ДатаНачала), ВидГраницы.Включая));
Запрос.УстановитьПараметр("КонечнаяДата", Новый Граница(КонецДня(Период.ДатаОкончания), ВидГраницы.Включая));
Основная причина кроется в том, как 1С обрабатывает документы, записанные ровно в 00:00:00. Если документ (например, "Реализация товаров и услуг" или "Перемещение") имеет время начала секунды, то использование ВидГраницы.Включая для начала периода приведет к тому, что движения этого документа будут учтены в остатках "на начало". Консоль запросов при передаче просто даты без указания типа границы может интерпретировать момент времени иначе, чем программный объект Граница.
Выясним причину расхождения через специфику работы с платформой. Когда мы передаем в виртуальную таблицу просто тип Дата, система неявно преобразует ее. Если мы хотим получить остаток СТРОГО до начала любых движений выбранного дня, нам нужно использовать ВидГраницы.Исключая.
Разберем по шагам правильную установку параметров для получения остатков на начало дня:
Новый Граница(НачалоДня(Дата), ВидГраницы.Исключая).23:59:59 предыдущего дня, помните, что это может быть ненадежно, если в системе есть документы, записанные между 23:59:59 и 00:00:00 (хотя это редкость для типовых решений).КонецДня(Дата) или ВидГраницы.Включая, чтобы захватить все документы последней секунды дня.Посмотрим на структуру исходного запроса автора. Он использует три левых соединения: два к таблице Остатки и одно к СрезПоследних. Это не только неоптимально с точки зрения производительности, но и потенциально опасно для согласованности данных.
Проанализируем, почему ОстаткиИОбороты лучше:
НачальныйОстаток и КонечныйОстаток в рамках одного обращения к базе данных. Это гарантирует, что данные будут согласованы между собой.Пример оптимизированного запроса с использованием ОстаткиИОбороты:
ВЫБРАТЬ
Товары.Ссылка КАК Номенклатура,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена,
ЕСТЬNULL(СкладскиеОстатки.ВНаличииНачальныйОстаток, 0) КАК ОстатокНаНачало,
ЕСТЬNULL(СкладскиеОстатки.ВНаличииКонечныйОстаток, 0) КАК ОстатокНаКонец
ИЗ
Справочник.Номенклатура КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, , , Номенклатура В (&СписокНужныхТоваров) И Склад = &Склад) КАК СкладскиеОстатки
ПО Товары.Ссылка = СкладскиеОстатки.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры25.СрезПоследних(&КонецПериода, ВидЦены = &ВидЦены И Номенклатура В (&СписокНужныхТоваров)) КАК Цены
ПО Товары.Ссылка = Цены.Номенклатура
ГДЕ
Товары.Ссылка В (&СписокНужныхТоваров)
Обратим внимание на типичную ошибку в исходном запросе автора: отбор по списку товаров ГДЕ Товары.Ссылка В(&СписокНужныхТоваров) находился в самом конце запроса. Это означает, что 1С сначала заставит сервер SQL вычислить остатки по ВСЕМ товарам на складе, и только потом "отсечет" лишние.
Рассмотрим подробнее: если на складе 50 000 позиций, а вам нужно 10, запрос без параметров в виртуальной таблице будет работать крайне медленно. Всегда передавайте условия в первый или второй параметр виртуальной таблицы (условие отбора). Это заставляет SQL-сервер использовать индексы и выбирать только нужные записи.
В запросе также фигурирует РегистрСведений.ЦеныНоменклатуры25.СрезПоследних. Помните следующие правила:
СрезПоследних не передать дату, система возьмет актуальные цены на текущую секунду (время сервера). Если вы строите отчет за прошлый месяц, вы получите цены "из будущего".&КонецПериода в качестве первого параметра, чтобы цена соответствовала моменту получения конечного остатка.СрезПоследних также крайне желательно дублировать отбор по номенклатуре для ускорения работы.Выяснив причины расхождений, мы можем сформулировать рекомендации для стабильной работы запросов:
ВидГраницы.Исключая для начала периода, чтобы не захватывать документы, проведенные ровно в полночь.Остатки используйте одно обращение к ОстаткиИОбороты. Это стандарт "хорошего тона" в разработке на платформе 1С.ГДЕ, если работаете с виртуальными таблицами. Переносите их в параметры этих таблиц.СрезПоследних, иначе результат будет зависеть от момента запуска обработки.Соблюдение этих правил позволит вам избежать трудноуловимых ошибок с "исчезающими" или "лишними" остатками и значительно повысит скорость работы ваших отчетов.