Pregunta

I have a Product Entity which has a ManyToMany relationship with a Taxon Entity. I want to find all the products that belong to the intersection of all Taxons. For instance I want to find all products that belong to taxons with IDs 1 and 2.

Products
{1,2,3}

Taxons
{1,2,3,4,5}

ProductsToTaxons
{{p1,t1},{p1,t2}, {p2,t2}}

I want to retrieve the following set ONLY when querying products for taxons 1 and 2:

Product
{1} 
which is from {{p1,t1}, {p1,t2}}

Okay, So here is the DQL that i tried... but it doesn't work?

SELECT p FROM SRCProductBundle:Product p
JOIN p.taxons t 
WHERE t.id = 1 AND t.id = 2

(P.S. I would also do this with QueryBuilder with as well)

EDIT

To clarify, here is the SQL that I would like to translate into DQL/QueryBuilder.

select p.id 
from product p 
where exists (select product_id 
              from product_to_taxon 
              where taxon_id = 1 
              and product_id = p.id) 
and exists (select product_id 
            from product_to_taxon 
            where taxon_id = 4 
            and product_id = p.id);
¿Fue útil?

Solución 4

For lack of a clean way to do this with DQL, and after a considerable amount of research, I resorted to doing this in Native SQL. Doctrine allows Native SQL via the EntityManager with createNativeQuery().

So in short, I utilized this ability and constructed the SQL query included in my question as a string and then passed it to the createNativeQuery() function.

This does appear to have some drawbacks as it appears I will be unable to use the KnpPaginatorBundle with it... So I might end up just filtering the results in PHP rather than SQL, which I'm hesitant to do as I think there are performance drawbacks.

Otros consejos

You can use the MEMBER OF statement to achieve this although in my experience it hasn't produced very performant results with a ManyToMany relationship: In DQL:

SELECT p FROM SRCProductBundle:Product p
WHERE 1 MEMBER OF p.taxons OR 2 MEMBER OF p.taxons

Or with Query builder

$this->createQueryBuilder('p')
    ->where(':taxon_ids MEMBER OF p.taxons')
    ->setParameter('taxon_ids', $taxonIdsArray)
    ->getQuery()
    ->getResult();

This will create SQL similar to the example provided although in my experience it still had a join in the EXISTS subqueries. Perhaps future versions of Doctrine can address this.

I think you want something like this:

    $qb = $this
        ->createQueryBuilder('p')
        ->select('p.id')
    ;
    $qb
        ->leftJoin('p.taxons', 'taxon1', Join::WITH, 'taxon1.id = :taxonId1')
        ->setParameter('taxonId1', 1)
        ->andWhere($qb->expr()->isNotNull('taxon1'))
        ->leftJoin('p.taxons', 'taxon2', Join::WITH, 'taxon2.id = :taxonId2')
        ->setParameter('taxonId2', 2)
        ->andWhere($qb->expr()->isNotNull('taxon2'))
    ;

Which is equivalent to the SQL:

SELECT p.id
  FROM products p
  LEFT JOIN taxons t1 ON (p.id = t1.product_id AND t1.id = 1)
  LEFT JOIN taxons t2 ON (p.id = t2.product_id AND t2.id = 2)
 WHERE t1.id IS NOT NULL
   AND t2.id IS NOT NULL
;

Your DQL has wrong logic. You can't have a taxon with both id=1 and id=4. You could do it like this:

SELECT p FROM SRCProductBundle:Product p
JOIN p.taxons t 
WHERE t.id = 1 OR t.id = 4

But I would prefer this way:

SELECT p FROM SRCProductBundle:Product p
JOIN p.taxons t 
WHERE t.id IN (1, 4)

Using query builder that would look something like this, assuming you're in EntityRepository class:

$this->createQueryBuilder('p')
    ->join('p.taxons', 't')
    ->where('t.id IN :taxon_ids')
    ->setParameter('taxon_ids', $taxonIdsArray)
    ->getQuery()
    ->getResult();
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top