Doctrine2 - присоединение к подзапросу без отношения

Хорошо, вот в чем проблема.

У меня есть сущность с именем HelpDocuments и сущность с именем LogEntry.

HelpDocuments может быть отклонен пользователем. Когда это происходит, я создаю LogEntry со следующими атрибутами:

  • событие - например: helpDocument.dismiss
  • entity_id - например: 11
  • entityDiscriminator - например: HelpDocument

Между HelpDocument и LogEntry не создается никаких отношений, поскольку я реализую свою собственную логику дискриминатора.

Итак, я пытаюсь получить запрос на все документы HelpDocuments, которые не были отклонены. Я могу сделать это с помощью sql, левое внешнее соединение подзапроса следующим образом:

SELECT HelpDocument.*, temp.*
FROM HelpDocument
LEFT OUTER JOIN(
    SELECT LogEntry.entity_id
    FROM LogEntry
    WHERE LogEntry.entityDiscriminator = 'HelpDocument'
    AND LogEntry.event = 'helpDocument.dismiss'
    AND LogEntry.entity_id = 11
) as temp ON HelpDocument.id = temp.entity_id
WHERE temp.entity_id IS NULL;

Моя проблема в том, как мне превратить это в DQL, учитывая, что отношения не определены?


Обновленное решение:

Таким образом, решение заключалось в том, чтобы не использовать LEFT OUTER JOIN, потому что они не существуют / не имеют смысла в Doctrine2. В конце концов, мне пришлось выполнить соединение подзапроса:

/**
 * Filter by User Dismissed
 *
 * @param $qb
 * @param $route
 * @return mixed
 */
public function filterQueryByUserDismissed(QueryBuilder $qb, $args)
{
    $args = array_merge(array(
        "user" => null,
        "dismissed" => false
    ), $args);

    /** @var $dismissedQB QueryBuilder */
    $dismissedQB = $this->_em->createQueryBuilder();

    /*
    This line is important. We select an alternative attribute rather than
    letting Doctrine select le.id
    */
    $dismissedQB->select('le.entityId')
                ->from('\Mlf\AppBundle\Entity\UserEntityEventLog', 'le')
                ->where('le.entityDiscriminator = :entityDiscriminator')
                ->andWhere('le.event = :event')
                ->andWhere('le.user = :userId');

    $function = (true === $args['dismissed']) ? "in" : "notIn";
    $expr = $qb->expr()->$function($this->classAlias.'.id', $dismissedQB->getDQL());

    /** @var $qb QueryBuilder */
    $qb->andWhere($expr)
       ->setParameter("entityDiscriminator", HelpDocument::getDiscriminator())
       ->setParameter("event", HelpDocumentEvents::HELPDOCUMENT_DISMISS)
       ->setParameter("userId", $args["user"]);

//  exit($result = $qb->getQuery()->getSQL());

    return $qb;
}

Этот запрос DQL приводит к следующему SQL:

SELECT h0_.id AS id0
FROM HelpDocument h0_ 
WHERE (
    h0_.id NOT IN (
        SELECT l1_.entity_id 
        FROM LogEntry l1_ 
        WHERE l1_.entityDiscriminator = 'helpDocument' 
        AND l1_.event = 'helpDocument.dismiss' 
        AND l1_.user_id = 1
    )
)

Ура!


person Leevi Graham    schedule 16.01.2013    source источник
comment
Хорошая работа, решая это самостоятельно. Отметьте, пожалуйста, ответ.   -  person Lighthart    schedule 22.02.2013


Ответы (1)


Я видел ваше решение, и у меня есть небольшое изменение, которое значительно улучшит производительность. Особенно, если у вас больше пары тысяч строк.

public function filterQueryByUserDismissed(QueryBuilder $qb, $args)
{
  $args = array_merge(array(
    "user" => null,
    "dismissed" => false
  ), $args);

  /** @var $dismissedQB QueryBuilder */
  $dismissedQB = $this->_em->createQueryBuilder();

  /*
  This line is important. We select an alternative attribute rather than
  letting Doctrine select le.id
  */
  $dismissedQB->select('le.entityId')
            ->from('\Mlf\AppBundle\Entity\UserEntityEventLog', 'le')
            ->where('le.entityDiscriminator = :entityDiscriminator')
            ->andWhere('le.event = :event')
            ->andWhere('le.user = :userId');

  // ---- My changes below
  // Get an array with the ids
  $dismissedIdsMap = $dismissedQB->getQuery()->getResults();
  $dismissedIds = array_map(
        function($a){
            return $a['entityId'];
        },
        $dismissedIdsMap);

  $function = (true === $args['dismissed']) ? "in" : "notIn";
  $expr = $qb->expr()->$function($this->classAlias.'.id', $dismissedIds);
  // ---- My changes above

  /** @var $qb QueryBuilder */
  $qb->andWhere($expr)
   ->setParameter("entityDiscriminator", HelpDocument::getDiscriminator())
   ->setParameter("event", HelpDocumentEvents::HELPDOCUMENT_DISMISS)
   ->setParameter("userId", $args["user"]);

  //  exit($result = $qb->getQuery()->getSQL());

  return $qb;
}

В приведенном выше коде используются два запроса. Если вы используете один запрос, MySQL создаст временное представление из вашего подзапроса, а затем запросит представление с помощью главного запроса. Создание этого представления требует больших затрат времени. С двумя запросами вы сохраните «представление» в памяти PHP, и это значительно снизит накладные расходы.

person Tobias Nyholm    schedule 07.01.2015