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