177  /  385
Справочник

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

Просмотров: 48378
Дата последнего изменения: 08.11.2023
Роберт Басыров
Сложность урока:
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 позволяет выполнить отдельный запрос на каждое отношение, чтобы не возникало декартова произведения всех записей.

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

Сортировка применяется при первичном выборе primary и в дальнейшем приводится в соответствии с этими primary. Что касается объектов отношений внутри объектов верхнего уровня, то там сортировка как правило не настолько важна, как при работе с массивами.


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

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