Search code examples
postgresqlsymfonyjoindoctrinedql

Add exclusion to Doctrine Query Builder in JOIN


I have built the following query with the Doctrine Query Builder in my Symfony application.

    $qb->select('c')
        ->from('AppBundle:Course', 'c')
        ->join('AppBundle:Log', 'a', Expr\Join::WITH, $qb->expr()->eq('c.id', 'a.course'))
        ->where($qb->expr()->in('a.type', ':type'))
        ->andWhere($qb->expr()->between('a.time', ':start', ':end'))
        ->andWhere($qb->expr()->eq('c.status', ':status'))
        ->setParameter(':type', ['opened'])
        ->setParameter(':standardScratchScore', [74])
        ->setParameter(':status', Course::OPENED)
        ->setParameter(':start', $dateFrom->format('Y-m-d H:i:s'))
        ->setParameter(':end', $dateTo->format('Y-m-d H:i:s'))
    ;

In my code I iterate over the Courses and then again query the Log table to check that an entry with a specific type doesn't exist for the Course. Is there a way I can incorporate the exclusion of log.type = 'log.sent-email' for this Course into this initial query, without using something like a sub-select?

Querying the same table again within the loop feels sub-optimal to me and NewRelic suggests it is hurting the performance of my application.


Solution

  • Well you can always join the table one more time for this specific need:

    $qb->select('c')
        ->from('AppBundle:Course', 'c')
        ->join('AppBundle:Log', 'a', Expr\Join::WITH, $qb->expr()->eq('c.id', 'a.course'))
        ->leftjoin(
            'AppBundle:Log', 
            'b', 
            Expr\Join::WITH, 
            $qb->expr()->andx(
                $qb->expr()->eq('c.id', 'b.course'),
                $qb->expr()->eq('b.type', 'log.sent-email')
            ))          
        ) // join log a second time, with the type condition
        ->where($qb->expr()->in('a.type', ':type'))
        ->andWhere($qb->expr()->between('a.time', ':start', ':end'))
        ->andWhere($qb->expr()->eq('c.status', ':status'))
        ->andWhere($qb->expr()->isNull('b.type')) // Only select records where no log record is found
        ->setParameter(':type', ['opened'])
        ->setParameter(':standardScratchScore', [74])
        ->setParameter(':status', Course::OPENED)
        ->setParameter(':start', $dateFrom->format('Y-m-d H:i:s'))
        ->setParameter(':end', $dateTo->format('Y-m-d H:i:s'))
    ;