views:

25

answers:

2

Hi there i've been having some trouble using an IN in a where clause using MySQLi this is my query:

SELECT * FROM core_tags WHERE tag_id IN (1,2,3,4,5) GROUP BY tag_id ORDER BY tag_popularity ASC

If I run this in PHP My Admin then I get 5 results as I would expect. However if I run it in PHP with the following code I only get one result of the tag_id '1'.

Here's my PHP. Originally I was running it using functions in a class but i've hand coded it to test that it wasn't simply an error in my functions with the same problem.

$mysqli = new mysqli(DB_SERVER, DB_NAME, DB_PASSWORD, DB_NAME);
$rawQuery = 'SELECT * FROM core_tags WHERE tag_id IN (?) GROUP BY tag_id ORDER BY tag_popularity ASC';
$stmt = $mysqli->prepare($rawQuery);
$stmt->bind_param("s", $tag_ids);
$tag_ids = "1,2,3,4,5";
$stmt->execute();
$stmt->bind_result($tag_id, $tag_name, $tag_desc, $tag_popularity);

while ($stmt->fetch()) {
    printf ("%s\n", $tag_name);
}

$stmt->close();
die();

Anyone have any idea why the mysqli version only returns one row? Using mysql instead of mysqli works fine as well, same as PHP My Admin.

+1  A: 

I'm not sure about PHP, but usually when you create a parameterized query like the one you're creating using only one parameter for the list of options in the 'IN' you would en up with something like:

select * from core_tags WHERE tag_id IN ('1,2,3')

that will not do what you want... you would need to add a parameter to the query for each value in the IN that you want

Jaime
That seems to work but makes it very difficult to use when the number of ids will vary from query to query. I'll see if anyone else can offer a better solution, but thankyou for your contribution.
Rob
+1: Jaime, you're right about how standard SQL you need to concatenate the string list of values in the IN clause before running the query string in mysqli.
OMG Ponies
I usually end up iterating trough the list of values and constructing the IN part kinda by hand in that way... not sure how to do it in PHP though
Jaime
A: 

Using a string prepared statement will cause your final SQL to look like:

IN ('1,2,3,4,5')

with the quotes, which is not what you want. What I'd do is this:

$ids= array(1,2,3,4,5);
$mysqli = new mysqli(DB_SERVER, DB_NAME, DB_PASSWORD, DB_NAME);

$rawQuery = 'SELECT * FROM core_tags WHERE tag_id IN (';
$rawQuery .= implode(',',array_fill(0,count($ids),'?'));
$rawQuery .= ') GROUP BY tag_id ORDER BY tag_popularity ASC';
$stmt = $mysqli->prepare($rawQuery);
call_user_func_array(array($stmt,'bind_param'),$ids);
$stmt->execute();
$stmt->bind_result($tag_id, $tag_name, $tag_desc, $tag_popularity);

while ($stmt->fetch()) {
    printf ("%s\n", $tag_name);
}

If the implode array_fill is confusing, it just is a shorthand way of creating an array of the same size as $ids full of "?", then turning them to a csv.

UPDATE: Non bind params way

Of course, if you want to skip the bind params nonsense, and you can trust the list of $ids to already be sanitized, you can just do this instead, and skip the bind_params section:

$rawQuery = 'SELECT * FROM core_tags WHERE tag_id IN (';
$rawQuery .= implode(',',$ids);
$rawQuery .= ') GROUP BY tag_id ORDER BY tag_popularity ASC';

If you can't trust the data:

function clean_ids(&$item){
 $item = intval($item);
}

$clean_ids = array_walk($ids,'clean_ids');
$rawQuery = 'SELECT * FROM core_tags WHERE tag_id IN (';
$rawQuery .= implode(',',$clean_ids);
$rawQuery .= ') GROUP BY tag_id ORDER BY tag_popularity ASC';
Mike Sherov
Cheers for this it was sort of how I was thinking it may need to be done, but thanks for figuring out exactly it should work. One thought, is there a reason for using implode and array_full instead of something like substr(str_repeat("?,", count($ids)), 0, -1); I don't know enough to know which is better performance wise etc
Rob
@Rob, Personal preference. I like working with arrays. I would consider the question about performance to be a microoptimization. The time PHP takes to do either one is miniscule compared to a db call :-). Also, see the updated answer, I provided two more methods to solve this.
Mike Sherov
Cheerrs for the extra examples, that was of course my alternative, but having moved over to prepared statements I wasn't keen to move away from it again. One thing. Your first example gives this error:Warning: mysqli_stmt::bind_param() [mysqli-stmt.bind-param]: Number of variables doesn't match number of parameters in prepared statementI presume because it uses the same variable each time and thus can't be used by reference or something like that?
Rob
@rob, oh yes, my mistake. Updating main example to use call_user_func_array
Mike Sherov
I don't know if you'll reply before I do, but the way around this was to use call_user_func_array(array($stmt, 'bind_param'), $args); with all the arguments needed to send to bind_param. I will edit my original question with the full answer. Thanks alot for your help.
Rob
haha we came to the same conclusion within 2 seconds of each other, very good and once again thankyou very much for the help.
Rob
@rob, No problem :) glad I could help and welcome to stackoverflow.
Mike Sherov