Почему $filter в стандартном интерфейсе OData 1С отклоняется на этапе условия и как переписать запрос

· 9 мин

Содержание

Ошибка OData «операция не разрешена в предложении WHERE» (в русской локали — ГДЕ) почти всегда означает одно: выражение из параметра $filter платформа 1С превратила в условие запроса, и это условие недопустимо для поля такого типа. Чаще всего виноваты строковые реквизиты неограниченной длины, поля типа ХранилищеЗначения и сравнение составного поля без указания типа. Права доступа и версия платформы — причины второго ряда, но проверять их тоже нужно.

Решение — переписать фильтр: сравнивать ключи (_Key) вместо представлений, указывать тип через cast, обрезать строку функцией substring или отфильтровать данные уже на стороне клиента. Если запрос нужен сложный, правильнее сделать в конфигурации свой HTTP-сервис.

Откуда берётся ошибка: как $filter становится условием запроса

Стандартный интерфейс OData в 1С 8.3 не читает таблицы СУБД напрямую. Каждый GET-запрос к standard.odata платформа переводит в запрос на языке запросов 1С. Параметр $filter превращается в секцию ГДЕ, $orderby — в УПОРЯДОЧИТЬ ПО, $select — в список полей.

Отсюда простое правило: в $filter можно только то, что разрешено в условии обычного запроса 1С. Если в Конфигураторе такой запрос с тем же условием упадёт — упадёт и OData-запрос. Текст ошибки придёт в теле ответа, в элементе odata.error, с кодом HTTP 400 или 500.

Типичный адрес запроса, на котором ловят ошибку:

http://srv/base/odata/standard.odata/Catalog_Контрагенты?$format=json&$filter=Комментарий eq 'Поставщик'

Здесь Комментарий у справочника — строка неограниченной длины. В языке запросов 1С такие поля нельзя сравнивать на равенство, группировать по ним и упорядочивать. Платформа честно сообщает, что операция в условии не разрешена.

Что обычно стоит за ошибкой:

  • 📝 сравнение строки неограниченной длины через eq, ne, gt, lt;
  • 📦 условие по полю типа ХранилищеЗначения — по нему фильтровать нельзя вообще;
  • 🔀 составное поле (например, Субконто1 или Регистратор) сравнивается с GUID без указания типа;
  • 🧮 функция в фильтре, которую платформа не умеет перевести для данного типа поля;
  • 🔐 ограничение доступа на уровне записей, которое накладывается на тот же запрос;
  • 🧷 ошибка литерала: дата без datetime, GUID без guid, неэкранированный апостроф.

Быстрая проверка: уберите из адреса $filter целиком. Если ответ приходит, проблема точно в условии, а не в публикации и не в правах на объект.

Права ролей: что проверить у OData-пользователя

Пользователь, под которым ходит внешняя система, — обычный пользователь информационной базы. Ему нужны права на чтение каждого объекта, который участвует в запросе: и самой таблицы, и объектов, на которые ссылаются поля из фильтра.

Порядок проверки:

  1. Откройте карточку пользователя и посмотрите его роли или профиль групп доступа (в конфигурациях на БСП — через группы доступа).
  2. Убедитесь, что у пользователя есть право на внешнее соединение или доступ через стандартный интерфейс OData. В типовых конфигурациях для этого есть отдельная роль удалённого доступа к интерфейсу OData.
  3. Проверьте право «Чтение» на основной объект и на объекты, к которым вы обращаетесь через поля фильтра.
  4. Проверьте, включены ли ограничения доступа на уровне записей (RLS) для этого профиля.

Почему RLS важно именно для этой ошибки. Ограничение доступа платформа добавляет в тот же запрос, что и ваш $filter. Если ограничение построено на поле, к которому у пользователя нет доступа, или условие фильтра конфликтует с шаблоном ограничения, ошибка может выглядеть как отказ в условии, хотя исходный фильтр корректен.

Не выдавайте OData-пользователю полные права «чтобы заработало». Через standard.odata он сможет выгрузить всё, что опубликовано. Права расширяйте точечно и проверяйте под этим пользователем, а не под администратором.

Отдельно проверьте состав опубликованных объектов. Объект, которого нет в составе стандартного интерфейса, не отдаётся вовсе. Состав задают методом УстановитьСоставСтандартногоИнтерфейсаOData() либо через обработку настройки стандартного интерфейса OData, которая есть в типовых конфигурациях на БСП. В БП 3.0 её ищите в разделе Администрирование → Синхронизация данных.

Фильтр по сложным типам: строки, хранилища, составные поля

Это центральный раздел. Большинство случаев ошибки решается здесь.

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

Длина реквизита задаётся в Конфигураторе. Значение 0 означает неограниченную строку. Такие поля напрямую сравнивать нельзя.

Варианты обхода:

СпособПример фильтраКогда подходит
Обрезать строкуsubstring(Комментарий,1,100) eq 'Поставщик'нужно точное совпадение по началу текста
Поиск вхожденияsubstringof('Поставщик',Комментарий)ищете слово внутри текста
Фильтр по другому полюDescription eq 'ООО Ромашка'есть ограниченный реквизит с той же информацией
Фильтр на клиентебез $filter, отбор в своём кодеданных немного

Функции substring и substringof платформа переводит в подстроку и ПОДОБНО. Поддерживается ли конкретная комбинация для поля неограниченной длины, зависит от версии платформы. Поэтому сначала проверьте вариант на тестовой базе.

Если вы владелец конфигурации и фильтр по этому полю нужен постоянно, проще ограничить длину реквизита (например, 150 символов). Но это изменение структуры: длинные значения при обновлении будут обрезаны. Перед изменением длины реквизита сделайте резервную копию базы.

Поля типа ХранилищеЗначения

По ХранилищеЗначения фильтровать нельзя ни в запросе, ни через OData. Это двоичные данные. Уберите такое поле из $filter и, если оно не нужно, из $select: это заметно ускорит ответ.

Составные типы

У поля составного типа стандартный интерфейс отдаёт два свойства: само значение и его тип с суффиксом _Type. Например, для Регистратор — Recorder и Recorder_Type.

Сравнение такого поля с голым GUID платформа не может однозначно перевести в условие: непонятно, ссылку какой таблицы вы имеете в виду. Есть два рабочих способа:

$filter=Recorder eq cast(guid'1a2b3c4d-0000-0000-0000-000000000001', 'Document_РеализацияТоваровУслуг')
$filter=Recorder_Type eq 'StandardODATA.Document_РеализацияТоваровУслуг'

Первый отбирает строки конкретного документа. Второй — все строки, где регистратор этого вида. Их можно соединять через and.

Литералы дат, GUID и строк

ТипПравильноЧастая ошибка
СсылкаКонтрагент_Key eq guid'…'GUID в кавычках без guid
ДатаDate ge datetime'2025-01-01T00:00:00'дата без datetime или без времени
БулевоDeletionMark eq false'false' в кавычках
Строка с апострофом'ООО ''Ромашка'''одиночный апостроф внутри строки

Пустую ссылку сравнивайте с нулевым GUID: guid'00000000-0000-0000-0000-000000000000'.

Версии протокола и платформы

Стандартный интерфейс 1С реализует протокол OData версии 3.0. Клиентские библиотеки, рассчитанные на OData 4, строят фильтр по своим правилам, и часть выражений платформа отвергает.

Где обычно сыплется клиент на OData 4:

  • 🔤 функция contains() вместо substringof();
  • 📅 даты без префикса datetime'…' в формате OData 4;
  • 🆔 GUID без префикса guid'…';
  • 🧩 лямбда-операторы any/all по табличным частям.

Если используете готовую библиотеку, переключите её в режим совместимости с OData v3. Не получается — соберите строку $filter вручную.

Вторая сторона — версия платформы на веб-сервере и на сервере 1С. Набор поддерживаемых функций в $filter и обработка составных типов дорабатывались от версии к версии. Если тот же запрос работает на одной базе и падает на другой, сравните номера платформы: на сервере с номером ниже проблема встречается чаще. Список изменений смотрите в описании версии платформы.

Обновление платформы на рабочем сервере обрывает сеансы пользователей и требует перезапуска службы агента. Планируйте его на нерабочее время, а запрос сначала проверьте на тестовом сервере с новой версией.

Трассировка: как увидеть, во что превратился ваш $filter

Когда причина неочевидна, нужно увидеть сам запрос. Двигайтесь снизу вверх.

1. Ответ сервера

Выполните запрос в браузере или через curl с параметром $format=json. В теле ответа будет odata.error с текстом сообщения. Скопируйте его полностью: в нём часто указано поле или фрагмент условия.

curl -u "odata_user:пароль" \
  "http://srv/base/odata/standard.odata/Catalog_Контрагенты?\$format=json&\$filter=Description%20eq%20'Ромашка'"

2. Лог веб-сервера

В журналах IIS или Apache проверьте, какой URL дошёл до сервера. Частая находка: клиент закодировал пробелы как + или повредил кириллицу. Фильтр приходит уже испорченным, и платформа разбирает не то, что вы писали.

3. Технологический журнал

Настройте logcfg.xml на сервере 1С с событиями EXCP (исключения) и SDBL (запросы на языке 1С). В событии SDBL вы увидите запрос, построенный из вашего $filter, а в EXCP — момент ошибки.

<config xmlns="http://v8.1c.ru/v8/tech-log">
  <log location="C:\techlog" history="2">
    <event><eq property="name" value="EXCP"/></event>
    <event><eq property="name" value="SDBL"/></event>
    <property name="all"/>
  </log>
</config>

Технологический журнал с событием SDBL на нагруженном сервере быстро пишет гигабайты. Включайте его на 10–15 минут, ограничьте history и удалите файл настройки сразу после диагностики.

4. Журнал регистрации

В журнале регистрации отберите события по пользователю OData. Там видны ошибки выполнения и отказы в доступе, если их перехватывает конфигурация.

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

Оптимизация запроса и альтернативы стандартному интерфейсу

Иногда фильтр формально корректен, но выбранный путь тупиковый. Вот что помогает.

Фильтровать по ключам, а не по представлениям

Контрагент_Key eq guid'…' работает быстрее и надёжнее, чем фильтр по наименованию через навигационное свойство. Сначала одним запросом получите Ref_Key нужного элемента, затем используйте его во втором.

Использовать параметры виртуальных таблиц

Для остатков и оборотов регистров у виртуальных таблиц есть параметр Condition. Он передаёт отбор внутрь виртуальной таблицы, как условие в её параметрах в обычном запросе:

AccumulationRegister_ТоварыНаСкладах/Balance(Period=datetime'2025-03-31T23:59:59', Condition='Склад_Key eq guid''…''')

Апострофы внутри Condition удваиваются. Отбор через $filter после виртуальной таблицы тоже допустим, но он работает уже по готовому результату и медленнее.

Уменьшить выборку

  • 🎯 $select — только нужные поля, без табличных частей и хранилищ;
  • 📄 $top и $skip — выгрузка порциями по 500–1000 записей;
  • 🗂️ $expand — только когда без связанных данных действительно не обойтись.

Когда переходить на HTTP-сервис

Стандартный интерфейс хорош для типовых выборок. Если нужен отбор по неограниченной строке, вложенный запрос, соединение нескольких таблиц или агрегаты, напишите HTTP-сервис в конфигурации или расширении. В нём вы сами строите запрос на языке 1С и можете, например, выбрать данные во временную таблицу и наложить условие уже на неё.

ПодходПлюсыМинусы
standard.odata + $filterбез программирования, сразу после публикацииограничения языка запросов, версия протокола 3.0
Фильтр на стороне клиентаобходит любые ограничения условиялишний трафик, медленно на больших таблицах
HTTP-сервис в расширениилюбой запрос, свой формат ответанужна разработка и сопровождение

Разбор ошибки по симптому

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

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

Можно ли отфильтровать по табличной части документа?
Напрямую через $filter основного документа — нет. Запросите табличную часть как отдельную сущность (Document_Имя_Товары) и фильтруйте по её полям и Ref_Key.
Почему ошибка появилась после обновления конфигурации?
Скорее всего, у реквизита изменили тип: строку сделали неограниченной или поле стало составным. Сверьте свойства реквизита до и после обновления.
Влияет ли $orderby на эту ошибку?
Да. По строке неограниченной длины нельзя и упорядочивать. Если фильтр исправлен, а ошибка осталась, уберите из $orderby такие поля.
Нужно ли перезапускать веб-сервер после изменения состава OData?
Нет. Состав стандартного интерфейса хранится в базе и применяется к новым запросам. Перезапуск нужен после перепубликации базы на веб-сервере.
Можно ли использовать регистр букв в фильтре по наименованию?
Сравнение строк в 1С обычно не зависит от регистра, но это определяется СУБД. Для надёжности используйте tolower(Description) eq 'ромашка'.