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

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

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

Анализ исходного кода и структуры запроса

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


Запрос.Текст = "ВЫБРАТЬ
               | НоменклатураДоп.Ссылка,
               | НоменклатураДоп.Остатки,
               | НоменклатураДоп.Резервы,
               | НоменклатураДоп.МинОстаток,
               | НоменклатураДоп.Цена,
               | НоменклатураДоп.Ячейка
               |ИЗ
               | Справочник.Номенклатура КАК НоменклатураДоп
               | ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваровКомпании.Остатки КАК ОстаткиТоваровКомпанииОстатки
               | ПО НоменклатураДоп.Ссылка = ОстаткиТоваровКомпанииОстатки.Номенклатура
               |ГДЕ
               | НоменклатураДоп.Остатки <> ЕстьNULL(ОстаткиТоваровКомпанииОстатки.КоличествоОстаток, 0)
               | ИЛИ НоменклатураДоп.Резервы <> ЕстьNULL(ОстаткиТоваровКомпанииОстатки.РезервОстаток, 0)";

Проанализируем этот код. На первый взгляд, логика верна: мы используем ЛЕВОЕ СОЕДИНЕНИЕ, чтобы не потерять записи справочника, по которым нет движений в регистре, и применяем функцию ЕстьNULL для корректного сравнения с нулем. Однако в этом подходе скрыто несколько технических и методологических ловушек.

Техническая ошибка: Параметры виртуальной таблицы

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

Почему это важно? Без указания параметров (периода и условий) система пытается получить остатки на самый актуальный момент времени. Если запрос выполняется в контексте проведения документа, отсутствие параметров может привести к тому, что будут получены данные без учета текущих движений или, наоборот, с их избыточным учетом, в зависимости от режима управления блокировками.

Проблема использования ГДЕ при ЛЕВОМ СОЕДИНЕНИИ

Использование полей из "правой" таблицы (таблицы остатков) в блоке ГДЕ часто превращает ЛЕВОЕ СОЕДИНЕНИЕ во внутреннее на уровне исполнения SQL-запроса, если условие не обрабатывает NULL должным образом. Хотя в данном примере функция ЕстьNULL присутствует, сам подход к фильтрации по результатам соединения физической таблицы и виртуальной таблицы остатков в одном запросе считается антипаттерном. Это может привести к неоптимальному плану запроса и замедлению работы базы.

Рассмотрим более правильный алгоритм:

  1. Выгрузим необходимые данные из справочника во временную таблицу.
  2. Получим остатки по нужной номенклатуре в другую временную таблицу.
  3. Проиндексируем обе таблицы по полю Ссылка / Номенклатура.
  4. Выполним итоговое соединение двух временных таблиц.

Выясняем истинную причину: Транзакционная целостность

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

Проанализируем ситуацию: если мы запускаем проверку сразу после записи данных, система видит, что реквизит Номенклатура.Остатки уже обновлен и равен значению в РегистрНакопления.ОстаткиТоваровКомпании.Остатки. Следовательно, условие <> никогда не сработает. Чтобы найти ошибки рассинхронизации, такую сверку нужно запускать вне контекста проведения, либо имитировать ситуацию сбоя, когда в регистр данные попали, а в справочник — нет.

Архитектурные риски: Хранение остатков в справочнике

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

Альтернативные решения для сортировки в Обычных формах

Если перед вами стоит задача реализовать сортировку по остаткам в обычном интерфейсе (где нет Динамических списков с произвольным запросом, как в Управляемых формах), рассмотрим более безопасные методы:

  1. Таблица значений на форме: Вместо использования СправочникСписок, выведите данные в обычную ТаблицуЗначений. Вы можете заполнить её запросом, который сразу соединит номенклатуру с остатками. В таблице значений сортировка по любой колонке работает "из коробки" нажатием на заголовок.
  2. Построение списка по требованию: Если номенклатуры слишком много для выгрузки в таблицу значений, используйте механизмы фильтрации. Пользователь сначала выбирает группу или вводит часть наименования, и только для этого подмножества рассчитываются остатки.
  3. Использование внешних компонент или доработка формы: В некоторых случаях можно использовать событие ПриВыводеСтроки для отображения остатков, но это не решит проблему сортировки. Если сортировка критична, лучше пересмотреть требования бизнеса в сторону использования специализированных отчетов или АРМ (Автоматизированных рабочих мест).

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

← На главную