Почему один и тот же запрос возвращает разные остатки в форме документа и в консоли запросов?

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

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

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

Исходный запрос и суть проблемы

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


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

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

---

Причина 1. Фильтрация через «ГДЕ» вместо параметров виртуальной таблицы и отсутствие измерений

Это самая распространенная и грубая ошибка при работе с виртуальными таблицами в 1С. Когда мы пишем условие фильтрации в секции ГДЕ, платформа 1С и СУБД обрабатывают этот запрос крайне неоптимально.

Рассмотрим, что происходит на уровне СУБД:

  1. Избыточная выборка: Поскольку параметры виртуальной таблицы Остатки не заданы, система пытается получить итоги по всем записям таблицы итогов регистра (_AccumRegTotals).
  2. Отсутствие группировочных полей: В секции ВЫБРАТЬ мы запрашиваем только ресурс КоличествоОстаток, полностью игнорируя измерения Номенклатура и МестоХранения.
  3. Непредсказуемая оптимизация SQL: СУБД (будь то MS SQL Server или PostgreSQL), строя план выполнения запроса, пытается оптимизировать агрегацию. Без явного указания группировок по измерениям и при наложении фильтра в секции ГДЕ, SQL-сервер может построить некорректный план кэширования. В зависимости от текущей нагрузки на рабочий процесс 1С, версии СУБД и состояния индексов, результат выборки без измерений может стать нестабильным и возвращать неверно агрегированные данные.

Решение проблемы: Перенесем условия фильтрации непосредственно в параметры виртуальной таблицы. В этом случае платформа 1С передаст СУБД жесткий отбор, и выборка произойдет только по нужным записям изначально. Также хорошей практикой является явный вывод измерений в запросе.

Правильный вариант запроса:


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

---

Причина 2. Транзакции и уровни изоляции данных (Dirty Reads)

Контекст выполнения кода в форме документа и в консоли запросов кардинально различается на уровне транзакционности:

Если в процессе проведения документа остатки меняются (например, списывается товар), то внутри транзакции остаток уже будет равен 1, в то время как внешняя консоль запросов все еще будет видеть старое значение 2, так как транзакция проведения еще не завершена (не зафиксирована).

---

Причина 3. Ограничение доступа на уровне записей (RLS)

Еще один невидимый фактор, который часто сбивает с толку разработчиков — это механизм RLS (Row-Level Security).

Выясним, как это влияет на результат:

Решение: Для проверки этой гипотезы запустите консоль запросов под тем же пользователем, у которого воспроизводится проблема в форме, либо временно предоставьте пользователю полные права для теста.

---

Причина 4. Системы компоновки данных (СКД) и динамические списки

Если запрос в форме выполняется не явным вызовом объекта Новый Запрос, а через динамический список или СКД:

Система компоновки данных — это мощный инструмент, который перед отправкой запроса в СУБД полностью его перестраивает. Она автоматически накладывает параметры периода, оптимизирует соединения, убирает неиспользуемые поля и добавляет системные отборы формы. Консоль запросов выполняет исходный «сырой» текст, тогда как СКД на форме выполняет глубоко модифицированную версию этого запроса.

---

Причина 5. Неявная работа с периодом: Момент времени и Граница

Обратите внимание на параметр периода. Если в запросе виртуальной таблицы остатков не указан период, система берет итоги на самую актуальную дату (на «сейчас»).

В коде формы период часто передается неявно, например, как МоментВремени() или Граница() документа. В консоли запросов разработчики часто указывают просто дату (например, конец дня).

Разберем разницу:

Разница в типах передаваемых параметров периода (просто дата в консоли и МоментВремени в форме) — классическая причина расхождения остатков ровно на величину движений текущего документа.

---

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

В редких случаях (например, после сбоев СУБД, аварийного завершения транзакций или некорректного динамического обновления конфигурации) может произойти рассинхронизация физической таблицы движений регистра накопления и таблицы его текущих итогов.

Запрос к виртуальной таблице без указания периода обращается к таблице текущих итогов. Если она повреждена, СУБД может возвращать неверные данные, которые будут отличаться в зависимости от кэша конкретного сеанса.

Решение: Выполните операцию «Тестирование и исправление» в конфигураторе с установленным флагом «Пересчет итогов».

---

Резюме и правильный шаблон разработки

Чтобы раз и навсегда исключить подобные расхождения и гарантировать стабильную работу ваших запросов, придерживайтесь следующих правил:

  1. Всегда передавайте условия в параметры виртуальной таблицы. Никогда не используйте секцию ГДЕ для фильтрации виртуальных таблиц остатков и оборотов.
  2. Всегда указывайте период получения остатков. Используйте МоментВремени() или объект Новый Граница(ДокументСсылка.МоментВремени(), ВидГраницы.Исключая) для получения остатков строго «до» проведения документа.
  3. Избегайте выборки ресурсов без измерений. Если вам нужно получить общее количество, выберите измерения, а затем сгруппируйте их с помощью СГРУППИРОВАТЬ ПО.

Рассмотрим надежный, профессиональный шаблон получения остатков в форме документа:


// Подготовим границу для получения остатков строго до момента времени документа
ГраницаОстатков = Новый Граница(Объект.Ссылка.МоментВремени(), ВидГраницы.Исключая);

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ОстаткиТоваров.Номенклатура КАК Номенклатура,
    |	ОстаткиТоваров.МестоХранения КАК МестоХранения,
    |	СУММА(ОстаткиТоваров.КоличествоОстаток) КАК Количество
    |ИЗ
    |	РегистрНакопления.ОстаткиТоваровНаСкладах.Остатки(&Период, Номенклатура = &Номенклатура И МестоХранения = &МестоХранения) КАК ОстаткиТоваров
    |ГРУППИРОВАТЬ ПО
    |	ОстаткиТоваров.Номенклатура,
    |	ОстаткиТоваров.МестоХранения";

Запрос.УстановитьПараметр("Период", ГраницаОстатков);
Запрос.УстановитьПараметр("Номенклатура", ВыбраннаяНоменклатура);
Запрос.УстановитьПараметр("МестоХранения", ВыбранныйСклад);

РезультатЗапроса = Запрос.Выполнить();

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

← На главную