Сложный фильтр выборки - ИЛИ, вложенные условия, подзапросы
Собираем условия выборки сложнее списка равенств: логика ИЛИ, вложенные группы, подзапросы и проверка того, что получилось на самом деле.
Что нужно знать заранее
Обычный фильтр - это набор условий, соединённых логикой И. Все перечисленные условия должны выполниться разом, и никакого другого поведения по умолчанию не предусмотрено.
Логика ИЛИ задаётся отдельной группой внутри фильтра. Группа - это вложенный массив с признаком логики и своими условиями, и таких групп в фильтре может быть несколько.
Оператор пишется префиксом перед именем поля. Больше, меньше, неравно, подстрока - всё это символы перед названием поля, а не отдельные ключи массива условий.
Шаги
- Разложить требование заказчика на условия и понять, где нужна логика ИЛИ.
- Собрать группы условий и вложить их друг в друга по смыслу задачи.
- Проверить готовый текст запроса до того, как удивляться результату выборки.
- Заменить фильтр подзапросом там, где условие живёт в другой таблице.
- Убедиться, что итоговый запрос попадает в индекс, а не перебирает таблицу.
Решение
Собираем условие ИЛИ в 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 без него условие означает другое. Правила разных ядер не переносят механически из одного кода в другой.
Запрос с подзапросом выполняется очень долго.
Подзапрос возвращает десятки тысяч строк и не попадает ни в один индекс. Поля подзапроса индексируют или собирают промежуточный результат порциями.
Фильтр по массиву идентификаторов обрывается.
Массив вырос до тысяч значений и запрос упёрся в предел длины. Такие условия переводят на подзапрос или обрабатывают порциями по несколько сотен.
Частые вопросы
Как задать условие ИЛИ?
Вложенной группой с признаком логики внутри массива фильтра. Условия вне группы остаются обязательными и соединяются логикой И.
Сколько уровней вложенности выдержит фильтр?
Технически много, практически - три уровня уже плохо читаются. Сложное условие проще собрать подзапросом или разбить на два запроса.
Почему неизвестное поле в фильтре не даёт ошибку?
Платформа отбрасывает ключи, которых нет в карте полей выборки. Поэтому опечатка в имени свойства выглядит как «фильтр не работает».
Когда брать подзапрос вместо фильтра?
Когда условие опирается на другую таблицу и список значений велик. Подзапрос выполняется в базе, а массив идентификаторов упирается в длину запроса.
Как посмотреть итоговый запрос?
Вызвать у объекта запроса метод получения текста и распечатать результат. Для старого ядра то же самое показывает трекер запросов.
Смежное
- Выборки из инфоблоков - оглавление подтемы
- Выборки из инфоблоков: GetList, ORM и разделы - основы выборки элементов
- Инфоблок через ORM: API_CODE, класс элементов, свойства - откуда берутся классы сущностей
- Свойства инфоблока: чтение, запись и фильтрация по значению - имена свойств в фильтре
- Элемент не появляется в списке: разбор причин - когда фильтр отсекает лишнее
- Медленный запрос к базе: поиск, план, индекс - что делать с медленным запросом
- Трекер SQL-запросов: счётчик запросов участка кода - как увидеть готовый запрос
- Инфоблоки - устройство инфоблоков целиком