1

Before I begin, I believe I have tried everything from this previous post: How to use WHERE IN with Doctrine 2

So I have a Silex application connected to a MySQL DB using Doctrine via composer (doctrine/dbal 2.2.*)

The query builder I am trying to run is this:

$qb = $this->db->createQueryBuilder();

$stmt = $qb->select('DAY(datefield1) x, COUNT(*) value')
    ->from('table1', 's')
    ->join('s', 'table2', 't', 't.key=s.key')
    ->where('MONTH(datefield1) = :billMonth')
    ->andWhere('YEAR(datefield1) = :billYear')
    ->andWhere('t.key IN (:keylist)')
    ->groupBy('x')
    ->orderBy('x', 'asc')
    ->setParameter(':billMonth', $month)
    ->setParameter(':billYear', $year)
    ->setParameter(':keylist', implode(",", $keylist))
    ->execute();

return $stmt->fetchAll(\PDO::FETCH_ASSOC);

The parameters are (month=8)(year=2014)(keylist=array(1,2,3,4))

The query does not fail but it curiously doesn't contain all the data that it should.

I have tried ->setParameter(':keylist', $keylist) to use the raw array, and this didn't work.

I have tried this kind of syntax too:

$qb->add('where', $qb->expr()->in('r.winner', array('?1')));

However that threw up an error because the in method wasn't available in expression builder class.

Please will someone cast an eye over this and save me from having to hardcode my SQL?

Community
  • 1
  • 1
puppyFlo
  • 445
  • 4
  • 16
  • 1
    ->setParameter(':keylist', $keylist) is the correct syntax. D2 is smart enough to handle arrays for parameters. Are you sure you are not getting all your data? You can try pasting the generated sql into your database connector. – Cerad Sep 11 '14 at 15:34
  • Yeah i have tried it and its producing different data. In fact its returning NO data - i.e. the query fails :( Maybe it too is suffering from the int/string conversion issue? – puppyFlo Sep 11 '14 at 15:45
  • If you take out the other where conditions do your get the $keylist records? Is t.key a string or integer? You don't have a database column named key do you? Because that is a reserved word and will cause select issues. When you say the query fails are you getting an actual sql error or just no records? – Cerad Sep 11 '14 at 15:47
  • You are using the doctrine connection object and not the orm entity manager. So having a column named key is a no no. D2 does not escape column names. – Cerad Sep 11 '14 at 15:52
  • I do not actually have a column named key, apologies for the confusion - i was trying to generalise the statement but see how that's made it look like an issue (i don't post here often). The column called something completely different "t.i_tariff". The only reserved word is the Value alias but with the suggested change below that still works. – puppyFlo Sep 11 '14 at 16:01
  • Yep. Once I realize that the question does not reflect the problem I just move on. – Cerad Sep 11 '14 at 16:47

6 Answers6

2

OK seeing as this old thread has seen some action since I last looked I wanted to confirm that the issue is long resolved - a third parameter in setParameter allows you to inform Doctrine how to handle the array:

    $qb = $this->db->createQueryBuilder();

    $stmt = $qb
        ->select('*')
        ->from(self::DB_TABLE, 'x')
        ->where('x.service IN (:services)')
        ->orderBy('x.session_end', 'DESC')
        ->addOrderBy('x.direction', 'DESC')
        ->setParameter(':services', $services, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
        ->setFirstResult($offset)
        ->setMaxResults($count)
        ->execute();

    $result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
puppyFlo
  • 445
  • 4
  • 16
1

DB placeholders/parameters are for single values. You're passing in a monolithic string 1,2,3,4 due to calling implode() on the array. Given:

WHERE t.key IN (:keylist)

then this query will be executed as the equivalent of

WHERE t.key IN ('1,2,3,4')
                ^-------^---note the quotes

Since it's a string, and only one single string in the IN clause, it's the functional equivalent of

WHERE t.key = '1,2,3,4'

and not the

WHERE (t.key = 1 OR t.key = 2 OR ....)

you want it to be. Either set up multiple parameters, one for each value in your array, or embed your string in the query directly

->andWhere('t.key IN (' . implode(',', $keylist) . ')')

which of course opens you up to sql injection attack vulnerabilities.

Marc B
  • 356,200
  • 43
  • 426
  • 500
  • Hi Marc, thanks for answering. I can see my obvious oversight with the implode and appreciate you pointing that out. I'm not sure who is responsible for the downvote - presumably they have a better answer coming but for now i'm going to use your suggested method as its working for me just fine. The array is hard coded elsewhere so no injection concerns afaik! Thanks – puppyFlo Sep 11 '14 at 15:32
1

Correct way to handle such IN clause is as simple as it can be:

$qb = $this->db->createQueryBuilder();

$stmt = $qb->(...)
    ->andWhere('t.key IN (:keylist)')
    ->setParameter(':keylist', $keylist)
    ->getQuery()
    ->getArrayResult();

I have used this dozen times and it works - doctrine is smart enough to handle your $keylist being array.

Other thing is that I don't know why you're using fetchAll method which is reduntant here - execute() is just enough. Maybe this is a root of your problem.

My suggestion: try to fire action in dev mode (app_dev.php) and check your app/logs/dev.log - you will find all sql queries performed. Verify that database returns data which you are expecting from Doctrine.

Tomasz Madeyski
  • 10,742
  • 3
  • 50
  • 62
  • Thank you for pointing out the fetchAll issue - upon inspection its used to get the data back as an associative array but this is solved with a simple return (array) $stmt; I wonder if the query was firing twice with the fetchAll? – puppyFlo Sep 11 '14 at 16:05
  • Sorry i should add that your suggested syntax simply is not working for me and i am aware that it should be correct. BTW i don't have an app/logs/dev.log – puppyFlo Sep 11 '14 at 16:07
  • Actually to add to the confusion (i should slow myself down..) the array casting doesn't work in the same way fetchAll does - i still need that for the data to return. Execute must just be getting the resource but not the data, right? – puppyFlo Sep 11 '14 at 16:09
  • you don't need casting thing at all, if you want regular array just use `getArrayResult` instead of `execute`. As for your logs file: do you have proper permissions on app/logs and app/cache? Try setting 777 on these (proper way to solve permissions: http://symfony.com/doc/current/book/installation.html#configuration-and-setup – Tomasz Madeyski Sep 11 '14 at 16:11
  • I don't even have the folders! I'm off to test getArrayResult now - still early days for me learning Doctrine and i really appreciate your time – puppyFlo Sep 11 '14 at 16:14
  • Following on - i just tested getArrayResult and my IDE complained that it wasn't a method in \Doctrine\DBAL\Query\QueryBuilder - i tested anyway and my IDE was right... – puppyFlo Sep 11 '14 at 16:16
  • "Method 'getQuery' not found in class \Doctrine\DBAL\Query\QueryBuilder" - Is my Doctrine library potentially the issue here?!! I have getSQL() but that seems irrelevant? – puppyFlo Sep 11 '14 at 16:24
  • 1
    @Tomasz is thinking that you are using the ORM QueryBuilder and not the DBAL QueryBuilder. Tagging your question with Symfony 2 and not Silex is causing the confusion. – Cerad Sep 11 '14 at 16:37
  • You're using `createQueryBuilder` on your `db` variable which is apparently instance of `Doctrine\DBAL\Connection` and to make all this examples work you need to call `createQueryBuilder` on `Doctrine\ORM\EntityManager` or `Doctrine\ORM\EntityRepository`. I don't know your context, and I don't know what objects you have access to, but your `$this->db` is a problem here – Tomasz Madeyski Sep 11 '14 at 16:38
  • Ah ok - its probably because its a 3rd party database and i can't fully configure the entities and repositories as i have elsewhere in my project if that makes any sense? Apologies for any lack of clarity - Doctrine/Silex are still very new to me – puppyFlo Sep 11 '14 at 17:05
1

If you build the sql query yourself you can use DBAL's PARAM_INT_ARRAY type:

use Doctrine\DBAL\Connection as DB;

$db->executeQuery('SELECT DAY(datefield1) x, COUNT(*) value
FROM table1 s
JOIN table2 t ON t.key=s.key
WHERE MONTH(datefield1) = :billMonth
    AND YEAR(datefield1) = :billYear
    AND t.key IN (:keylist)
GROUP BY x
ORDER BY x ASC',
array(':billMonth' => $month, ':billYear' => $year, ':keylist' => $keylist),
array(':billMonth' => \PDO::PARAM_INT, ':billYear' => \PDO::PARAM_INT, ':keylist' => DB::PARAM_INT_ARRAY
)->fetchAll(\PDO::FETCH_ASSOC);
0
    ->add('where', $qb->expr()->andX(                    
                        $qb->expr()->in('r.winner', ':winner')
                    ))
                ->setParameters(array(
                        "winner" => $winners,
                        "billMonth", $month,
                     // add all params here
                    ));
SylarBg
  • 121
  • 1
  • 2
0

You should use the in expression via the querybuilder like below, and change how you set the parameter:

->andWhere($qb->expr()->in('t.key', ':keylist'))

The complete code:

$qb = $this->db->createQueryBuilder();
$stmt = $qb->select('DAY(datefield1) x, COUNT(*) value')
    ->from('table1', 's')
    ->join('s', 'table2', 't', 't.key=s.key')
    ->where('MONTH(datefield1) = :billMonth')
    ->andWhere('YEAR(datefield1) = :billYear')
    ->andWhere('t.key')
    ->andWhere($qb->expr()->in('t.key', ':keylist'))
    ->groupBy('x')
    ->orderBy('x', 'asc')
    ->setParameter(':billMonth', $month)
    ->setParameter(':billYear', $year)
    ->setParameter(':keylist', $keylist)
    ->execute();

return $stmt->fetchAll(\PDO::FETCH_ASSOC);
tom
  • 8,189
  • 12
  • 51
  • 70