Doctrine DQL наибольший-n-на-группу

Вот 2 таблицы моего проекта Symfony2:

+-----------+     +----------------------------+ 
|  EVENT    |     |         PHOTO              |
+-----------+     +------+-----------+---------+
|    id     |     | id   | event_id  |  likes  |
+-----------+     +------+-----------+---------+
|     1     |     |  1   |    1      |   90    |
|     2     |     |  2   |    1      |   50    |
+-----------+     |  3   |    2      |   20    |
                  |  4   |    2      |   10    |
                  +------+-----------+---------+

Я хотел бы выбрать 2 события с наиболее понравившейся фотографией, которая выглядела бы так:

+------------+------------+---------+  
|  event_id  |  photo_id  |  likes  |  
+------------+------------+---------+
|     1      |     1      |    90   |
+------------+----------------------+
|     2      |     3      |    20   |
+------------+----------------------+

Решение SQL объясняется здесь (SQL Select only rows with Максимальное значение в столбце) и может быть:

SELECT p.event_id, p.likes, p.id
FROM photo p
INNER JOIN(
    SELECT event_id, max(likes) likes
    FROM photo
    GROUP BY event_id
) ss on p.event_id = ss.event_id and p.likes = ss.likes

Каким должен быть DQL-запрос для этого? Я пробовал многое, но всегда получаю ошибки.


person httpete    schedule 23.04.2013    source источник


Ответы (4)


Мне не удалось найти подходящий ответ с помощью DQL, но с помощью доктрины есть способ обрабатывать SQL-запрос, используя то, что они называют Native Query.

Мне удалось создать рабочий пример вашего образца SQL, используя модуль Native Query и Symfony 2.

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\ResultSetMapping;

class PhotoRepository extends EntityRepository
{
    public function findSomeByEvent()
    {
        $rsm = new ResultSetMapping;
        $rsm->addEntityResult('theNameOfYourBundle:Photo', 'p');
        $rsm->addFieldResult('p', 'id', 'id');
        $rsm->addFieldResult('p', 'likes', 'likes');
        $rsm->addFieldResult('p', 'event', 'event');

        $sql = 'SELECT p.event_id, p.likes, p.id
                FROM Photo p
                INNER JOIN(
                    SELECT event_id, max(likes) likes
                    FROM Photo
                    GROUP BY event_id
                ) ss on p.event_id = ss.event_id and p.likes = ss.likes';
        $query = $this->_em->createNativeQuery($sql, $rsm);

        $resultats = $query->getArrayResult();

        return $resultats;
    }
}

Вот ссылка на документацию: Native Query, если ответ не работает должным образом

person Maxime Lequain    schedule 24.04.2013

Чтобы сделать это с помощью DQL, вы можете использовать подход ниже, для обработки того же подхода с использованием подзапроса было бы сложно сделать с доктриной, поэтому я использую наибольшее-n-на-группу подход без использования агрегатной функции

ДЕМО SQL

$DM      = $this->get( 'Doctrine' )->getManager();
$DQL     = 'SELECT a 
        FROM AppBundle\Entity\Photo a
        LEFT JOIN AppBundle\Entity\Photo b WITH a.event = b.event AND a.likes < b.likes
        WHERE b.id IS NULL
        ORDER BY a.likes DESC
        ';
$query   = $DM->createQuery( $DQL );
$results = $query->getResult();
foreach ( $results as $result ) {
    echo '<pre>';
    print_r($result->getEvent()->getId().' - photo'.$result->getId().' - '. $result->getLikes() );
    echo '</pre>';
}

Обратите внимание, что я тестировал это с доктриной 2 и symfony 2.8.

person M Khalid Junaid    schedule 15.09.2017

Не эксперт в DQL, но если вы избегаете использования агрегатных функций, вы всегда можете использовать второй подход, описанный в SQL Выберите только строки с максимальным значением в столбце:

select p1.*
from photos p1
left outer join photos p2
on (p1.event_id = p2.event_id and p1.likes < p2.likes)
where p2.event_id is null;
person Adriano Carneiro    schedule 23.04.2013
comment
Ошибка: Идентификационная переменная AppNameBundle:Photo используется в выражении пути соединения, но не была определена ранее. :( - person httpete; 23.04.2013

В своем запросе вы присоединяетесь к ss.Id, но вы не выбрали этот столбец в своем подзапросе, поэтому ss.id не существует. Также ваше внутреннее соединение относится к таблице, которую вы назвали p2.event e2. Я думаю, вы имели в виду просто "событие e2"

SELECT p.id
FROM Photo p
INNER JOIN (
            SELECT p2.likes, e2.id
            FROM Photo p2
            INNER JOIN event e2
            GROUP BY e2.id
           ) ss ON ( p.event_id = ss.id AND p.likes = ss.likes )

Единственное изменение здесь заключается в том, что я добавил e2.id в ваш выбор подзапроса. Этот запрос по-прежнему не решает вашу проблему. Вам нужен тот, у которого больше всего лайков. Таким образом, вы должны превратить p2.likes в MAX(p2.likes)

SELECT p.id
FROM Photo p
INNER JOIN (
            SELECT MAX(p2.likes) AS Likes, e2.id
            FROM Photo p2
            INNER JOIN event e2
            GROUP BY e2.id
           ) ss ON ( p.event_id = ss.id AND p.likes = ss.likes )

Далее, учтите, что если две картинки привязаны к лайкам, вы вернете два результата с помощью этого запроса. Вы можете рассмотреть возможность использования CROSS APPLY (при условии, что он есть в DQL), чтобы избежать чего-то подобного.

SELECT e.id, p.id
FROM Events E
CROSS APPLY (
              SELECT TOP 1 p.id
              FROM photos p 
              WHERE p.event_id = e.id
              ORDER BY Likes DESC 
            ) p
person Aushin    schedule 23.04.2013
comment
Ваш ответ SQL или DQL? Я получаю ту же ошибку SELECT MAX(p2.likes),': Ошибка: Идентификационная переменная (используется в выражении пути соединения, но не была определена ранее. - person httpete; 23.04.2013
comment
Ой, простите. MAX(p2.likes) КАК лайков. Придется дать ему имя нравится. Я отредактировал второй запрос. Это в SQL. Я просто держу пари, что DQL поддерживает по крайней мере второй запрос здесь. - person Aushin; 23.04.2013
comment
Ваша ставка неверна, я все еще получаю ту же ошибку. Я ожидаю DQL, а не SQL. - person httpete; 23.04.2013
comment
Тогда очень жаль потраченного времени! - person Aushin; 23.04.2013