15

少なくとも 2 つの左結合で複雑なリクエストをページ分割したいのですが、私が使用しているページ分割バンドル (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 */
    );

複雑なリクエストでページネーションする方法についてのアイデアは、このユースケースが一般的であると確信しています。ページネーション後に結果を水和したくありません。

4

4 に答える 4

25

これについての答えを探している人には、 https ://github.com/KnpLabs/KnpPaginatorBundle/blob/master/Resources/doc/manual_counting.md に良い解決策があります。

$paginator = new Paginator;

// determine the count of the entire result set
$count = $entityManager
    ->createQuery('SELECT COUNT(c) FROM Entity\CompositeKey c')
    ->getSingleScalarResult();

// create a query to be used with the paginator, and specify a hint
$query = $entityManager
     ->createQuery('SELECT c FROM Entity\CompositeKey c')
     ->setHint(
        'knp_paginator.count', 
        $count
    );

// paginate, and set "disctinct" option to false
$pagination = $paginator->paginate(
    $query, 
    1, 
    10, 
    array(
        'distinct' => false,
    )
);

基本的に、あなたがしていることは、独自の「カウント」クエリを作成し、これを使用するように knp paginator に指示することです。

于 2013-07-26T14:09:00.363 に答える
14

元の質問のエンティティを理解するのは難しいですが、私はこの同じ問題に遭遇し、実際に解決できました。

次のようなエンティティがあるとします。

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

重要なことは、別のエンティティと 1 対多の関係があり、何らかの理由で QueryBuilder を介してそれに参加できるようにすることです (たとえば、HAVINGクエリに節を追加して選択することができます)。これらの他のエンティティを 1 つ以上持つエンティティのみ)。

元のコードは次のようになります。

  $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 が "from" コンポーネントを 1 つだけ持つようにします。そのようです:

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

他のバンドルは必要なく、Doctrine の Paginator クラスで問題なく動作するはずです。結合の簡略構文にも注意してください。Doctrine では、結合されたエンティティを明示的に指定することはありません (可能ではありますが)。これは、マッピングが既にそれが何であるかを知っているためです。

これが誰かに役立つことを願っています。この問題についてはあまりありません。内部がこれをどのように処理するかについては、@halfer のコメントを参照してください

于 2013-05-20T21:52:33.320 に答える
3

doctrine/dbalバージョンに更新すると2.5、これが修正されました。

于 2015-06-26T19:00:37.287 に答える
0

上記の回答で提供された方法で通常のマップされたエンティティを使用していない場合、結合により結果セットに追加のコンポーネントが作成されます。これは、スカラー結果ではないため、セットをカウントできないことを意味します。

したがって、重要なのは、結果セットを 1 つのコンポーネントに保持することです。これにより、スカラー結果を生成してカウントできるようになります。

実行時に通常のフィールドをエンティティ マッピングに変換できます。これにより、他のエンティティをランダムに結合している場合でも、返されるコンポーネントが 1 つだけのクエリを作成できます。「その他のエンティティ」は、追加のルート エンティティまたは結果の余分なコンポーネントとは対照的に、メイン エンティティの子になります。

以下の例は、1 対 1 のマッピングを示しています。より複雑なマッピングを試したことはありませんが、これで成功したことは、それが可能であることを示唆しています。登録エンティティには、ユーザー ID を含む user というフィールドがありますが、マップされたエンティティではなく、単なるフィールドです。

(これは実際の例から変更されていますが、そのままではテストされていないため、疑似コードとして扱います)

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

注: 私は KNP ではなく PagerFanta を使用していましたが、問題は Doctrine にあるため、これはどこでも機能すると思います。

于 2014-11-06T10:46:01.063 に答える