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