В практике разработки на платформе 1С иногда возникают ситуации, когда необходимо сравнить данные, хранящиеся в физических таблицах справочников, с данными виртуальных таблиц регистров накопления. Часто это связано с попытками оптимизировать скорость отображения данных (например, для сортировки в обычных формах). Рассмотрим ситуацию, когда запрос, предназначенный для поиска расхождений между "закешированным" остатком в справочнике и актуальным остатком в регистре, не выдает ожидаемых результатов, даже если данные визуально различаются.
Разберем исходный запрос, который вызывает затруднения. Программист пытается выбрать номенклатуру, у которой остатки или резервы в справочнике Номенклатура не совпадают с данными в виртуальной таблице ОстаткиТоваровКомпании.Остатки.
Запрос.Текст = "ВЫБРАТЬ
| НоменклатураДоп.Ссылка,
| НоменклатураДоп.Остатки,
| НоменклатураДоп.Резервы,
| НоменклатураДоп.МинОстаток,
| НоменклатураДоп.Цена,
| НоменклатураДоп.Ячейка
|ИЗ
| Справочник.Номенклатура КАК НоменклатураДоп
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваровКомпании.Остатки КАК ОстаткиТоваровКомпанииОстатки
| ПО НоменклатураДоп.Ссылка = ОстаткиТоваровКомпанииОстатки.Номенклатура
|ГДЕ
| НоменклатураДоп.Остатки <> ЕстьNULL(ОстаткиТоваровКомпанииОстатки.КоличествоОстаток, 0)
| ИЛИ НоменклатураДоп.Резервы <> ЕстьNULL(ОстаткиТоваровКомпанииОстатки.РезервОстаток, 0)";
Проанализируем этот код. На первый взгляд, логика верна: мы используем ЛЕВОЕ СОЕДИНЕНИЕ, чтобы не потерять записи справочника, по которым нет движений в регистре, и применяем функцию ЕстьNULL для корректного сравнения с нулем. Однако в этом подходе скрыто несколько технических и методологических ловушек.
Первое, на что стоит обратить внимание — это обращение к виртуальной таблице остатков. В тексте запроса указано: РегистрНакопления.ОстаткиТоваровКомпании.Остатки. В языке запросов 1С обращение к виртуальной таблице без скобок допустимо, но считается плохой практикой. Правильнее использовать Остатки(,).
Почему это важно? Без указания параметров (периода и условий) система пытается получить остатки на самый актуальный момент времени. Если запрос выполняется в контексте проведения документа, отсутствие параметров может привести к тому, что будут получены данные без учета текущих движений или, наоборот, с их избыточным учетом, в зависимости от режима управления блокировками.
Использование полей из "правой" таблицы (таблицы остатков) в блоке ГДЕ часто превращает ЛЕВОЕ СОЕДИНЕНИЕ во внутреннее на уровне исполнения SQL-запроса, если условие не обрабатывает NULL должным образом. Хотя в данном примере функция ЕстьNULL присутствует, сам подход к фильтрации по результатам соединения физической таблицы и виртуальной таблицы остатков в одном запросе считается антипаттерном. Это может привести к неоптимальному плану запроса и замедлению работы базы.
Рассмотрим более правильный алгоритм:
Ссылка / Номенклатура.Как выяснилось в ходе обсуждения, проблема часто кроется не в самом тексте запроса, а в моменте его выполнения. Автор обнаружил, что записи не находятся, потому что изменения в справочник и в регистр вносятся одновременно в одной транзакции при проведении документа.
Проанализируем ситуацию: если мы запускаем проверку сразу после записи данных, система видит, что реквизит Номенклатура.Остатки уже обновлен и равен значению в РегистрНакопления.ОстаткиТоваровКомпании.Остатки. Следовательно, условие <> никогда не сработает. Чтобы найти ошибки рассинхронизации, такую сверку нужно запускать вне контекста проведения, либо имитировать ситуацию сбоя, когда в регистр данные попали, а в справочник — нет.
Важно понимать, почему опытные разработчики крайне негативно относятся к хранению остатков непосредственно в карточке номенклатуры. Основная цель автора — обеспечить штатную сортировку по остаткам в списке номенклатуры в "Обычных формах". Однако цена такого решения высока:
Справочник.Номенклатура. Если несколько пользователей одновременно проводят документы по одной и той же позиции, возникнут критические ошибки блокировок.Если перед вами стоит задача реализовать сортировку по остаткам в обычном интерфейсе (где нет Динамических списков с произвольным запросом, как в Управляемых формах), рассмотрим более безопасные методы:
СправочникСписок, выведите данные в обычную ТаблицуЗначений. Вы можете заполнить её запросом, который сразу соединит номенклатуру с остатками. В таблице значений сортировка по любой колонке работает "из коробки" нажатием на заголовок.ПриВыводеСтроки для отображения остатков, но это не решит проблему сортировки. Если сортировка критична, лучше пересмотреть требования бизнеса в сторону использования специализированных отчетов или АРМ (Автоматизированных рабочих мест).Резюме: Неисправность запроса в данном случае была вызвана логической ошибкой — попыткой найти расхождения в момент, когда система уже привела данные к идентичному состоянию. Однако сама практика хранения остатков в справочнике является тупиковой и ведет к деградации производительности системы при росте количества пользователей.