Доктрина: разбиение на страницы с левым соединением
Я хотел бы разбивать сложный запрос, по крайней мере, на два левых соединения, но пакет разбиения на страницы, который я использую (KnpPaginationBundle), не может сказать Doctrine, как подсчитать результат (который необходим для процесса разбиения на страницы) и сохраняйте это исключение.
Cannot count query which selects two FROM components, cannot make distinction
Вот пример запроса, построенного с помощью Doctrine QueryBuilder.
public function findGroupsByUser(User $user, $listFilter, $getQuery = false, $order = 'ASC')
{
$query = $this->createQueryBuilder('r')
->select('r as main,g')
->select('r as main,g, count(gg) as members')
->leftjoin('r.group', 'g')
->innerjoin('MyBundle:GroupMemberRel', 'gg', 'WITH', 'r.group = gg.group')
->addGroupBy('g.groupId')
->add('orderBy', 'g.name ' . $order);
if ($getQuery == true) {
return $query;
}
return $query->getQuery()->getResult();
}
Затем я передаю этот запрос службе knp_paginator, а затем у меня есть исключение
$groupQuery = $this->em->getRepository('MyBundle:GroupMemberRel')->findGroupsByUser($user, $listFilter, true);
$paginator = $this->container->get('knp_paginator');
/* @var $groups Knp\Component\Pager\Pagination\PaginationInterface */
$groups = $paginator->paginate(
$groupQuery, $this->container->get('request')->query->get('page', 1), 10 /* limit per page */
);
Любая идея о том, как разбивать страницы на сложный запрос, я вполне уверен, что этот прецедент распространен, не хочу гидратировать мой результат после разбивки на страницы.
Ответы
Ответ 1
Для тех, кто ищет ответ об этом, есть хорошее решение:
https://github.com/KnpLabs/KnpPaginatorBundle/blob/master/Resources/doc/manual_counting.md
$paginator = new Paginator;
$count = $entityManager
->createQuery('SELECT COUNT(c) FROM Entity\CompositeKey c')
->getSingleScalarResult();
$query = $entityManager
->createQuery('SELECT c FROM Entity\CompositeKey c')
->setHint('knp_paginator.count', $count);
$pagination = $paginator->paginate($query, 1, 10, array('distinct' => false));
В основном, что вы делаете, вы создаете свой собственный запрос "count" и инструктируете papinator knp использовать это. Не забудьте добавить
Ответ 2
Трудно понять сущности в исходном вопросе, но я столкнулся с этой проблемой и действительно разрешился.
Предположим, что у вас есть такая сущность:
class User
{
// ...
/**
* @ORM\OneToMany(targetEntity="Registration", mappedBy="user")
*/
private $registrations;
// ...
}
Важно то, что он имеет отношения "один ко многим" с другим объектом, и вы хотите присоединиться к нему с помощью QueryBuilder по любой причине (например, вы можете добавить HAVING
в ваш запрос, чтобы выбрать только те объекты с одним или несколькими из этих других объектов).
Оригинальный код может выглядеть так:
$qb = $this->createQueryBuilder();
$query = $qb
->select('u')
->add('from', '\YourBundle\ORM\Model\User u')
->leftJoin('\YourBundle\ORM\Model\Registration', 'r', 'WITH', 'u.id = r.user')
->groupBy('u.id')
->having($qb->expr()->gte($qb->expr()->count('u.registrations'), '1')
->getQuery();
Это вызовет исключение: Cannot count query which selects two FROM components, cannot make distinction
Чтобы исправить это, перепишите запрос так, чтобы QueryBuilder имел только один компонент "от" - точно так же, как указывает исключение, - перемещая соединение inline. Например:
$qb = $this->createQueryBuilder();
$query = $qb
->select('u')
->add('from', '\YourBundle\ORM\Model\User u LEFT JOIN u.registrations r')
->groupBy('u.id')
->having($qb->expr()->gte($qb->expr()->count('r'), '1')
->getQuery();
Должно отлично работать с классом класса "Учение", без каких-либо других пакетов. Также обратите внимание на сокращенный синтаксис соединения; с Доктриной, которую вы не видели, чтобы указать объединенную сущность явно (хотя вы можете), поскольку отображение уже знает, что это такое.
Надеюсь, это поможет кому-то, там не так много по этому вопросу. Вы можете узнать больше о том, как внутренне справляются с этим, просмотрев комментарий @halfer здесь.
Ответ 3
Обновление доктрины /dbal до 2.5 исправлено для меня.
Ответ 4
Если вы не работаете с обычными сопоставленными объектами способом, указанным в ответах выше, объединения будут создавать дополнительные компоненты в результирующем наборе. Это означает, что набор не может считаться, поскольку он не является скалярным результатом.
Таким образом, ключ сохраняет ваши результирующие наборы в один компонент, который позволит производить и подсчитывать скалярный результат.
Вы можете преобразовать регулярное поле в сопоставление сущности во время выполнения. Это позволяет вам писать запросы с возвратом только одного компонента, даже если вы произвольно присоединяетесь к другим объектам. "Другие сущности" становятся дочерними элементами основной сущности, а не дополнительной корневой сущностью или дополнительным компонентом результата.
В приведенном ниже примере показано взаимно однозначное отображение. Я не пробовал более сложные сопоставления, но мой успех с этим предполагает, что это должно быть возможно. У объекта регистрации есть поле, называемое пользователем, которое содержит идентификатор пользователя, но его не отображенный объект просто простое поле.
(Это было изменено из рабочего примера, но не проверено как есть - поэтому рассматривайте как псевдокод)
$queryBuilder // The query builder already has the other entities added.
->innerJoin('r.user', 'user')
;
$mapping['fieldName'] = 'user';
$mapping['targetEntity'] = '\YourBundle\ORM\Model\User';
$mapping['sourceEntity'] = '\YourBundle\ORM\Model\Registration';
$mapping['sourceToTargetKeyColumns'] = array('user' => 'id');
$mapping['targetToSourceKeyColumns'] = array('id' => 'user');
$mapping['fetch'] = 2;
$mapping['joinColumns'] = array(
array(
'name' => 'user',
'unique' => false,
'nullable' => false,
'onDelete' => null,
'columnDefinition' => null,
'referencedColumnName' => 'id',
)
);
$mapping['mappedBy'] = 'user';
$mapping['inversedBy'] = null; // User doesn't have to link to registrations
$mapping['orphanRemoval'] = false;
$mapping['isOwningSide'] = true;
$mapping['type'] = ClassMetadataInfo::MANY_TO_ONE;
$vm = $this->em->getClassMetadata('YourBundle:Registration');
$vm->associationMappings["user"] = $mapping;
Примечание. Я использовал PagerFanta, а не KNP, но проблема заключается в Doctrine, поэтому я ожидаю, что это сработает где угодно.