Search code examples
phpmysqlpdoprepared-statementin-clause

PHP - Using PDO with IN clause array


I'm using PDO to execute a statement with an IN clause that uses an array for its values:

$in_array = array(1, 2, 3);
$in_values = implode(',', $in_array);
$my_result = $wbdb->prepare("SELECT * FROM my_table WHERE my_value IN (".$in_values.")");
$my_result->execute();
$my_results = $my_result->fetchAll();

The above code works perfectly fine, but my question is why this doesn't:
 $in_array = array(1, 2, 3);
    $in_values = implode(',', $in_array);
    $my_result = $wbdb->prepare("SELECT * FROM my_table WHERE my_value IN (:in_values)");
    $my_result->execute(array(':in_values' => $in_values));
    $my_results = $my_result->fetchAll();

This code will return the item whose my_value equals the first item in the $in_array (1), but not the remaining items in the array (2, and 3).


Solution

  • PDO is not good with such things. You need to create a string with placeholders dynamically and insert it into the query, while binding array values the usual way. With positional placeholders it would be like this:

    $in  = str_repeat('?,', count($in_array) - 1) . '?';
    $sql = "SELECT * FROM my_table WHERE my_value IN ($in)";
    $stm = $db->prepare($sql);
    $stm->execute($in_array);
    $data = $stm->fetchAll();
    

    In case there are other placeholders in the query, you could use the following approach (the code is taken from my PDO tutorial):

    You could use array_merge() function to join all the variables into a single array, adding your other variables in the form of arrays, in the order they appear in your query:

    $arr = [1,2,3];
    $in  = str_repeat('?,', count($arr) - 1) . '?';
    $sql = "SELECT * FROM table WHERE foo=? AND column IN ($in) AND bar=? AND baz=?";
    $stm = $db->prepare($sql);
    $params = array_merge([$foo], $arr, [$bar, $baz]);
    $stm->execute($params);
    $data = $stm->fetchAll();
    

    In case you are using named placeholders, the code would be a little more complex, as you have to create a sequence of the named placeholders, e.g. :id0,:id1,:id2. So the code would be:

    // other parameters that are going into query
    $params = ["foo" => "foo", "bar" => "bar"];
    
    $ids = [1,2,3];
    $in = "";
    $i = 0; // we are using an external counter 
            // because the actual array keys could be dangerous
    foreach ($ids as $item)
    {
        $key = ":id".$i++;
        $in .= ($in ? "," : "") . $key; // :id0,:id1,:id2
        $in_params[$key] = $item; // collecting values into a key-value array
    }
    
    $sql = "SELECT * FROM table WHERE foo=:foo AND id IN ($in) AND bar=:bar";
    $stm = $db->prepare($sql);
    $stm->execute(array_merge($params,$in_params)); // just merge two arrays
    $data = $stm->fetchAll();
    

    Luckily, for the named placeholders we don't have to follow the strict order, so we can merge our arrays in any order.