Почему $filter в стандартном интерфейсе OData 1С отклоняется на этапе условия и как переписать запрос
Содержание
Ошибка 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-пользователя
Пользователь, под которым ходит внешняя система, — обычный пользователь информационной базы. Ему нужны права на чтение каждого объекта, который участвует в запросе: и самой таблицы, и объектов, на которые ссылаются поля из фильтра.
Порядок проверки:
- Откройте карточку пользователя и посмотрите его роли или профиль групп доступа (в конфигурациях на БСП — через группы доступа).
- Убедитесь, что у пользователя есть право на внешнее соединение или доступ через стандартный интерфейс OData. В типовых конфигурациях для этого есть отдельная роль удалённого доступа к интерфейсу OData.
- Проверьте право «Чтение» на основной объект и на объекты, к которым вы обращаетесь через поля фильтра.
- Проверьте, включены ли ограничения доступа на уровне записей (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?
Можно ли использовать регистр букв в фильтре по наименованию?
tolower(Description) eq 'ромашка'.