99 votes

PHP - Utilisation de PDO avec un tableau de clauses IN

J'utilise PDO pour exécuter une déclaration avec un fichier de type IN qui utilise un tableau pour ses valeurs :

$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();

Le code ci-dessus fonctionne parfaitement bien, mais ma question est de savoir pourquoi ceci ne fonctionne pas :

 $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();

Ce code renverra l'élément dont my_value est égal au premier élément du $in_array (1), mais pas les autres éléments du tableau (2, et 3).

144voto

L'AOP n'est pas doué pour ce genre de choses. Vous devez créer une chaîne avec des placeholders de façon dynamique et l'insérer dans la requête, tout en liant les valeurs du tableau de la façon habituelle. Avec des placeholders positionnels, ce serait comme ceci :

$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();

S'il y a d'autres caractères de remplacement dans la requête, vous pouvez utiliser l'approche suivante (le code est tiré de mon site web) Tutoriel PDO ):

Vous pourriez utiliser array_merge() pour réunir toutes les variables en un seul tableau, en ajoutant vos autres variables sous forme de tableaux, dans l'ordre où elles apparaissent dans votre requête :

$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();

Si vous utilisez des caractères de remplacement nommés, le code sera un peu plus complexe, car vous devrez créer une séquence de caractères de remplacement nommés, par exemple. :id0,:id1,:id2 . Donc le code serait :

// 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();

Heureusement, pour les placeholders nommés, nous n'avons pas à suivre l'ordre strict, nous pouvons donc fusionner nos tableaux dans n'importe quel ordre.

18voto

GordonM Points 14008

La substitution de variables dans les instructions préparées de PDO ne prend pas en charge les tableaux. C'est du un pour un.

Vous pouvez contourner ce problème en générant le nombre de placeholders dont vous avez besoin en fonction de la longueur du tableau.

$variables = array ('1', '2', '3');
$placeholders = str_repeat ('?, ',  count ($variables) - 1) . '?';

$query = $pdo -> prepare ("SELECT * FROM table WHERE column IN($placeholders)");
if ($query -> execute ($variables)) {
    // ...
}

5voto

Dienow Points 93

Comme PDO ne semble pas fournir une bonne solution, vous pourriez tout aussi bien envisager d'utiliser DBAL, qui suit en grande partie l'API de PDO, mais ajoute aussi quelques fonctionnalités utiles http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion

$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?)',
    array(array(1, 2, 3, 4, 5, 6)),
    array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY)
);

Il existe probablement d'autres paquets qui n'ajoutent pas de complexité et n'obscurcissent pas l'interaction avec la base de données (comme le font la plupart des ORM), mais qui facilitent en même temps les petites tâches typiques.

3voto

celsowm Points 554

Une version alternative de PHP Delusions (@your-common-sense) utilisant les fermetures :

$filter      = ["min_price" => "1.98"];
$editions    = [1,2,10];

$editions = array_combine(
    array_map(function($i){ return ':id'.$i; }, array_keys($editions)),
    $editions
);
$in_placeholders = implode(',', array_keys($editions));
$sql = "SELECT * FROM books WHERE price >= :min_price AND edition IN ($in_placeholders)";
$stm = $pdo->prepare($sql);
$stm->execute(array_merge($filter,$editions));
$data = $stm->fetchAll();

2voto

Ted Points 51

J'utilise souvent FIND_IN_SET au lieu de IN, comme ceci :

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

Ce n'est pas la meilleure solution en termes de performances, mais si le nombre d'options possibles pour le $in_array est limité, ce n'est généralement pas un problème. Je l'utilise souvent pour les statuts où my_value est un champ enum. Je n'ai jamais eu de problème avec cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X