Как сравнивать ссылки в языке запросов 1С 8.3
Содержание
Ссылки в запросе 1С сравнивают обычными операторами = и <>. Платформа сверяет не наименование и не код, а внутренний идентификатор объекта вместе с его типом. Поэтому условие Номенклатура = &Товар отберёт только ту самую позицию справочника, даже если в базе есть другой элемент с таким же названием.
Проблемы начинаются там, где в поле лежит не обычная ссылка, а составной тип, пустая ссылка или NULL, прилетевший из левого соединения. Прямое сравнение с ними ведёт себя не так, как ждёшь: строки молча пропадают из выборки или, наоборот, попадают лишние. Ниже разберу все случаи с примерами на языке запросов 8.3.
Чем сравнение ссылок отличается от строк и чисел
Ссылка хранится в базе как двоичный идентификатор (GUID) плюс код типа. Когда вы пишете Контрагент = &Клиент, движок сравнивает именно эту пару. Отсюда два следствия.
Первое: две ссылки равны, только если указывают на один и тот же объект одного типа. Ссылка на документ и ссылка на справочник не равны между собой никогда, даже если их идентификаторы случайно совпали — тип входит в сравнение.
Второе: сравнивать ссылку через её представление бессмысленно и медленно. Не пишите Контрагент.Наименование = "ООО Ромашка" там, где нужна конкретная ссылка — наименований-дублей может быть несколько, а обращение к реквизиту добавляет неявное соединение с таблицей справочника.
Третье: ссылку нельзя сравнить со строкой или с уникальным идентификатором напрямую. Если на руках только строковый GUID из внешней системы, сначала получите из него ссылку во встроенном языке — Справочники.Номенклатура.ПолучитьСсылку(Новый УникальныйИдентификатор(СтрокаGUID)) — и передайте результат параметром. Преобразования «строка → ссылка» внутри текста запроса не существует.
Для ссылок доступны операторы:
- ✅
=и<>— равно / не равно; - ✅
В (&Список)— вхождение в список или результат вложенного запроса; - ✅
В ИЕРАРХИИ (&Группа)— элемент сам или любой его родитель равен указанной группе; - ✅
ЕСТЬ NULL/ЕСТЬ НЕ NULL— проверка наNULL.
Операторы >, <, МЕЖДУ к ссылкам применять не стоит: порядок идентификаторов не несёт смысла, сортировать по нему бесполезно.
ВЫБРАТЬ
Продажи.Номенклатура,
Продажи.Количество
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Номенклатура = &Товар
И Продажи.Контрагент В (&СписокКлиентов)
Список для оператора В удобно передавать параметром-массивом из встроенного языка — Запрос.УстановитьПараметр("СписокКлиентов", МассивСсылок). Отдельное перечисление значений прямо в тексте допустимо, но массив гибче.
Оператор В принимает не только массив, но и результат вложенного запроса. Так сравнивают ссылку со списком, собранным прямо в базе, не выгружая его во встроенный язык:
ВЫБРАТЬ
Продажи.Номенклатура
ИЗ
РегистрНакопления.Продажи КАК Продажи
ГДЕ
Продажи.Номенклатура В (
ВЫБРАТЬ
Цены.Номенклатура
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК Цены
ГДЕ
Цены.Цена > &Порог)
Пустая ссылка, NULL и Неопределено — три разных «пусто»
Самая частая путаница в этой теме. В поле может оказаться одно из трёх «пустых» значений, и сравниваются они по-разному.
- 🔹 Пустая ссылка — это полноценная ссылка конкретного типа без заполненного идентификатора. Так выглядит незаполненный реквизит, у которого тип ровно один (например,
Контрагент). - 🔹 NULL — признак «значения нет вообще». Появляется в результатах соединений, когда для строки не нашлось пары, и у виртуальных таблиц остатков.
- 🔹 Неопределено — значение типа «Неопределено». Его отдаёт незаполненное поле составного типа (например,
Регистраторили субконто, которое может быть и справочником, и документом).
Чтобы отобрать строки с незаполненной ссылкой одного типа, сравнивайте с пустой ссылкой через оператор ЗНАЧЕНИЕ:
ВЫБРАТЬ
Товары.Ссылка
ИЗ
Справочник.Номенклатура КАК Товары
ГДЕ
Товары.ОсновнойПоставщик = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
NULL так не поймать. Любое сравнение с NULL через = или <> возвращает не истину и не ложь, а «неизвестно», и такая строка в выборку не попадает. Для NULL есть отдельный оператор:
ВЫБРАТЬ
Контрагенты.Ссылка,
Договоры.Ссылка КАК Договор
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
ПО Договоры.Владелец = Контрагенты.Ссылка
ГДЕ
Договоры.Ссылка ЕСТЬ NULL
Этот запрос вернёт контрагентов, у которых вообще нет договоров. Если же нужно подставить вместо NULL что-то осмысленное для дальнейших вычислений, примените функцию ЕСТЬNULL:
ВЫБРАТЬ
Номенклатура.Ссылка,
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
Разницу легко запомнить: ЕСТЬ NULL — это условие в ГДЕ, а ЕСТЬNULL(поле, значение) — функция подстановки в списке полей.
Сравнение полей составного типа
Поле составного типа хранит значение вместе с его типом. Сравнивать такое поле с конкретной ссылкой через = можно напрямую — платформа сама учтёт тип:
ГДЕ
Движения.Регистратор = &ДокументРеализации
А вот если нужно отобрать строки по типу, а не по конкретному значению, используйте оператор ССЫЛКА:
ВЫБРАТЬ
Движения.Регистратор
ИЗ
РегистрНакопления.ТоварыНаСкладах КАК Движения
ГДЕ
Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
Теперь главная тонкость составных типов. Чтобы обратиться к реквизиту объекта
, который лежит в составном поле, поле нужно привести к нужному типу оператором ВЫРАЗИТЬ. Без приведения реквизит вернёт NULL, потому что у разных типов составного поля набор реквизитов разный.
ВЫБРАТЬ
ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент
ИЗ
РегистрНакопления.ТоварыНаСкладах КАК Движения
ГДЕ
Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
Если убрать условие ССЫЛКА из ГДЕ, запрос не упадёт, но для всех «чужих» регистраторов поле Контрагент станет NULL. Приведение к типу, которому значение не соответствует, молча даёт NULL, а не ошибку — об этом легко забыть.
Подстановка значений: параметры, ЗНАЧЕНИЕ и динамический текст
Есть три способа подставить в условие сравнения конкретную ссылку.
1. Параметр запроса. Основной способ. Значение передаётся отдельно от текста, типы совпадают, инъекций нет.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура КАК Т
|ГДЕ Т.Родитель = &Группа";
Запрос.УстановитьПараметр("Группа", ВыбраннаяГруппа);
Результат = Запрос.Выполнить();
2. Оператор ЗНАЧЕНИЕ. Когда ссылка известна на этапе написания запроса — предопределённый элемент, значение перечисления или пустая ссылка. Параметр для этого не нужен:
ГДЕ
Товары.СтавкаНДС = ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.НДС20)
И Товары.Валюта = ЗНАЧЕНИЕ(Справочник.Валюты.ПустаяСсылка)
3. Динамическая сборка текста. Когда набор условий заранее неизвестен — одни фильтры заполнены, другие нет. Собирайте только текст условия, а значения всё равно передавайте параметрами:
ТекстУсловия = "ИСТИНА";
Если ЗначениеЗаполнено(Склад) Тогда
ТекстУсловия = ТекстУсловия + " И Остатки.Склад = &Склад";
КонецЕсли;
Запрос.Текст = СтрЗаменить(Шаблон, "&УсловиеОтбора", ТекстУсловия);
Если ЗначениеЗаполнено(Склад) Тогда
Запрос.УстановитьПараметр("Склад", Склад);
КонецЕсли;
Никогда не склеивайте идентификатор ссылки в текст запроса как строку. Это и небезопасно, и просто не сработает: язык запросов не умеет восстанавливать ссылку из строкового GUID. Любая ссылка попадает в запрос только параметром или через ЗНАЧЕНИЕ.
Сравнение ссылок в группировках, условиях ИМЕЮЩИЕ и итогах
Ссылка — нормальное поле группировки. По ней сворачивают строки так же, как по любому измерению:
ВЫБРАТЬ
Продажи.Контрагент КАК Контрагент,
СУММА(Продажи.Сумма) КАК Сумма
ИЗ
РегистрНакопления.Продажи КАК Продажи
СГРУППИРОВАТЬ ПО
Продажи.Контрагент
ИМЕЮЩИЕ
СУММА(Продажи.Сумма) > 100000
Строки с одной и той же ссылкой контрагента попадут в одну группу: сравнение внутри группировки работает по тому же идентификатору. В условии ИМЕЮЩИЕ можно сравнивать и саму ссылку, но чаще там фильтруют агрегат (СУММА, КОЛИЧЕСТВО), а отбор по ссылке оставляют в ГДЕ — это быстрее, потому что ГДЕ отсекает строки до группировки.
Если строите многоуровневые итоги, ссылка задаёт уровень:
ИТОГИ
СУММА(Сумма)
ПО
Контрагент,
Договор
Группировка по составному полю (например, по Регистратор) сведёт вместе значения разных типов корректно, но сортировать и подписывать такие группы в отчёте придётся аккуратно: представление у документа и у справочника выглядит по-разному.
Отдельная ловушка — сортировка по ссылке. Условие УПОРЯДОЧИТЬ ПО Контрагент без ключевого слова АВТОУПОРЯДОЧИВАНИЕ сортирует строки по внутреннему идентификатору, то есть фактически в случайном порядке. Чтобы контрагенты шли по наименованию, добавьте в конец запроса АВТОУПОРЯДОЧИВАНИЕ — тогда платформа сама заменит сортировку по ссылке на сортировку по полям представления справочника. Либо упорядочивайте явно по нужному реквизиту, например УПОРЯДОЧИТЬ ПО Контрагент.Наименование.
Что замедляет сравнение и как ускорить
Само по себе сравнение ссылок на равенство — быстрая операция: идентификатор занимает 16 байт и, если это измерение регистра или индексированный реквизит, поиск идёт по индексу. Тормозит обычно не оператор =, а то, как написано условие.
- 🐢 Сравнение через реквизит ссылки.
ГДЕ Контрагент.ИНН = &ИННдобавляет соединение со справочником контрагентов и не использует индекс самого поля. Если есть ссылка — сравнивайте ссылку. - 🐢
ВЫРАЗИТЬв условии по большой таблице. Приведение типа вГДЕмешает оптимизатору использовать индекс. Лучше сначала отобрать строки нужного типа операторомССЫЛКА, аВЫРАЗИТЬоставить для списка полей. - 🐢
В ИЕРАРХИИпо глубокому дереву. Оператор разворачивает всю ветку подчинённых групп — на большой иерархии это заметно дороже обычногоВ. Применяйте, только когда действительно нужны вложенные элементы. - 🐢 Огромные списки в
В (&Список). Передача тысяч ссылок массивом бьёт по плану запроса. Если список сам берётся из базы — соедините с таблицей-источником или используйте вложенный запрос вместо массива.
Отбор по ссылке дешевле всего ставить на измерение регистра или на поле, по которому есть индекс. Проверить план можно в технологическом журнале или в консоли запросов, сравнив время с индексом и без.
Ошибки, из-за которых сравнение «не работает»
Короткий список того, что чаще всего ломает отбор по ссылке, когда синтаксически запрос верен, а результат пустой или неправильный.
- Сравнение с
NULLчерез=. УсловиеДоговор = NULLне отберёт ничего — нужноДоговор ЕСТЬ NULL. - Путаница пустой ссылки и
NULL. После левого соединения пустых ссылок там нет, естьNULL; после прямого обращения к незаполненному реквизиту одного типа — наоборот, пустая ссылка, а неNULL. - Обращение к реквизиту составного поля без
ВЫРАЗИТЬ. Реквизит молча станетNULL, и условие по нему ничего не отберёт. - Сравнение ссылок разных типов в надежде на совпадение. Документ и справочник не равны никогда — проверьте, что оба операнда одного типа.
- Подстановка GUID строкой в текст запроса. Ссылка передаётся только параметром или через
ЗНАЧЕНИЕ. - Отбор
ВЫРАЗИТЬ(... КАК ...)без предварительной проверки типа. Для «чужих» значений приведение вернётNULL, и условие по приведённому реквизиту отсечёт нужные строки — сначала фильтруйте тип операторомССЫЛКА.
Почему условие `Ссылка = NULL` всегда ложно и ничего не отбирает?
NULL означает «значение неизвестно», а сравнение с неизвестным даёт результат «неизвестно» — ни истину, ни ложь. Платформа отбрасывает такие строки. Для проверки на NULL используйте оператор ЕСТЬ NULL.Чем `ЕСТЬ NULL` отличается от функции `ЕСТЬNULL()`?
ЕСТЬ NULL — логическое условие в секции ГДЕ или ИМЕЮЩИЕ, оно отбирает строки. ЕСТЬNULL(поле, значение) — функция в списке полей, она подставляет второе значение, когда первое оказалось NULL.Можно ли сравнить ссылку на документ со ссылкой на справочник?
ССЫЛКА.Как сравнить две ссылки во встроенном языке, а не в запросе?
=. В модуле Если Ссылка1 = Ссылка2 Тогда сравнивает объекты по идентификатору так же, как запрос. Приводить ссылку к строке или сравнивать по наименованию не нужно.Как в запросе проверить, что реквизит-ссылка заполнен?
ГДЕ Поле <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка). Функции ЗначениеЗаполнено в языке запросов нет — она работает только во встроенном языке.