Сравнение значений во встроенном языке 1С 8.3: что сравнивается так, как ожидаешь, а что нет
Содержание
Два значения в 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) = ВРег(Стр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), который можно использовать для сортировки.