4

Working with Symfony 2 and Doctrine, I'm searching for a way to select every rows having the max value in a specific column.

Right now, I'm doing it in two queries:

  • One to get the max value of the column in the table
  • Then I select rows having this value.

I'm sure this can be done with one query.

Searching, I have found this answer in a thread, that seems to be what I am searching for, but in SQL.

So according to the answer's first solution, the query I'm trying to build would be something like that:

select yt.id, yt.rev, yt.contents
from YourTable yt
inner join(
    select id, max(rev) rev
    from YourTable
    group by id
) ss on yt.id = ss.id and yt.rev = ss.rev

Does anybody know how to make it in Doctrine DQL?

For now, here is the code for my tests (not working):

$qb2= $this->createQueryBuilder('ms')
            ->select('ms, MAX(m.periodeComptable) maxPeriode')
            ->where('ms.affaire = :affaire')
            ->setParameter('affaire', $affaire);

$qb = $this->createQueryBuilder('m')
            ->select('m')
            //->where('m.periodeComptable = maxPeriode')

            // This is what I thought was the most logical way of doing it:
            ->innerJoin('GAAffairesBundle:MontantMarche mm, MAX(mm.periodeComptable) maxPeriode', 'mm', 'WITH', 'm.periodeComptable = mm.maxPeriode')

            // This is a version trying with another query ($qb2) as subquery, which would be the better way of doing it for me,
            // as I am already using this subquery elsewhere
            //->innerJoin($qb2->getDQL(), 'sub', 'WITH', 'm.periodeComptable = sub.maxPeriode')

            // Another weird try mixing DQL and SQL logic :/
            //->innerJoin('SELECT MontantMarche mm, MAX(mm.periodeComptable) maxPeriode ON m.periodeComptable = mm.maxPeriode', 'sub')

            //->groupBy('m')
            ->andWhere('m.affaire = :affaire')
            ->setParameter('affaire', $affaire);

    return $qb->getQuery()->getResult();

The Entity is GAAffairesBundle:MontantMarche, so this code is in a method of the corresponding repository.

More generally, I'm learning about how to handle sub-queries (SQL & DQL) and DQL syntax for advanced queries.

Thx!

Community
  • 1
  • 1
Cedo
  • 639
  • 7
  • 17
  • Possible duplicate of [`Doctrine Query Language get Max/Latest Row Per Group`](https://stackoverflow.com/a/46233080/853360) – M Khalid Junaid Dec 05 '17 at 05:02
  • This is not a duplicate of the other thread. First because mine was posted nearly 3 years ago (which would make the other one a possible duplicate), and because in my case, I'm searching for a selection of joined elements (having a field's max value), while on this other thread, the author seems to be searching directly for a selection of elements having a field's max value for each group of elements with the same name. – Cedo Dec 06 '17 at 10:58

3 Answers3

5

After some hours of headache and googling and stackOverflow readings... I finally found out how to make it.

Here is my final DQL queryBuilder code:

    $qb = $this->createQueryBuilder('a');
    $qb2= $this->createQueryBuilder('mss')
            ->select('MAX(mss.periodeComptable) maxPeriode')
            ->where('mss.affaire = a')
            ;

    $qb ->innerJoin('GAAffairesBundle:MontantMarche', 'm', 'WITH', $qb->expr()->eq( 'm.periodeComptable', '('.$qb2->getDQL().')' ))
        ->where('a = :affaire')
        ->setParameter('affaire', $affaire)
        ;

    return $qb->getQuery()->getResult();
Cedo
  • 639
  • 7
  • 17
0

For me when i trying to make a subquery i make:

->andWhere($qb->expr()->eq('affaire', $qb2->getDql()));
Charlie Lucas
  • 280
  • 1
  • 10
0

To achieve this using pure DQL and without use of any aggregate function you can write doctrine query as

SELECT a
FROM GAAffairesBundle:MontantMarche a
    LEFT JOIN GAAffairesBundle:MontantMarche b
    WITH a.affaire = b.affaire 
    AND a.periodeComptable < b.periodeComptable
WHERE b.affaire IS NULL
ORDER BY a.periodeComptable DESC

The above will return you max record per group (per affaire)

Expalnation

The equivalent SQL for above DQL will be like

SELECT a.*
FROM MontantMarche a
LEFT JOIN MontantMarche b 
    ON a.affaire = b.affaire 
    AND a.periodeComptable < b.periodeComptable
WHERE b.affaire IS NULL
ORDER BY a.periodeComptable DESC

Here i assume there can be multiple entries in table e.g(MontantMarche) for each affaire, so here i am trying to do a self join on affaire and another tweak in join is i am trying to join only rows from right table(b) where a's periodeComptable < b's periodeComptable, So the row for left table (a) with highest periodeComptable will have a null row from right table(b) thus to pick the highest row per affaire the WHERE right table row IS NULL necessary.

Similarly using your posted sample query with inner join can be written as

select yt.id, yt.rev, yt.contents
from YourTable yt
left join YourTable ss on yt.id = ss.id and yt.rev < ss.rev
where ss.rev is null

Hope it makes sense

M Khalid Junaid
  • 63,861
  • 10
  • 90
  • 118
  • Hello Khalid, thank you for this answer and sorry for the delay of mine. This is interesting. Kind of weird though. Would you explain to me the logic here? (and how did you find that one??) I'm curious about what would be the value of b.periodeComptable where b.affaire IS NULL.. How can this return anything when we're asking for elements where a.affaire = b.affaire AND b.affaire IS NULL?? As there is no element (montantMarche) having affaire NULL. Also, I think your missing the main point here: I'm searching for all 'top' elements (montantMarches) from a particular Affaire.. – Cedo Dec 04 '17 at 10:56
  • You don't seem to bind any affaire.id here in the query. – Cedo Dec 04 '17 at 10:56
  • @Cedo i have updated my answer to clarify what DQL and its equivalent SQL s doing, For better understanding you can provide your table structure with sample data so i can add details using your sample data set – M Khalid Junaid Dec 04 '17 at 11:11