2

I would like to ask for your help with Doctrine2 DBAL query built with QueryBuilder. I'm used to ORM, but I think it's an overkill for such query which is being called in a listener.

I need a query with SELECT EXISTS and I don't know how I can construct it using DBAL QueryBuilder.

I have a subquery already created:

$subQuery = $connection->createQueryBuilder();
$subQuery
    ->select('o.id')
    ->from('order', 'o')
    ->leftJoin('o', 'payment', 'p')
    ->where($subQuery->expr()->isNull('p.id'))
;

I basically want to check if there are any unpaid orders. I now have no idea how to build the SELECT EXISTS query? Can anyone point me in the right direction? I was thinking about something like this:

$qb->select('EXISTS(?)')->setParameter($subQuery->getDQL())

Will that be the correct solution?

@EDIT

After a while of thinking I decided to use ORM instead. Unfortunately that did not work either, I'm getting an error:

line 0, col 7: Error: Expected known function, got 'EXISTS'

The DQL is: SELECT EXISTS(<subquery here>)

It is a bit weird considering that It has been build with QueryBuilder:

/* @var $qb QueryBuilder */
$qb = $this->em->createQueryBuilder();
$qb
        ->select($qb->expr()->exists($subQuery->getDQL()));
pzaj
  • 1,062
  • 1
  • 17
  • 37
  • See http://stackoverflow.com/questions/10030538/query-with-exists-for-doctrine-symfony2 – Weenesta - Mathieu Dormeval Dec 14 '16 at 10:59
  • Unfortunately this is ORM and I need DBAL, it does not have "exists()" expression. And I must avoid writing plain SQL. – pzaj Dec 14 '16 at 11:03
  • This explains how to add EXISTS to DQL: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/dql-user-defined-functions.html There are a couple of these user defined function bundles around so you might find an implementation somewhere. Shame you can't just use sql. – Cerad Dec 14 '16 at 14:15

1 Answers1

5

A few years late, but you need to specify your EXISTS subquery SQL within the SELECT or WHERE statement portion of the QueryBuilder, as opposed to using a parameter.

Additionally since order is a reserved word in MySQL, you will need to use identifier quotes ` (back-tick) to escape the table name.

When using the ORM; you must specify a FROM statement that references an entity, so you would need to change your approach.

$connection = $this->em->getConnection();
$expr = $connection->getExpressionBuilder();
$qbSub = $connection->createQueryBuilder()
    ->select('1')
    ->from('`order`', 'o')
    ->leftJoin('o', '`payment`', 'p', $expr->eq('p.order_id', 'o.id'))
    ->where($expr->isNull('p.id'));

/**
 * @return string "1" if a record exists, "0" otherwise
 */
$connection->createQueryBuilder()
    ->select('EXISTS(' . $qbSub->getSQL() . ')')
    ->execute()
    ->fetchColumn();

Resulting SQL

SELECT EXISTS(
   SELECT 1
   FROM `order` AS o
   LEFT JOIN `payment` AS p
   ON p.order_id = o.id
   WHERE p.id IS NULL
);

Note: If you have any parameters, the values for the placeholders must be bound using QueryBuilder::setParameter() on the top-level query, not the sub-queries.

$qbSub = $connection->createQueryBuilder()
    ->select('1')
    ->from('`order`', 'o')
    ->leftJoin('o', '`payment`', 'p', $expr->andX(
        $expr->eq('p.order_id', 'o.id'), 
        $expr->eq('p.name', ':name') // subquery placeholder
    ))
    ->where($expr->isNull('p.id'));

$connection->createQueryBuilder()
    ->select('EXISTS(' . $qbSub->getSQL() . ')')
    ->setParameter('name', $value) // subquery placeholder param value
    ->execute()
    ->fetchColumn();

However, I suggest changing your query from an exclusion join to an inclusion join with NOT EXISTS. Doing so will filter orders that have been paid, out of your result-set. Instead of attempting to join every order on every payment and retrieve the payments that return null. Dramatically improving the performance of the query.

Example db-fiddle

SELECT EXISTS (
    SELECT 1
    FROM `order` AS o
    WHERE NOT EXISTS(
        SELECT NULL
        FROM `payment` AS p
        WHERE p.order_id = o.id
    )
)
Will B.
  • 17,883
  • 4
  • 67
  • 69
  • subquery with params did not work in any way – max4ever Apr 20 '22 at 13:41
  • As stated in the answer, you must assign the params to the **root** query builder, not the sub-query builder. Otherwise please post a new question that includes your query. If you would like me to take a look comment with a link to your question. – Will B. Apr 20 '22 at 14:31
  • @max4ever Updated the subquery placeholder parameter note example to better illustrate usage. – Will B. Apr 20 '22 at 14:49