Почему отбор в СКД по вычисляемому полю 'МаршрутныйЛист = "Пусто"' выводит лишние строки?

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

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

Наш коллега столкнулся с проблемой: при формировании отчета в СКД, несмотря на то что в консоли запросов и консоли СКД все работает верно, отбор по полю "МаршрутныйЛист" со значением "Пусто" возвращает 90 строк вместо ожидаемых 5. Поле "МаршрутныйЛист" является вычисляемым и формируется с помощью функции ЕСТЬNULL(ВТ.МаршрутныйЛист.Ссылка, "Пусто").

Анализ исходного запроса

Давайте подробно рассмотрим исходный запрос, который используется в СКД:


ВЫБРАТЬ
    МаршрутныйЛист.Ссылка КАК МаршрутныйЛист,
    МаршрутныйЛист.Проведен КАК Проведен
ПОМЕСТИТЬ ВТ
ИЗ
    Документ.МаршрутныйЛист КАК МаршрутныйЛист
ГДЕ
    МаршрутныйЛист.Дата МЕЖДУ &ДатаНач И &ДатаКон
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Транспорт.Код КАК Код,
    Транспорт.Ссылка КАК Автомобиль,
    Транспорт.ДатаВвода КАК ДатаВвода,
    Транспорт.ГосНомер КАК ГосНомер,
    Транспорт.Модель КАК Модель,
    ЕСТЬNULL(ВТ.МаршрутныйЛист.Ссылка, "Пусто") КАК МаршрутныйЛист,
    ВТ.Проведен КАК Проведен
ИЗ
    Справочник.Транспорт КАК Транспорт
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ВТ
        ПО Транспорт.Ссылка = ВТ.МаршрутныйЛист.ТранспортноеСредство.ОсновноеСредство.Ссылка
ГДЕ
    Транспорт.Актуальность = ЛОЖЬ

Здесь мы сначала получаем список маршрутных листов за определенный период во временную таблицу (ВТ). Затем мы выполняем левое соединение справочника "Транспорт" с этой временной таблицей. Ключевым моментом является строка:

ЕСТЬNULL(ВТ.МаршрутныйЛист.Ссылка, "Пусто") КАК МаршрутныйЛист

Эта конструкция должна гарантировать, что если для транспорта не найден соответствующий маршрутный лист, то поле "МаршрутныйЛист" будет содержать строковое значение "Пусто" вместо NULL.

Почему отбор 'Равно "Пусто"' работает некорректно?

Проблема возникает, когда мы пытаемся применить отбор МаршрутныйЛист = "Пусто" на закладке "Отбор" в настройках СКД. Ожидается, что будут отображены только те строки, где МаршрутныйЛист действительно равен строке "Пусто", то есть строки, для которых не нашлось соответствующих маршрутных листов. Однако вместо 5 таких строк мы получаем 90.

Для того чтобы разобраться в причинах, нам необходимо понять, как СКД обрабатывает отборы и вычисляемые поля, а также рассмотреть особенности функции ПРЕДСТАВЛЕНИЕССЫЛКИ, которая часто используется в связке с такими полями.

1. Особенности работы СКД с NULL и вычисляемыми полями

Значение NULL в 1С (и в базах данных в целом) означает отсутствие значения, оно отличается от пустой строки, нуля или пустой ссылки. Это важно, поскольку NULL не равно ничему, даже самому себе (то есть NULL = NULL вернет ЛОЖЬ в SQL). Функция ЕСТЬNULL, которую мы используем, предназначена для замены NULL на указанное значение, в нашем случае – на строку "Пусто".

Отборы в СКД могут применяться на разных уровнях:

  1. К набору данных: В этом случае СКД пытается "встроить" условие отбора непосредственно в секцию ГДЕ основного запроса. Это самый эффективный способ, так как фильтрация происходит на уровне базы данных, и в память загружается меньше данных.
  2. К группировкам: Отбор применяется к данным после их получения, фильтруя записи, выводимые в конкретной группировке.
  3. К отчету в целом: Отбор применяется к уже сформированным данным, что часто означает, что сначала выбираются все данные, а затем они фильтруются программно.

В нашем случае, если отбор МаршрутныйЛист = "Пусто" применяется к вычисляемому полю, которое внутренне еще может быть связано с ссылочным типом, СКД может попытаться преобразовать это условие в запрос. И здесь кроется потенциальная проблема: если на каком-то этапе СКД пытается применить отбор к исходному полю ВТ.МаршрутныйЛист.Ссылка до того, как сработает ЕСТЬNULL, или если оно пытается сравнить строковое "Пусто" с неопределенным значением ссылки, то отбор может не сработать так, как мы ожидаем.

2. Влияние функции ПРЕДСТАВЛЕНИЕССЫЛКИ

Обратим внимание на запрос, сгенерированный компоновщиком СКД, который наш коллега предоставил:


<query>ВЫБРАТЬ
    МаршрутныйЛист.Ссылка КАК МаршрутныйЛист
ПОМЕСТИТЬ ВТ
ИЗ
    Документ.МаршрутныйЛист КАК МаршрутныйЛист
ГДЕ
    МаршрутныйЛист.Дата МЕЖДУ &ДатаНач И &ДатаКон
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Транспорт.Код КАК Код,
    Транспорт.Ссылка КАК Автомобиль,
    Транспорт.ГосНомер КАК ГосНомер,
    ЕСТЬNULL(ВТ.МаршрутныйЛист.Ссылка, "Пусто") КАК МаршрутныйЛист,
    Транспорт.Представление КАК ТранспортПредставление,
    ПРЕДСТАВЛЕНИЕССЫЛКИ(ЕСТЬNULL(ВТ.МаршрутныйЛист.Ссылка, "Пусто")) КАК МаршрутныйЛистПредставление
ИЗ
    Справочник.Транспорт КАК Транспорт
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ВТ
        ПО Транспорт.Ссылка = ВТ.МаршрутныйЛист.ТранспортноеСредство.ОсновноеСредство.Ссылка
ГДЕ
    Транспорт.НеАктуален = ЛОЖЬ</query>

Здесь мы видим дополнительное поле:

ПРЕДСТАВЛЕНИЕССЫЛКИ(ЕСТЬNULL(ВТ.МаршрутныйЛист.Ссылка, "Пусто")) КАК МаршрутныйЛистПредставление

Эта строка является критически важной. Функция ПРЕДСТАВЛЕНИЕССЫЛКИ (или ПРЕДСТАВЛЕНИЕ в СКД) предназначена для получения строкового представления ссылочных типов. Однако, если аргумент ЕСТЬNULL(ВТ.МаршрутныйЛист.Ссылка, "Пусто") возвращает строку "Пусто" (а не ссылку), функция ПРЕДСТАВЛЕНИЕССЫЛКИ не знает, как с этим работать. Она не предназначена для получения "представления" произвольной строки. В результате, ПРЕДСТАВЛЕНИЕССЫЛКИ("Пусто") может возвращать пустую строку, NULL или неопределенное значение, что полностью ломает логику отбора.

Если отбор применяется к полю МаршрутныйЛистПредставление, то условие МаршрутныйЛистПредставление = "Пусто" будет сравнивать "Пусто" с тем, что вернет ПРЕДСТАВЛЕНИЕССЫЛКИ("Пусто"), а это, скорее всего, не "Пусто".

Как диагностировать проблему

Для точного понимания, как СКД обрабатывает отбор, мы рекомендуем следующие шаги:

  1. Проверяем Макет компоновщика данных: Как правильно заметили коллеги, необходимо посмотреть на вкладку "Макет компоновщика данных" в консоли СКД. Это позволяет увидеть итоговый запрос, который будет выполняться к базе данных, а также понять, как СКД пытается интерпретировать наши отборы. Ищите секцию ГДЕ или дополнительные условия, которые могли быть добавлены.
  2. Используем отладчик: Если вы разрабатываете отчет программно, используйте отладчик в обработчике события ПриКомпоновкеРезультата. Вы можете получить текст запроса через объект МакетКомпоновкиДанных и проанализировать его.

Анализ макета показал, что СКД действительно добавляет ПРЕДСТАВЛЕНИЕССЫЛКИ, что является основным источником проблемы.

Решения и обходные пути

Рассмотрим несколько подходов к решению данной проблемы.

1. Корректное использование представления ссылки и строки

Основной причиной является некорректное применение функции ПРЕДСТАВЛЕНИЕССЫЛКИ к полю, которое уже может быть строкой. Мы должны отделить логику получения ссылки от логики получения ее представления, особенно когда ссылка может быть NULL.

Вместо того чтобы комбинировать ЕСТЬNULL и ПРЕДСТАВЛЕНИЕССЫЛКИ в одном выражении, создадим два отдельных вычисляемых поля или скорректируем поле представления:

  1. Поле для ссылки/строки: Оставьте поле МаршрутныйЛист как ЕСТЬNULL(ВТ.МаршрутныйЛист.Ссылка, "Пусто"). Это поле будет иметь тип "Произвольный", и СКД будет понимать, что в нем может быть как ссылка, так и строка.
  2. Поле для представления: Создайте отдельное вычисляемое поле, например, МаршрутныйЛистПредставлениеДляОтбора, и задайте для него следующее выражение:

ВЫБОР
    КОГДА ВТ.МаршрутныйЛист.Ссылка ЕСТЬ NULL
        ТОГДА "Пусто"
    ИНАЧЕ ПРЕДСТАВЛЕНИЕ(ВТ.МаршрутныйЛист.Ссылка)
КОНЕЦ

Теперь мы отбираем данные именно по этому новому полю МаршрутныйЛистПредставлениеДляОтбора. В этом случае, когда ссылка NULL, мы явно подставляем строку "Пусто", а когда ссылка существует, мы корректно получаем ее представление функцией ПРЕДСТАВЛЕНИЕ(), которая работает с любыми типами значений. Критически важно: используйте ПРЕДСТАВЛЕНИЕ(), а не ПРЕДСТАВЛЕНИЕССЫЛКИ(), так как первая более универсальна и подходит для вычисляемых полей, которые могут менять свой тип (ссылка/строка) в зависимости от значения.

2. Использование операторов 'Содержит' или 'Начинается с' как обходной путь

Наш коллега обнаружил интересный обходной путь: если вместо условия Равно "Пусто" использовать Содержит "П" или Начинается с "Пу", то отбор начинает работать корректно и выводит ожидаемые 5 строк.

Это может происходить потому, что операторы Содержит и Начинается с менее требовательны к точному совпадению типа данных и значения. Они могут выполнять неявное преобразование к строке, прежде чем сравнивать, что позволяет им корректно найти нашу строку "Пусто", даже если поле МаршрутныйЛистПредставление в некоторых случаях оказывается пустым или NULL из-за ошибок в ПРЕДСТАВЛЕНИЕССЫЛКИ.

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

3. Явное создание булевого поля для отбора NULL

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

МаршрутныйЛистНеЗаполнен

с выражением:

ВТ.МаршрутныйЛист.Ссылка ЕСТЬ NULL

Теперь в настройках отбора СКД мы можем установить условие:

МаршрутныйЛистНеЗаполнен = Истина

Это условие является абсолютно однозначным для СКД, так как оно работает с чистым булевым значением, полученным напрямую из проверки NULL. СКД без труда преобразует это условие в секцию ГДЕ запроса, что обеспечит максимальную производительность и корректность отбора.

Важные выводы

  1. Различайте NULL и пустую строку: Всегда помните, что NULL – это отсутствие значения, а "Пусто" – это конкретная строка. Сравнение NULL с чем-либо (даже с NULL) часто дает ЛОЖЬ (или UNKNOWN в SQL).
  2. Осторожно с ПРЕДСТАВЛЕНИЕССЫЛКИ: Используйте эту функцию только для ссылочных типов. Для полей, которые могут быть как ссылкой, так и строкой (или другими типами), предпочтительнее использовать универсальную функцию ПРЕДСТАВЛЕНИЕ() или явно проверять тип значения перед получением представления.
  3. Отладка в СКД: При возникновении неочевидного поведения всегда начинайте с просмотра "Макета компоновки данных". Это самый надежный способ понять, какой запрос СКД генерирует на самом деле.
  4. Явные условия отбора: По возможности, старайтесь формулировать условия отбора максимально явно, особенно когда речь идет о NULL значениях или вычисляемых полях. Использование булевых полей для таких условий часто оказывается самым надежным решением.

Надеемся, что этот подробный разбор поможет вам избежать подобных проблем при работе с СКД и сделает ваши отчеты более предсказуемыми и точными.

← На главную