Question Puis-je lier un tableau à une condition IN ()?


Je suis curieux de savoir s'il est possible de lier un tableau de valeurs à un espace réservé en utilisant PDO. Le cas d’utilisation ici tente de passer un tableau de valeurs à utiliser avec un IN() condition.

J'aimerais pouvoir faire quelque chose comme ça:

<?php
$ids=array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN(:an_array)'
);
$stmt->bindParam('an_array',$ids);
$stmt->execute();
?>

Et PDO lier et citer toutes les valeurs dans le tableau.

En ce moment je fais:

<?php
$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
foreach($ids as &$val)
    $val=$db->quote($val); //iterate through array and quote
$in = implode(',',$ids); //create comma separated list
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN('.$in.')'
);
$stmt->execute();
?>

Ce qui fait certainement le travail, mais je me demande simplement s'il y a une solution intégrée qui me manque?


513
2018-05-28 11:17


origine


Réponses:


Je pense que Soulmerge a raison. vous devrez construire la chaîne de requête.

<?php
$ids     = array(1, 2, 3, 7, 8, 9);
$inQuery = implode(',', array_fill(0, count($ids), '?'));

$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN(' . $inQuery . ')'
);

// bindvalue is 1-indexed, so $k+1
foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();
?>

réparer: dan, tu avais raison corrigé le code (ne l'a pas testé quand même)

modifier: les deux chris (commentaires) et somebodyintrouble ont suggéré que le foreach-boucle ...

(...)
// bindvalue is 1-indexed, so $k+1
foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();

... pourrait être redondant, donc le foreach boucle et le $stmt->execute pourrait être remplacé par juste ...

<?php 
  (...)
  $stmt->execute($ids);
?>

(encore une fois, je ne l'ai pas testé)


241
2018-05-28 12:02



Pour quelque chose de rapide:

//$db = new PDO(...);
//$ids = array(...);

$qMarks = str_repeat('?,', count($ids) - 1) . '?';
$sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
$sth->execute($ids);

157
2018-05-23 15:10



Est-ce si important d'utiliser IN déclaration? Essayez d'utiliser FIND_IN_SET op.

Par exemple, il y a une requête dans PDO comme ça

SELECT * FROM table WHERE FIND_IN_SET(id, :array)

Ensuite, vous avez seulement besoin de lier un tableau de valeurs, implosé avec une virgule, comme celui-ci

$ids_string = implode(',', $array_of_smth); // WITHOUT WHITESPACES BEFORE AND AFTER THE COMMA
$stmt->bindParam('array', $ids_string);

et c'est fait.

UPD: Comme certaines personnes l'ont souligné dans les commentaires à cette réponse, il y a quelques problèmes qui devraient être énoncés explicitement.

  1. FIND_IN_SET n'utilise pas d'index dans une table, et il n'est pas encore implémenté - voir cet enregistrement dans le bug tracker MYSQL. Merci à @BillKarwin pour l'avis.
  2. Vous ne pouvez pas utiliser une chaîne avec une virgule comme valeur du tableau pour la recherche. Il est impossible d'analyser cette chaîne de la bonne manière après implode puisque vous utilisez un symbole de virgule comme séparateur. Merci à @VaL pour la note.

En résumé, si vous n'êtes pas très dépendant des index et n'utilisez pas de chaînes avec une virgule pour la recherche, ma solution sera beaucoup plus simple, plus simple et plus rapide que les solutions listées ci-dessus.


41
2017-10-02 23:22



Comme je fais beaucoup de requêtes dynamiques, c'est une fonction d'aide super simple que j'ai faite.

public static function bindParamArray($prefix, $values, &$bindArray)
{
    $str = "";
    foreach($values as $index => $value){
        $str .= ":".$prefix.$index.",";
        $bindArray[$prefix.$index] = $value;
    }
    return rtrim($str,",");     
}

Utilisez-le comme ceci:

$bindString = helper::bindParamArray("id", $_GET['ids'], $bindArray);
$userConditions .= " AND users.id IN($bindString)";

Renvoie une chaîne :id1,:id2,:id3 et met également à jour votre $bindArray des liaisons dont vous aurez besoin au moment d'exécuter votre requête. Facile!


27
2018-03-26 14:10



La solution d'EvilRygy n'a pas fonctionné pour moi. Dans Postgres vous pouvez faire une autre solution de contournement:


$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id = ANY (string_to_array(:an_array, ','))'
);
$stmt->bindParam(':an_array', implode(',', $ids));
$stmt->execute();

16
2018-04-15 13:07



très propre pour postgres utilise le postgres-array ("{}"):

$ids = array(1,4,7,9,45);
$param = "{".implode(', ',$ids)."}";
$cmd = $db->prepare("SELECT * FROM table WHERE id = ANY (?)");
$result = $cmd->execute(array($param));

14
2018-02-18 22:00



J'ai étendu PDO pour faire quelque chose de similaire à ce que les stefs suggèrent, et cela a été plus facile pour moi à long terme:

class Array_Capable_PDO extends PDO {
    /**
     * Both prepare a statement and bind array values to it
     * @param string $statement mysql query with colon-prefixed tokens
     * @param array $arrays associatve array with string tokens as keys and integer-indexed data arrays as values 
     * @param array $driver_options see php documention
     * @return PDOStatement with given array values already bound 
     */
    public function prepare_with_arrays($statement, array $arrays, $driver_options = array()) {

        $replace_strings = array();
        $x = 0;
        foreach($arrays as $token => $data) {
            // just for testing...
            //// tokens should be legit
            //assert('is_string($token)');
            //assert('$token !== ""');
            //// a given token shouldn't appear more than once in the query
            //assert('substr_count($statement, $token) === 1');
            //// there should be an array of values for each token
            //assert('is_array($data)');
            //// empty data arrays aren't okay, they're a SQL syntax error
            //assert('count($data) > 0');

            // replace array tokens with a list of value tokens
            $replace_string_pieces = array();
            foreach($data as $y => $value) {
                //// the data arrays have to be integer-indexed
                //assert('is_int($y)');
                $replace_string_pieces[] = ":{$x}_{$y}";
            }
            $replace_strings[] = '('.implode(', ', $replace_string_pieces).')';
            $x++;
        }
        $statement = str_replace(array_keys($arrays), $replace_strings, $statement);
        $prepared_statement = $this->prepare($statement, $driver_options);

        // bind values to the value tokens
        $x = 0;
        foreach($arrays as $token => $data) {
            foreach($data as $y => $value) {
                $prepared_statement->bindValue(":{$x}_{$y}", $value);
            }
            $x++;
        }

        return $prepared_statement;
    }
}

Vous pouvez l'utiliser comme ceci:

$db_link = new Array_Capable_PDO($dsn, $username, $password);

$query = '
    SELECT     *
    FROM       test
    WHERE      field1 IN :array1
     OR        field2 IN :array2
     OR        field3 = :value
';

$pdo_query = $db_link->prepare_with_arrays(
    $query,
    array(
        ':array1' => array(1,2,3),
        ':array2' => array(7,8,9)
    )
);

$pdo_query->bindValue(':value', '10');

$pdo_query->execute();

12
2018-01-21 12:15



Voici ma solution:

$total_items = count($array_of_items);
$question_marks = array_fill(0, $total_items, '?');
$sql = 'SELECT * FROM foo WHERE bar IN (' . implode(',', $question_marks ). ')';

$stmt = $dbh->prepare($sql);
$stmt->execute(array_values($array_of_items));

Notez l'utilisation de array_values. Cela peut résoudre les problèmes de commande de clés.

Je fusionnais des tableaux d'identifiants puis supprimais les éléments en double. J'avais quelque chose comme:

$ids = array(0 => 23, 1 => 47, 3 => 17);

Et ça échouait.


11
2018-05-28 11:48



Regarder PDO: Constantes pré-définies il n'y a pas de PDO :: PARAM_ARRAY dont vous auriez besoin comme indiqué sur PDOStatement-> bindParam 

bool PDOStatement :: bindParam (paramètre $ mélangé, mixed & $ variable [, int $ data_type [, int $ length [, mélangé $ driver_options]]]

Donc je ne pense pas que ce soit réalisable.


10
2018-03-18 19:16



Lorsque vous avez d'autres paramètres, vous pouvez faire comme ceci:

$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
$query = 'SELECT *
            FROM table
           WHERE X = :x
             AND id IN(';
$comma = '';
for($i=0; $i<count($ids); $i++){
  $query .= $comma.':p'.$i;       // :p0, :p1, ...
  $comma = ',';
}
$query .= ')';

$stmt = $db->prepare($query);
$stmt->bindValue(':x', 123);  // some value
for($i=0; $i<count($ids); $i++){
  $stmt->bindValue(':p'.$i, $ids[$i]);
}
$stmt->execute();

10
2017-07-12 14:44



Je me rends compte également que ce thread est vieux mais j'ai eu un problème unique où, en convertissant le pilote mysql bientôt obsolète au pilote PDO, j'ai dû faire une fonction qui pourrait construire, dynamiquement, à la fois les params normaux et les IN du même tableau de param. J'ai donc rapidement construit ceci:

/**
 * mysql::pdo_query('SELECT * FROM TBL_WHOOP WHERE type_of_whoop IN :param AND siz_of_whoop = :size', array(':param' => array(1,2,3), ':size' => 3))
 *
 * @param $query
 * @param $params
 */
function pdo_query($query, $params = array()){

    if(!$query)
        trigger_error('Could not query nothing');

    // Lets get our IN fields first
    $in_fields = array();
    foreach($params as $field => $value){
        if(is_array($value)){
            for($i=0,$size=sizeof($value);$i<$size;$i++)
                $in_array[] = $field.$i;

            $query = str_replace($field, "(".implode(',', $in_array).")", $query); // Lets replace the position in the query string with the full version
            $in_fields[$field] = $value; // Lets add this field to an array for use later
            unset($params[$field]); // Lets unset so we don't bind the param later down the line
        }
    }

    $query_obj = $this->pdo_link->prepare($query);
    $query_obj->setFetchMode(PDO::FETCH_ASSOC);

    // Now lets bind normal params.
    foreach($params as $field => $value) $query_obj->bindValue($field, $value);

    // Now lets bind the IN params
    foreach($in_fields as $field => $value){
        for($i=0,$size=sizeof($value);$i<$size;$i++)
            $query_obj->bindValue($field.$i, $value[$i]); // Both the named param index and this index are based off the array index which has not changed...hopefully
    }

    $query_obj->execute();

    if($query_obj->rowCount() <= 0)
        return null;

    return $query_obj;
}

Il n'est toujours pas testé mais la logique semble être là.

J'espère que ça aide quelqu'un dans la même position,

Edit: Après quelques tests j'ai découvert:

  • AOP n'aime pas '.' dans leurs noms (ce qui est un peu stupide si vous me demandez)
  • bindParam est la mauvaise fonction, bindValue est la bonne fonction.

Code édité à la version de travail.


9
2018-02-18 15:18