2012-10-08 23 views
15

Me gustaría paginar una solicitud compleja con al menos 2 uniones izquierdas, pero el paquete de paginación que estoy usando (KnpPaginationBundle) no puede decirle a Doctrine cómo contar el resultado (which is needed for the pagination process) y sigue teniendo esta excepción.Doctrina: Paginación con combinaciones de la izquierda

Cannot count query which selects two FROM components, cannot make distinction

Aquí está una muestra de construcción con la Doctrina 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(); 
} 

Entonces daré esta solicitud al servicio knp_paginator, y luego tengo la excepción

$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 */ 
    ); 

Cualquier idea sobre cómo paginar a través de una solicitud compleja, estoy bastante seguro de que este uso -case es común, no quiero hidratar mi resultado después de la paginación.

+0

Los adaptadores de ORM de la doctrina de Pagerfanta deben ser capaces de manejar las consultas de weirds. https://github.com/whiteoctober/Pagerfanta – AdrienBrault

+0

Esta consulta no es tan extraña, estoy seguro de que hay una manera de hacerlo simplemente con knp paginator, tal vez debería tener un aspecto más profundo para el buscador de nudillos –

+0

Estoy enfrentando este problema ... ¿alguna solución? – tttony

Respuesta

20

Para aquellos que buscan una respuesta acerca de esto, no es una buena solución en: 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)); 

Básicamente, lo que está haciendo, es que va a crear su propia consulta 'contar' e instruye paginador knp para usar esto No olvide agregar

+5

Chicos, fíjate en ** array ('distinct' => false) ** ... me llevó años – Eugene

14

Es difícil de entender las entidades en la pregunta original, pero me encontré con este mismo problema y de hecho fue solucionable.

Suponga que tiene una entidad como esta:

class User 
    { 
    // ... 
    /** 
    * @ORM\OneToMany(targetEntity="Registration", mappedBy="user") 
    */ 
    private $registrations; 
    // ... 
    } 

Lo importante es que tiene una relación de uno a muchos con otra entidad, y que desea ser capaz de unirse a través de la QueryBuilder por cualquier razón (por ejemplo, puede agregar una cláusula HAVING a su consulta para seleccionar solo aquellas entidades con una o más de estas otras entidades).

Su código original podría ser:

$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(); 

Esto lanzará la excepción: Cannot count query which selects two FROM components, cannot make distinction

Para solucionar este problema, reescribir la consulta para que el QueryBuilder sólo tiene un "de" componente - exactamente como lo indica la excepción, moviendo la unión en línea. Como tal:

$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(); 

Debería funcionar perfectamente con la clase Doctrine's Paginator, sin necesidad de otros paquetes. También tenga en cuenta la sintaxis abreviada de la unión; con Doctrine no se ve que se especifique la entidad unida explícitamente (aunque se puede) ya que la asignación ya sabe de qué se trata.

Espero que esto ayude a alguien, no hay mucho sobre este tema. Puede leer más sobre cómo los internos manejan esto mirando @halfer's comment here.

+3

¡Esta debería ser la respuesta aceptada! – newbie

+0

Just FYI @futureal Intenté su sugerencia pero no funciona. https://gist.github.com/TrkiSF2/16d64a8aec846f4053a5 No se puede investigar más necesidad de cambiar el enfoque :( – EnchanterIO

+0

Parece que el problema que tienes no está relacionado con la unión, sino con el uso de 'HAVING', que no es compatible con el paginador con este tipo de uso. Desde el fragmento de código proporcionado, no está claro cuál es la intención, pero tal vez está tratando de obtener solo usuarios con 1 o más inicios de sesión. De ser así, simplemente use '-> donde ('lh.user IS NOT NULL') 'en lugar del' HAVING' y debería funcionar. – futureal

0

Si no está trabajando con entidades mapeadas de la forma en que se proporcionan en las respuestas anteriores, las uniones crearán componentes adicionales en el conjunto de resultados. Esto significa que no se puede contar el conjunto, ya que no es un resultado escalar.

Por lo tanto, la clave es mantener sus conjuntos de resultados en un componente que permita producir y contar un resultado escalar.

Puede convertir un campo regular a una asignación de entidades en tiempo de ejecución. Esto le permite escribir consultas con solo un componente devuelto, incluso si se une a otras entidades al azar. Las "otras entidades" se convierten en hijos de la entidad principal en lugar de una entidad raíz adicional o componente adicional del resultado.

El siguiente ejemplo muestra una asignación de uno a uno. No he intentado mapeos más complejos pero mi éxito con esto sugiere que debería ser posible. La entidad de registro tiene un campo llamado usuario que contiene un ID de usuario, pero no es una entidad mapeada simplemente un campo simple.

(Esto se ha modificado a partir de un ejemplo de trabajo, pero no probado como es - por lo que tratar como pseudo-código)

$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; 

Nota: yo estaba usando PagerFanta no KNP - pero el problema radica en Doctrina así que espero que esto funcione en cualquier lugar.

2

Al actualizar doctrine/dbal to 2.5 solucioné esto por mí.