Home » Php » php – doctrine dql where clause interpretation with associated entity

php – doctrine dql where clause interpretation with associated entity

Posted by: admin July 12, 2020 Leave a comment

Questions:

Given this entity

class SystemRecord
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer", name="ID")
     * @ORM\GeneratedValue
     * @var int
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Application\Entity\User")
     * @ORM\JoinColumn(name="USER_SERIAL", referencedColumnName="SERIAL", nullable=false)
     * @var User
     */
    private $user;

    /**
     * @ORM\Column(type="utcdatetime", name="DATE_DATA_WAS_FETCHED", nullable=false)
     * @var DateTimeInterface
     */
    private $dateDataWasFetched;
}

…and this dql

$dql = "
    select r
      from Application\Entity\SystemRecord r
      join Application\Entity\User u
     where r.dateDataWasFetched = (
         select max(r2.dateDataWasFetched)
           from Application\Entity\SystemRecord r2
     )
       and u.serial = :serial
";

$query = $this->getEntityManager()->createQuery($dql);
$query->setParameter('serial', $user->getSerial());
$sql = $query->getSql();

… I’m hoping to get “the SystemRecords for the user with the specified serial, but only those with the most recent date out of any SystemRecord”. In other words, procedurally, “find the most recent date of any SystemRecord for any user. Then find records for the specified user which occurred on that date.”

If I were writing sql, I would write

select *
  from SYSTEM_RECORDS r
  join USER u
    on r.USER_SERIAL = u.SERIAL
 where DATE_DATA_WAS_FETCHED = (select max(DATE_DATA_WAS_FETCHED) from SYSTEM_RECORDS)
   and u.SERIAL = ?

But, doctrine is giving me the following sql

SELECT ...fields from s0_ ...
  FROM SYSTEM_RECORDS s0_ 
 INNER 
  JOIN 
  USER u1_
   ON (s0_.DATE_DATA_WAS_FETCHED = (SELECT max(s2_.DATE_DATA_WAS_FETCHED) AS dctrn__1
                                     FROM SYSTEM_RECORDS s2_) AND u1_.SERIAL = ?)

Which isn’t what I want. That gives me “SystemRecords for all users whose SystemRecords have the same date as the most recent SystemRecords for the user with the specified serial”.

How do I formulate my query using dql?


How to&Answers:

If I understand you correctly you need to use a sub query like you did but I think you are missing the in expression. With QueryBuilder you would built the query to get your result like this (I always write my queries with QueryBuilder):

$qb->select(r)
   ->from('SystemRecord', 'r')
   ->join('r.user', 'u')
   ->where(
       $qb->expr()->in(
           'r.dateDataWasFetched',
           "SELECT max(r2.dateDataWasFetched) FROM Application\Entity\SystemRecord r2"
       )
   )
   ->andWhere('u.serial' = :user_serial)
   ->setParameter('user_serial', $user->getSerial())
   ->getQuery()
   ->getResult();

This answer is based on this answer to similar question on stackoverflow.

EDIT:

If you really want the DQL then you can easily get it from your QueryBuilder instance after building the query using the getDQL method like this:

$dql = $qb->getQuery()->getDQL();

Answer:

I was able to solve/avoid my problem by avoiding a join

$dql = "
    select r
      from Application\Entity\SystemRecord r
     where r.dateDataWasFetched = (
         select max(r2.dateDataWasFetched)
           from Application\Entity\SystemRecord r2
     )
       and r.user = :user
";

$query = $this->getEntityManager()->createQuery($dql);
$query->setParameter('user', $user);

Resulting sql(correct)

SELECT ...fields from s0_ ...
FROM SYSTEM_RECORDS s0_
WHERE s0_.DATE_DATA_WAS_FETCHED = (SELECT max(s1_.DATE_DATA_WAS_FETCHED) AS dctrn__1
                                   FROM SYSTEM_RECORDS s1_) AND s0_.USER_SERIAL = ?

The notable difference is that instead of specifying the id for the associated entity(via u.serial = :serial, I’m now specifying the entity itself(via r.user = :user). This allows me to omit the join, too. btw – The serial field is tagged with @ORM\Id in my User entity.

However, this is just avoiding the problem. I’m still perplexed by how doctrine interprets the query when a join is present.

Edit – real solution found

Thanks to Wilt, after using the query builder and then using the getDQL() method I found the missing detail. The working dql is

select r
  from Application\Entity\SystemRecord r
  join r.user u
 where r.dateDataWasFetched = (
     select max(r2.dateDataWasFetched)
       from Application\Entity\SystemRecord r2
 )
   and u.serial = :serial

Note that the difference between the DQL in my original question, and this working solution is join Application\\Entity\\User u vs join r.user u, respectively.