С выходом новых версий платформы «1С:Предприятие 8.3» (начиная с 8.3.20) разработчики получили долгожданный «синтаксический сахар» в языке запросов — функции для работы со строками, такие как ЛЕВ, ПРАВ, СОКРЛП и другие. Однако при попытке их активного использования в секции ГДЕ или в условиях связи многие столкнулись с неприятной ошибкой: «Нельзя сравнивать поля неограниченной длины и поля несовместимых типов». Рассмотрим подробнее, почему это происходит и как правильно работать с новыми функциями.
Проанализируем ситуацию, с которой сталкивается программист. Допустим, нам нужно выбрать номенклатуру, наименование которой начинается на букву «м». Мы пишем интуитивно понятный запрос:
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
ЛЕВ(Номенклатура.Наименование, 1) = "м"
На этапе выполнения этого запроса система выдает ошибку. Выясним причину: в системе компоновки данных или при выполнении запроса 1С жестко разделяет строки на строки ограниченной длины (например, Строка(100)) и строки неограниченной длины (Memo-поля в терминах баз данных). По правилам языка запросов 1С, поля неограниченной длины нельзя использовать в операциях сравнения (=, <>, <, >) и в группировках.
Функция ЛЕВ(Строка, Количество), в отличие от классической функции ПОДСТРОКА, спроектирована таким образом, что тип её результата всегда определяется как строка неограниченной длины. Это происходит потому, что второй параметр функции может быть не константой, а выражением или полем из базы данных, и платформа «перестраховывается», назначая результату максимально универсальный тип.
Самый простой и очевидный способ заставить запрос работать — это явно указать платформе, какую длину строки мы ожидаем получить. Для этого используется оператор ВЫРАЗИТЬ. Рассмотрим, как изменится наш запрос:
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
ВЫРАЗИТЬ(ЛЕВ(Номенклатура.Наименование, 1) КАК СТРОКА(1)) = "м"
В данном случае мы явно говорим парсеру запросов, что результат функции ЛЕВ нужно привести к типу Строка(1). После этого сравнение с литералом "м" становится легитимным, и ошибка исчезает.
Важный нюанс: использование конструкции ВЫРАЗИТЬ(ЛЕВ(...)) часто является избыточным. Если ваша цель — просто получить первый символ, то выражение ВЫРАЗИТЬ(Наименование КАК СТРОКА(1)) даст точно такой же результат, но будет работать быстрее, так как не потребует вызова дополнительных строковых функций внутри СУБД.
Если нам нужно проверить вхождение символов в начале строки, стоит вспомнить про классический оператор ПОДОБНО. Посмотрим на пример:
ВЫБРАТЬ
Номенклатура.Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Наименование ПОДОБНО "м%"
Этот вариант является наиболее предпочтительным по нескольким причинам:
ПОДОБНО с маской в конце строки ("Текст%") позволяет SQL-серверу использовать индекс по полю Наименование (Index Seek). Любая же функция (ЛЕВ, ПОДСТРОКА, ВЫРАЗИТЬ) превращает поиск в сканирование всей таблицы (Index Scan), что на больших справочниках приведет к существенным задержкам.Многих разработчиков удивляет, почему функция ПОДСТРОКА(Поле, 1, 1) работает без ошибок сравнения, а ЛЕВ(Поле, 1) — нет. Разберем ситуацию глубже:
В функции ПОДСТРОКА(ИсходнаяСтрока, Начало, Длина) третий параметр (Длина) по стандарту 1С обязательно должен быть числовой константой. Благодаря этому платформа еще при разборе запроса точно знает размер результирующего поля и назначает ему тип строки фиксированной длины.
В новых же функциях (ЛЕВ, ПРАВ) второй параметр может быть динамическим. Рассмотрим пример:
ВЫБРАТЬ
ЛЕВ(Таблица.Наименование, Таблица.ДлинаПрефикса)
ИЗ
ВременнаяТаблица КАК Таблица
Поскольку ДлинаПрефикса может быть разной для каждой строки, 1С не может заранее определить фиксированную длину для всей колонки результата. Единственный выход для системы — поместить результат в универсальный контейнер неограниченной длины (NVARCHAR(MAX) в MSSQL или TEXT в PostgreSQL).
Проанализируем, как эти функции выглядят «под капотом» со стороны СУБД. Платформа 1С стремится обеспечить идентичное поведение кода на разных серверах (Microsoft SQL, PostgreSQL, Oracle, IBM DB2). Однако стандартные функции LEFT в разных СУБД могут вести себя по-разному при передаче отрицательных значений или специфических кодировок.
В некоторых релизах платформы (например, в зависимости от режима совместимости) можно увидеть, что 1С транслирует ЛЕВ не в прямой вызов LEFT(), а в сложную конструкцию CASE WHEN или даже собственную встроенную функцию СУБД (например, GET_LEFT), чтобы гарантировать, что если пользователь передал во второй параметр число -1, запрос не «упал» с ошибкой, а вернул пустую строку.
Проблема неограниченной длины актуальна не только для ЛЕВ. Рассмотрим остальные функции, вошедшие в обновление:
ПРАВ(Строка, Длина) — аналогично возвращает строку неограниченной длины.СОКРЛ(Строка), СОКРП(Строка), СОКРЛП(Строка) — удаление пробелов также превращает строку фиксированной длины в «безразмерную». Будьте осторожны при использовании их в соединениях таблиц.СТРЗАМЕНИТЬ(ГдеИщем, ЧтоИщем, НаЧтоЗаменяем) — крайне ресурсоемкая операция для уровня СУБД. Результат — всегда неограниченная длина.СТРНАЙТИ(Строка, Подстрока) — возвращает Число, поэтому проблем со сравнением не вызывает, но помните, что поиск подстроки внутри запроса почти всегда приводит к полному сканированию таблицы.Для эффективной работы и исключения ошибок рекомендуем придерживаться следующих правил:
ЛЕВ, ПРАВ или СОКРЛП используется в секции ГДЕ для сравнения через =, всегда оборачивайте его в ВЫРАЗИТЬ(... КАК СТРОКА(N)).ПО / JOIN). Это «убивает» производительность запроса. Если такая необходимость возникла, старайтесь подготовить данные во временной таблице, заранее типизировав их.ВЫБРАТЬ для вывода данных в отчет (где не важна типизация 1С), они работают прекрасно и делают код запроса гораздо читаемее.ЛЕВ в колонку временной таблицы, 1С создаст эту колонку как поле неограниченной длины. Вы не сможете построить по ней индекс, пока не примените ВЫРАЗИТЬ при создании таблицы.Рассмотрим финальный пример правильного создания временной таблицы с типизацией:
ВЫБРАТЬ
ВЫРАЗИТЬ(ЛЕВ(Номенклатура.Наименование, 5) КАК СТРОКА(5)) КАК Префикс,
Номенклатура.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Справочник.Номенклатура КАК Номенклатура;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Данные.Ссылка
ИЗ
ВТ_Данные КАК ВТ_Данные
ГДЕ
ВТ_Данные.Префикс = "АБВ12"
В этом примере мы обеспечили корректную работу сравнения и создали условия для стабильной работы платформы с типами данных.