Перейти к содержимому

Сложный фильтр выборки - ИЛИ, вложенные условия, подзапросы

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

Что нужно знать заранее

Обычный фильтр - это набор условий, соединённых логикой И. Все перечисленные условия должны выполниться разом, и никакого другого поведения по умолчанию не предусмотрено.

Логика ИЛИ задаётся отдельной группой внутри фильтра. Группа - это вложенный массив с признаком логики и своими условиями, и таких групп в фильтре может быть несколько.

Оператор пишется префиксом перед именем поля. Больше, меньше, неравно, подстрока - всё это символы перед названием поля, а не отдельные ключи массива условий.

Шаги

  1. Разложить требование заказчика на условия и понять, где нужна логика ИЛИ.
  2. Собрать группы условий и вложить их друг в друга по смыслу задачи.
  3. Проверить готовый текст запроса до того, как удивляться результату выборки.
  4. Заменить фильтр подзапросом там, где условие живёт в другой таблице.
  5. Убедиться, что итоговый запрос попадает в индекс, а не перебирает таблицу.

Решение

Собираем условие ИЛИ в ORM:

$rows = \Bitrix\Iblock\Elements\ElementCatalogTable::getList(['filter' => [
'=IBLOCK_ID' => $iblockId,
['LOGIC' => 'OR', ['=CODE' => 'hit'], ['>=SORT' => 500]], // одно из двух
], 'select' => ['ID', 'NAME']])->fetchAll();
// условия верхнего уровня по-прежнему соединяются логикой И

Группа с логикой ИЛИ живёт рядом с обычными условиями. Всё, что перечислено вне группы, остаётся обязательным, и это позволяет собирать условия вида «активный и при этом хит или дорогой».

Вкладываем группы друг в друга:

'filter' => ['=ACTIVE' => 'Y', ['LOGIC' => 'OR',
['=PROPERTY_BRAND_VALUE' => 'Alpha', '>=CATALOG_PRICE_1' => 1000],
['LOGIC' => 'AND', ['=PROPERTY_BRAND_VALUE' => 'Beta'], ['<=CATALOG_PRICE_1' => 500]],
]],
// внутри группы ИЛИ лежат две группы условий, каждая со своей логикой

Вложенность ограничена только читаемостью кода. Три уровня - это уже повод достать бумагу и записать условие словами, прежде чем переводить его в массив.

Фильтруем в старом ядре:

$rs = \CIBlockElement::GetList([], ['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y',
['LOGIC' => 'OR', ['PROPERTY_BRAND' => 'Alpha'], ['PROPERTY_BRAND' => 'Beta']],
], false, false, ['ID', 'NAME']);
// в старом ядре знак равенства в имени поля не ставится

Берём подзапрос вместо фильтра:

$sub = \Bitrix\Sale\Internals\OrderTable::query()
->setSelect(['USER_ID'])->where('STATUS_ID', 'F')->where('>DATE_INSERT', $from);
$users = \Bitrix\Main\UserTable::query()->setSelect(['ID', 'EMAIL'])
->whereIn('ID', $sub)->fetchAll(); // покупатели с завершёнными заказами

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

Смотрим, что получилось на самом деле:

$query = \Bitrix\Main\UserTable::query()->setSelect(['ID'])
->where('ACTIVE', 'Y')->whereIn('ID', $sub);
echo $query->getQuery(); // готовый текст запроса к базе данных

Текст запроса снимает половину вопросов о странной выборке. Он же показывает, превратилось ли условие в соединение таблиц, и стоит ли ждать от такого запроса скорости.

Типичные проблемы

Выборка возвращает больше записей, чем ожидалось.

Группа с логикой ИЛИ поглотила условия, которые должны были остаться обязательными. Обязательные условия выносят на верхний уровень фильтра, рядом с самой группой.

Условие по свойству молча не применяется.

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

Фильтр работает в одном ядре и не работает в другом.

В старом ядре знак равенства в имени поля не ставится, а в ORM без него условие означает другое. Правила разных ядер не переносят механически из одного кода в другой.

Запрос с подзапросом выполняется очень долго.

Подзапрос возвращает десятки тысяч строк и не попадает ни в один индекс. Поля подзапроса индексируют или собирают промежуточный результат порциями.

Фильтр по массиву идентификаторов обрывается.

Массив вырос до тысяч значений и запрос упёрся в предел длины. Такие условия переводят на подзапрос или обрабатывают порциями по несколько сотен.

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

Как задать условие ИЛИ?

Вложенной группой с признаком логики внутри массива фильтра. Условия вне группы остаются обязательными и соединяются логикой И.

Сколько уровней вложенности выдержит фильтр?

Технически много, практически - три уровня уже плохо читаются. Сложное условие проще собрать подзапросом или разбить на два запроса.

Почему неизвестное поле в фильтре не даёт ошибку?

Платформа отбрасывает ключи, которых нет в карте полей выборки. Поэтому опечатка в имени свойства выглядит как «фильтр не работает».

Когда брать подзапрос вместо фильтра?

Когда условие опирается на другую таблицу и список значений велик. Подзапрос выполняется в базе, а массив идентификаторов упирается в длину запроса.

Как посмотреть итоговый запрос?

Вызвать у объекта запроса метод получения текста и распечатать результат. Для старого ядра то же самое показывает трекер запросов.

Смежное

Первоисточник