Сравнение значений во встроенном языке 1С 8.3: что сравнивается так, как ожидаешь, а что нет

· 9 мин

Содержание

Два значения в 1С сравнивают операторами =, <>, <, >, <=, >=, а результат всегда имеет тип Булево. Частое заблуждение — что = сравнивает «по смыслу». Нет: строки сравниваются с учётом регистра, массивы и структуры — по ссылке на экземпляр, а дата хранит ещё и время до секунды.

Отсюда «плавающие» ошибки в коде: условие выглядит верным, но ветка не срабатывает. Правила сравнения заданы платформой 8.3 и одинаковы в Бухгалтерии, ЗУП, УТ и любой другой конфигурации.

Операторы сравнения в 1С и логика условий

Набор операторов короткий:

ОператорСмыслДля каких типов
=равнолюбые
<>не равнолюбые
<, >, <=, >=меньше / большеЧисло, Строка, Дата, Булево — и только одного типа

Условия объединяют через И, ИЛИ, НЕ. Приоритет: сначала НЕ, затем И, затем ИЛИ. Если сомневаетесь — ставьте скобки, это бесплатно.

Если (Сумма > 0 И Количество > 0) ИЛИ ЭтоВозврат Тогда
    // ...
КонецЕсли;

Сокращённое вычисление условий

Логические выражения вычисляются слева направо и останавливаются, как только результат известен. Если левая часть И ложна, правая не выполняется. Этим пользуются, чтобы не получить ошибку обращения к полю:

Если ЗначениеЗаполнено(Договор) И Договор.ВидДоговора = Перечисления.ВидыДоговоровКонтрагентов.СПокупателем Тогда
    // обращение к реквизиту только для заполненной ссылки
КонецЕсли;

💡 Ставьте первым дешёвое условие (сравнение переменной), вторым — дорогое (обращение к реквизиту через точку, которое читает объект из базы).

Сравнение значений разных типов

Оператор = между значениями разных типов не падает, а просто возвращает Ложь: 1 = "1" даёт Ложь. А вот «больше-меньше» для разных типов вызывает ошибку времени выполнения:

Операция сравнения на больше-меньше допустима только для значений совпадающих примитивных типов (Булево, Число, Строка, Дата)

Типичный источник — реквизит составного типа или значение, прочитанное из файла как строка. Перед сравнением приведите тип: Число(СтрокаСуммы), Строка(Номер), или проверьте его явно (см. раздел про типы ниже).

Числа и точность

Числа в 1С десятичные, а не двоичные с плавающей точкой. Поэтому 0.1 + 0.2 = 0.3 возвращает Истина, в отличие от многих языков. Подвох в другом — в делении:

Доля = 1 / 3;
Результат = Доля * 3;        // 0,999...
Сообщить(Результат = 1);     // Ложь
Сообщить(Окр(Результат, 2) = 1); // Истина

Суммы после деления и распределения сравнивайте после Окр(Значение, 2) или через допуск: Макс(А - Б, Б - А) < 0.01.

Сравнение ссылок, объектов и коллекций

Ссылки

Ссылки на элементы справочников, документы, перечисления сравниваются корректно: = возвращает Истина, если обе ссылки указывают на один и тот же элемент базы. Именно так и надо проверять «тот же ли это контрагент»:

Если Документ.Контрагент = Справочники.Контрагенты.НайтиПоКоду("000000012") Тогда

Для перечислений сравнение пишут через менеджер:

Если СтатусЗаказа = Перечисления.СтатусыЗаказовКлиентов.Закрыт Тогда

Для предопределённых элементов используйте ПредопределенноеЗначение("Справочник.Валюты.Рубль") в клиентском коде — там менеджеры справочников недоступны.

Объекты

Объекты (СправочникОбъект, ДокументОбъект) сравниваются как экземпляры в памяти. Два объекта, полученные двумя вызовами ПолучитьОбъект() для одного элемента, через = будут не равны. Сравнивайте их ссылки:

Если Объект1.Ссылка = Объект2.Ссылка Тогда

Массивы, структуры, таблицы значений

С коллекциями та же история: = сравнивает, один ли это экземпляр, а не одинаково ли содержимое.

А = Новый Массив; А.Добавить(1);
Б = Новый Массив; Б.Добавить(1);
Сообщить(А = Б); // Ложь

Для сравнения по содержимому нужна своя функция. Простой вариант для массивов примитивных значений и ссылок:

Функция МассивыРавны(Массив1, Массив2)
    Если Массив1.Количество() <> Массив2.Количество() Тогда
        Возврат Ложь;
    КонецЕсли;
    Для Индекс = 0 По Массив1.ВГраница() Цикл
        Если Массив1[Индекс] <> Массив2[Индекс] Тогда
            Возврат Ложь;
        КонецЕсли;
    КонецЦикла;
    Возврат Истина;
КонецФункции

Функция учитывает порядок элементов. Если порядок не важен — сначала сложите значения в Соответствие и проверяйте наличие ключей. На сервере для сложных структур иногда сравнивают сериализованный вид через ЗначениеВСтрокуВнутр(), но это работает только для сериализуемых значений и зависит от порядка ключей.

Строки: регистр, пробелы, частичное совпадение

"Иванов" = "иванов" возвращает Ложь: оператор сравнивает строки посимвольно и с учётом регистра. Варианты сравнения без регистра:

  1. Привести обе строки к одному регистру: ВРег(Стр1) = ВРег(Стр2).
  2. Вызвать СтрСравнить(Стр1, Стр2) — функция сравнивает без учёта регистра и возвращает 0, если строки равны, -1 или 1, если первая меньше или больше. Появилась в платформе 8.3.6.
Если СтрСравнить(ВведенноеИмя, Пользователь.Наименование) = 0 Тогда
    // совпадение без учёта регистра
КонецЕсли;

Пробелы и невидимые символы

Строки из Excel, XML или ручного ввода часто несут хвостовые пробелы, неразрывный пробел (Символы.НПП) или перевод строки. Для = это разные строки. Перед сравнением чистите:

Значение = СокрЛП(СтрЗаменить(Значение, Символы.НПП, " "));

Начало, конец, вхождение

Для частичного сравнения строк есть готовые функции:

  • 🔍 СтрНачинаетсяС(Стр, "ООО") — строка начинается с подстроки;
  • 🔚 СтрЗаканчиваетсяНа(Стр, ".xml") — заканчивается на подстроку;
  • 📍 СтрНайти(Стр, "ИНН") — позиция вхождения, 0 если не найдено.

Эти три функции учитывают регистр, поэтому при необходимости оборачивайте аргументы в НРег().

Сравнение строк «больше-меньше»

"Б" > "А" вернёт Истина: строки сравниваются по кодам символов. Для сортировки русских слов это работает предсказуемо, но цифры внутри строк сравниваются как символы: "10" < "9" — Истина. Номера документов с префиксами сравнивайте как числа после выделения цифровой части или используйте номера одинаковой длины с ведущими нулями.

Даты и время: сравнение с точностью до дня

Тип Дата в 1С хранит дату и время с точностью до секунды. Поэтому сравнение ДатаДокумента = '20260301' сработает только для документа, проведённого ровно в 00:00:00.

Для сравнения по дню приведите обе стороны к началу дня:

Если НачалоДня(ДатаДокумента) = НачалоДня(ТекущаяДатаСеанса()) Тогда
    // документ сегодняшний
КонецЕсли;

Аналогично работают НачалоМесяца(), НачалоКвартала(), НачалоГода() и парные КонецДня(), КонецМесяца().

Попадание в период

Если ДатаДокумента >= НачалоПериода И ДатаДокумента <= КонецДня(КонецПериода) Тогда

КонецДня() здесь нужен, если КонецПериода пришёл из поля ввода без времени — иначе документы последнего дня выпадут.

Разница между датами

Вычитание дат даёт число секунд:

Секунд = Дата2 - Дата1;
Дней = (НачалоДня(Дата2) - НачалоДня(Дата1)) / 86400;

Пустая дата

Незаполненная дата — это '00010101', то есть 1 января 0001 года. Проверка: Дата = '00010101' или Не ЗначениеЗаполнено(Дата). Сравнение «пустая дата меньше любой другой» верно, это используют при поиске минимальной даты в цикле.

ТекущаяДата() возвращает время компьютера, где выполняется код: на клиенте это время пользователя, на сервере — сервера. Для бизнес-логики берите ТекущаяДатаСеанса(), иначе сравнения дат будут разными у разных пользователей.

Неопределено, NULL, пустые ссылки и проверка типа

Чем отличаются «пустые» значения

ЗначениеОткуда берётсяКак проверить
Неопределенонеинициализированная переменная, пустой реквизит составного типа, «не найдено» в СоответствиеЗначение = Неопределено
NULLполе запроса при левом соединении без пары, обращение через точку к пустой ссылке в запросеЗначение = NULL в коде, ЕСТЬ NULL в запросе
пустая ссылканезаполненный реквизит ссылочного типаСсылка.Пустая(), = Справочники.Х.ПустаяСсылка()
0, "", '00010101', Ложьзначения по умолчанию примитивных типовсравнить с ними

Универсальная проверка — ЗначениеЗаполнено(). Она возвращает Ложь для всех значений из таблицы: пустой ссылки, Неопределено, NULL, нуля, пустой строки, пустой даты и Ложь.

Не используйте ЗначениеЗаполнено() для чисел, где ноль — допустимое значение. Остаток 0 и «остаток не получен» для неё одинаковы. В таком случае проверяйте отдельно: Остаток = NULL или Остаток = Неопределено.

Как сравнить тип значения

Если ТипЗнч(Значение) = Тип("Строка") Тогда
ИначеЕсли ТипЗнч(Значение) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
КонецЕсли;

Для реквизитов составного типа удобно проверять принадлежность к описанию типов: Метаданные.Документы.Х.Реквизиты.Основание.Тип.СодержитТип(ТипЗнч(Значение)).

Ещё один приём для ссылок любого вида — Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(Значение)): так проверяют, что значение является ссылкой на любой справочник. Аналогичные методы есть у менеджеров Документы, Перечисления и других.

Упорядочивание значений разных типов

Если нужно сравнить на «больше-меньше» значения, тип которых заранее неизвестен, оператор < даст ошибку. Для этого есть объект СравнениеЗначений:

Сравнение = Новый СравнениеЗначений;
Результат = Сравнение.Сравнить(Значение1, Значение2); // -1, 0 или 1

Он умеет упорядочивать значения разных типов без исключения, что полезно в универсальных обработках и при сортировке смешанных списков.

Сравнения в запросах и скорость выполнения

В языке запросов операторы те же: =, <>, <, >, <=, >=, плюс МЕЖДУ, В, ПОДОБНО, ЕСТЬ NULL, ССЫЛКА. Но работают они на стороне СУБД, и правила там свои.

NULL в запросе

В запросе Поле = NULL не вернёт строки, даже если поле действительно NULL. Правильно:

ГДЕ Остатки.КоличествоОстаток ЕСТЬ NULL

А для расчётов — заменять NULL на значение по умолчанию:

ГДЕ ЕСТЬNULL(Остатки.КоличествоОстаток, 0) < Заказы.Количество

Сравнение с параметром, а не с литералом

Ссылки и даты передавайте параметрами: ГДЕ Док.Контрагент = &Контрагент. Для предопределённых значений и перечислений есть ЗНАЧЕНИЕ():

ГДЕ Заказ.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказовКлиентов.Закрыт)

Сравнить значение со списком быстрее одним условием В (&СписокКонтрагентов), где параметр — массив, чем цепочкой из десятков ИЛИ.

Тип значения в запросе

Для полей составного типа (Регистратор, Основание) тип проверяют оператором ССЫЛКА:

ГДЕ Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг

Вариант ТИПЗНАЧЕНИЯ(Поле) = ТИП(Документ.РеализацияТоваровУслуг) тоже рабочий, но ССЫЛКА короче и читается проще.

Строки неограниченной длины

Поле строкового типа неограниченной длины (комментарий, текст) нельзя напрямую сравнивать через = в условиях и соединениях. Приведите его к строке фиксированной длины:

ГДЕ ВЫРАЗИТЬ(Док.Комментарий КАК СТРОКА(100)) = &Текст

Или используйте ПОДОБНО "%" + &Текст + "%" для поиска вхождения, понимая, что такой поиск не использует индексы.

Что замедляет сравнения

  • 🐢 Функция над индексированным полем. НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) = &День заставляет СУБД вычислять функцию для каждой строки. Пишите Док.Дата МЕЖДУ &НачалоДня И &КонецДня.
  • 🐢 Разыменование через точку в условии. ГДЕ Док.Контрагент.ИНН = &ИНН добавляет неявное соединение со справочником. Для составных полей — соединение с каждой таблицей типа. Лучше найти ссылку заранее и сравнить Док.Контрагент = &Контрагент.
  • 🐢 Сравнение в цикле вместо запроса. Перебор тысяч строк с обращением к реквизитам через точку — это тысячи чтений из базы. Одним запросом с условием это делается за один проход.
  • 🐢 Поиск в таблице значений перебором. Для повторного поиска используйте НайтиСтроки() с проиндексированной колонкой (Таблица.Индексы.Добавить("Номенклатура")) или Соответствие, где ключ — искомое значение.

💡 В виртуальных таблицах регистров условие сравнения ставьте в параметры таблицы (Остатки(&Дата, Склад = &Склад)), а не в ГДЕ после неё: тогда отбор применится до расчёта итогов.

Частые ошибки при сравнении значений

Диагностика: выберите симптом

Частые вопросы

Можно ли сравнить Булево через больше-меньше?
Да. Булево входит в список типов для операторов < и >, при этом Ложь < Истина. На практике это нужно разве что при сортировке.
Равны ли Неопределено и NULL?
Нет. Это разные значения разных типов, и Неопределено = NULL возвращает Ложь. Обе стороны при этом считаются незаполненными для ЗначениеЗаполнено().
Почему сравнение двух ссылок на клиенте работает, а обращение к их реквизитам нет?
Сравнение ссылок не требует чтения базы, поэтому доступно везде. Реквизит через точку на тонком клиенте прочитать нельзя: его получают серверным вызовом.
Как сравнить значение с несколькими вариантами без длинной цепочки ИЛИ?
Сложите варианты в массив и проверьте Массив.Найти(Значение) <> Неопределено. Метод возвращает индекс найденного элемента или Неопределено.
Чем СтрСравнить() лучше ВРег() с оператором =?
Результат тот же, но СтрСравнить() не создаёт две новые строки и сразу даёт порядок (-1, 0, 1), который можно использовать для сортировки.