176  /  381
Справочник

Выборки в отношениях 1:N и N:M

Просмотров: 34456
Дата последнего изменения: 02.03.2022
Роберт Басыров
Сложность урока:
4 уровень - сложно, требуется сосредоточиться, внимание деталям и точному следованию инструкции.
1
2
3
4
5
Недоступно в лицензиях:
Ограничений нет

При работе с отношениями 1:N и N:M в общем случае и с множественными свойствами инфоблока в частности можно столкнуться с двумя проблемами.

Логика LIMIT

Интуитивное ожидание логики работы LIMIT:

   $iblockEntity = IblockTable::compileEntity(…);
   
    $query = $iblockEntity->getDataClass()::query()
        ->addSelect('NAME')
        ->addSelect('MULTI_PROP_1')
    ->setLimit(5);

    $elements = $query->fetchCollection();

Ожидать в данном примере выборку 5 элементов будет ошибкой. Лимит указывается не на уровне объектов, а на уровне SQL запроса:

    SELECT ... FROM `b_iblock_element`
        LEFT JOIN `b_iblock_element_property` ...
    LIMIT 5

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

Выбор полей соотношений в одном запросе

Если выбирать несколько полей отношений в одном запросе, то результатом будет декартово произведение всех записей. Например:

    $iblockEntity = IblockTable::compileEntity(…);
    
    $query = $iblockEntity->getDataClass()::query()
        ->addSelect('NAME')
        ->addSelect('MULTI_PROP_1')
        ->addSelect('MULTI_PROP_2')
        ->addSelect('MULTI_PROP_3');
    
    $elements = $query->fetchCollection();

Выполнится запрос вида:

    SELECT ... FROM `b_iblock_element`
        LEFT JOIN `b_iblock_element_property` ... // 15 значений свойства
        LEFT JOIN `b_iblock_element_property` ... // 7 значений свойства
        LEFT JOIN `b_iblock_element_property` ... // 11 значений свойства

И если интуитивно кажется, что будет выбрано 15 + 7 + 11 = 33 строки, то фактически будет выбрано 15 * 7 * 11 = 1155 строк. Если свойств или значений в запросе еще больше, то счет может идти на миллионы результирующих записей, и как следствие - о нехватке памяти в приложении для получения всего результата.

Решение проблем

Для обхода этих проблем был добавлен класс Bitrix\Main\ORM\Query\QueryHelper с универсальным методом decompose:

    /**
    ** Декомпозиция запросов с 1:N и N:M отношениями
    ** 
    ** @param Query $query
    ** @param bool $fairLimit При установке этой опции сначала выбираются ID объектов, а следующим запросом остальные данные с фильтром по ID
    ** @param bool $separateRelations При установке этой опции каждое 1:N или N:M отношение выбирается отдельным запросом
    ** @return Collection
    **/
    public static function decompose(Query $query, $fairLimit = true, $separateRelations = true)

Параметр fairLimit приводит к двум запросам: сначала выбирается primary записей с заданным Limit / Offset в запросе, и после этого для всех primary одним запросом выбираются все отношения.

Дополнительный параметр separateRelations позволяет выполнить отдельный запрос на каждое отношение, чтобы не возникало декартова произведения всех записей.

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


Курсы разработаны в компании «1С-Битрикс»

Если вы нашли неточность в тексте, непонятное объяснение, пожалуйста, сообщите нам об этом в комментариях.
Развернуть комментарии