При переходе на новые релизы 1С:ERP (начиная с 2.4.12) или 1С:Управление торговлей программисты часто сталкиваются с загадочным поведением системы компоновки данных (СКД). Отчет, который годами работал корректно, внезапно начинает выводить пустой результат, хотя в консоли запросов тот же самый текст запроса выдает данные. Давайте разберем, почему «волшебство» СКД перестает работать и как привести отчет в чувство.
Проанализируем классический пример запроса, который часто используется в управленческих отчетах. Мы хотим объединить данные из двух разных регистров, например, ЗаказыКлиентов и ЗаказыПоставщикам, чтобы увидеть общую картину по номенклатуре. В исходном варианте запрос выглядит следующим образом:
ВЫБРАТЬ
ЗаказыКлиентовОбороты.Номенклатура КАК Номенклатура,
ЗаказыКлиентовОбороты.ЗаказКлиента КАК ЗаказКлиента,
ЗаказыКлиентовОбороты.ЗаказаноОборот КАК ЗаказКлиентаОборот,
NULL КАК ЗаказПоставщику,
0 КАК ЗаказПоставщикуОборот
ИЗ
РегистрНакопления.ЗаказыКлиентов.Обороты КАК ЗаказыКлиентовОбороты
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ЗаказыПоставщикамОбороты.Номенклатура,
NULL,
0,
ЗаказыПоставщикамОбороты.ЗаказПоставщику,
ЗаказыПоставщикамОбороты.ЗаказаноОборот
ИЗ
РегистрНакопления.ЗаказыПоставщикам.Обороты КАК ЗаказыПоставщикамОбороты
В пользовательских настройках СКД мы добавляем два отбора: ЗаказКлиента.Склад = &ЦентральныйСклад и ЗаказПоставщику.Склад = &ЦентральныйСклад. До версии 2.4.11 этот механизм работал, но в 2.4.12+ результат становится пустым. Выясним причину этого поведения.
Основная причина кроется в изменении режима совместимости конфигурации. Начиная с версии 8.3.14, платформа 1С изменила логику оптимизации запросов СКД. Теперь система более агрессивно «пробрасывает» пользовательские отборы внутрь каждой части объединения ОБЪЕДИНИТЬ ВСЕ.
Рассмотрим, что происходит «под капотом»:
ЗаказПоставщику заполнено значением NULL.ЗаказПоставщику.Склад, СКД пытается применить это условие ко всем веткам объединения.NULL.Склад = &Склад. С точки зрения SQL это выражение всегда дает Ложь.ЗаказКлиента равно NULL, и отбор ЗаказКлиента.Склад = &Склад также обнуляет результат этой ветки.Первое, что мы должны сделать — отказаться от использования чистого NULL в полях, по которым возможен отбор (особенно через «точку» от ссылки). Разберем по шагам, как правильно типизировать пустые значения в запросе. Вместо неопределенного типа мы явно укажем платформе, что это пустая ссылка конкретного документа.
Заменим NULL на конструкцию ЗНАЧЕНИЕ(Документ.ИмяДокумента.ПустаяСсылка):
ВЫБРАТЬ
ЗаказыКлиентовОбороты.Номенклатура КАК Номенклатура,
ЗаказыКлиентовОбороты.ЗаказКлиента КАК ЗаказКлиента,
ЗаказыКлиентовОбороты.ЗаказаноОборот КАК ЗаказКлиентаОборот,
ЗНАЧЕНИЕ(Документ.ЗаказПоставщику.ПустаяСсылка) КАК ЗаказПоставщику,
0 КАК ЗаказПоставщикуОборот
ИЗ
РегистрНакопления.ЗаказыКлиентов.Обороты КАК ЗаказыКлиентовОбороты
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ЗаказыПоставщикамОбороты.Номенклатура,
ЗНАЧЕНИЕ(Документ.ЗаказКлиента.ПустаяСсылка),
0,
ЗаказыПоставщикамОбороты.ЗаказПоставщику,
ЗаказыПоставщикамОбороты.ЗаказаноОборот
ИЗ
РегистрНакопления.ЗаказыПоставщикам.Обороты КАК ЗаказыПоставщикамОбороты
Это помогает платформе понять метаданные поля. Однако, этого может быть недостаточно, если СКД продолжает использовать INNER JOIN для получения реквизитов из пустых ссылок.
Наиболее корректный и производительный способ решения проблемы в типовых конфигурациях вроде ERP — это перенос отборов в параметры виртуальных таблиц. Вместо того чтобы позволять СКД накладывать фильтр на результат объединения, мы передадим его непосредственно в механизм получения данных из регистров.
Рассмотрим, как изменить текст запроса для использования параметров:
ВЫБРАТЬ
ЗаказыКлиентовОбороты.Номенклатура КАК Номенклатура,
ЗаказыКлиентовОбороты.ЗаказКлиента КАК ЗаказКлиента,
ЗаказыКлиентовОбороты.ЗаказаноОборот КАК ЗаказКлиентаОборот,
ЗНАЧЕНИЕ(Документ.ЗаказПоставщику.ПустаяСсылка) КАК ЗаказПоставщику
ИЗ
РегистрНакопления.ЗаказыКлиентов.Обороты(&НачалоПериода, &КонецПериода, , Склад = &Склад) КАК ЗаказыКлиентовОбороты
В этом случае необходимо добавить параметр Склад на закладке «Параметры» в СКД и настроить его доступность для пользователя. Это гарантирует, что условие применится только к той таблице, где поле реально существует, и не будет конфликтовать с NULL в других частях запроса.
Если нам всё же необходимо выполнять отбор по полям объединения, и мы не хотим использовать параметры виртуальных таблиц (например, когда Склад — это не измерение, а реквизит документа), проанализируем использование функции ВЫРАЗИТЬ. В новых режимах совместимости 1С это критически важно для корректного разыменования полей.
Посмотрим на пример уточнения типа в запросе:
ВЫБРАТЬ
ВЫРАЗИТЬ(ЗаказыКлиентовОбороты.ЗаказКлиента КАК Документ.ЗаказКлиента) КАК ЗаказКлиента,
...
Хотя поле ЗаказКлиента и так имеет ссылочный тип, явное указание типа в ОБЪЕДИНИТЬ ВСЕ помогает оптимизатору запросов СКД правильно построить дерево условий и избежать исключения строк из результата при обращении к реквизитам через точку.
Выясним причину, по которой отчет возвращает пустоту при стандартных настройках. Как мы уже поняли, СКД объединяет отборы по ЗаказКлиента.Склад и ЗаказПоставщику.Склад через И. Чтобы отчет заработал без изменения структуры запроса, можно предложить пользователю группировать условия.
В настройках отчета (вкладка «Отбор») создадим Группу ИЛИ и поместим в неё наши условия:
В такой конфигурации, если в строке заполнено хотя бы одно из полей (или Заказ Клиента, или Заказ Поставщику) и его склад соответствует условию, строка попадет в отчет. Однако стоит помнить, что это решение менее производительно, чем использование параметров виртуальных таблиц.
Рассмотрим еще несколько важных моментов, которые влияют на результат в новых версиях платформы:
1. Механизм «Автозаполнение». Если в вашей схеме включено автозаполнение, СКД может самостоятельно добавлять поля в выборку для обеспечения работы RLS (ограничения доступа на уровне записей). В сочетании с NULL это часто приводит к тому, что система безопасности «отрезает» строки, так как не может определить права на объект NULL. Рекомендуется отключать автозаполнение и описывать поля выбора и отбора вручную.
2. Использование консоли компоновки. Никогда не ограничивайтесь проверкой запроса в обычной консоли запросов. Для отладки отчетов СКД используйте специальную Консоль компоновки данных. Она позволяет увидеть реальный текст запроса, который СКД отправляет на сервер СУБД после наложения всех пользовательских настроек. Именно там вы увидите те самые условия WHERE NULL.Field = ..., которые убивают результат.
3. Смена режима совместимости. Помните, что поведение СКД жестко привязано к версии платформы, указанной в свойстве конфигурации «Режим совместимости». Если ваша база обновилась с 2.4.11 до 2.4.12, это почти всегда означает переход на режим 8.3.14 или выше, что и влечет за собой описанные изменения в логике UNION.
Подводя итог, можно сказать, что «магия» никуда не исчезла — она просто стала требовать более строгого отношения к типам данных. Использование пустых ссылок вместо NULL, параметров виртуальных таблиц и явного приведения типов — это три «кита», на которых строится стабильная работа отчетов в актуальных версиях 1С:ERP.