web-dev-qa-db-fra.com

PHP - Utilisation de PDO avec un tableau de clauses IN

J'utilise PDO pour exécuter une instruction avec une clause 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, mais ma question est de savoir pourquoi.

 $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 qui est my_value égal au premier élément du $in_array (1), mais pas aux éléments restants du tableau (2 et 3).

39
iRector

AOP n'est pas bon avec de telles choses. Vous devez créer une chaîne avec des points d'interrogation de manière dynamique et l'insérer dans une requête.

$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 existe d'autres espaces réservés dans la requête, vous pouvez utiliser l'approche suivante (le code provient de mon PDO tutorial ):

Vous pouvez utiliser la fonction array_merge() pour réunir toutes les variables dans un seul tableau, en ajoutant vos autres variables sous la forme de tableaux, dans l'ordre dans lequel 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 espaces réservés nommés, le code serait un peu plus complexe, car vous devez créer une séquence des espaces réservés 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 = "";
foreach ($ids as $i => $item)
{
    $key = ":id".$i;
    $in .= "$key,";
    $in_params[$key] = $item; // collecting values into key-value array
}
$in = rtrim($in,","); // :id0,:id1,:id2

$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 espaces réservés nommés, nous n'avons pas à suivre l'ordre strict, nous pouvons donc fusionner nos tableaux dans n'importe quel ordre.

54
Your Common Sense

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

Vous pouvez contourner ce problème en générant le nombre d'espaces réservés 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)) {
    // ...
}
10
GordonM

PDO ne semblant pas offrir une bonne solution, vous pouvez également envisager d’utiliser DBAL, qui suit principalement l’API de PDO, mais ajoute également des fonctionnalités utiles http://docs.doctrine-project.org/projects/doctrine- dbal/fr/latest/référence/récupération-de-données-et-manipulation.html # conversion de liste de paramètres

$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 packages qui n'apportent pas de complexité et n'occultent pas l'interaction avec la base de données (comme le font la plupart des ORM), mais qui simplifient en même temps les petites tâches typiques. 

2
Dienow

J'ai pu le faire comme ça. L'idée est qu'une valeur provient d'un formulaire de recherche. Ils recherchent quelque chose et la valeur peut être dans l'un des deux champs suivants: thisField, thatField ou égal à someField.

$randomValue = "whatever";
$query = "SELECT * FROM myTable WHERE ((:randomValue in(thisField) or :randomValue in(thatField)) or (someField = :randomValue));";
                $statement = $this->mySQL_db->prepare($query);
                $statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);
                $statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);
                $statement->bindparam(":randomValue", $randomValue, PDO::PARAM_STR);
1
Karin

Je viens de me heurter à ce problème et codé un petit wrapper. Ce n'est pas le code le plus joli ou le meilleur, j'en suis sûr, mais cela pourrait aider quelqu'un, alors voici:

function runQuery(PDO $PDO, string $sql, array $params = [])
{
    if (!count($params)) {
        return $PDO->query($sql);
    }

    foreach ($params as $key => $values) {
        if (is_array($values)) {
            // get placeholder from array, e.g. ids => [7,12,3] would be ':ids'
            $oldPlaceholder  = ':'.$key;
            $newPlaceholders = '';
            $newParams = [];
            // loop through array to create new placeholders & new named parameters
            for($i = 1; $i <= count($values); $i++) {
                // this gives us :ids1, :ids2, :ids3 etc
                $newKey = $oldPlaceholder.$i;
                $newPlaceholders .= $newKey.', ';
                // this builds an associative array of the new named parameters
                $newParams[$newKey] = $values[$i - 1];
            }
            //trim off the trailing comma and space
            $newPlaceholders = rtrim($newPlaceholders, ', ');

            // remove the old parameter
            unset($params[$key]);

            // and replace with the new ones
            $params = array_merge($params, $newParams);

            // amend the query
            $sql = str_replace($oldPlaceholder, $newPlaceholders, $sql);
        }
    }

    $statement = $PDO->prepare($sql);
    $statement->execute($params);
    return $statement;
}

E.g, en les passant dans:

SELECT * FROM users WHERE userId IN (:ids)

array(1) {
  ["ids"]=>
  array(3) {
    [0]=>
    int(1)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
}

Devient:

SELECT * FROM users WHERE userId IN (:ids1, :ids2, :ids3)

array(3) {
  [":ids1"]=>
  int(1)
  [":ids2"]=>
  int(2)
  [":ids3"]=>
  int(3)
}

Ce n'est pas pare-balles, mais en tant que développeur unique pour mes besoins, tout se passe bien, jusqu'à présent de toute façon.

0
Codemonkey

Voici une solution pour les espaces réservés non nommés (?). Si vous passez $ sql avec un point d'interrogation comme "A =? AND B IN (?)" Et $ args où certains des éléments sont des tableaux comme [1, [1,2,3]], le code retournera SQL chaîne avec le nombre approprié d'espaces réservés - "A =? AND B IN (?,?,?)". Il faut le paramètre $ args uniquement pour trouver quel élément est un tableau et combien d'espaces réservés il a besoin de . Vous pouvez trouver la petite classe d'extension PDO avec cette méthode qui exécutera votre requête: https://github.com/vicF/pdo/blob/master/src/PDO.php

public function replaceArrayPlaceholders($sql, $args)
{
    $num = 0;
    preg_match_all('/\?/', $sql, $matches, PREG_OFFSET_CAPTURE);  // Captures positions of placeholders
    //echo $matches[0][1][1];
    $replacements = [];
    foreach($args as $arg) {
        if(is_array($arg)) {
            $replacements[$matches[0][$num][1]] = implode(',',array_fill(0, count($arg), '?')); // Create placeholders string
        }
        $num++;
    }
    krsort($replacements);
    foreach($replacements as $position => $placeholders) {
        $sql = substr($sql, 0, $position).$placeholders.substr($sql, $position+1); // Replace single placeholder with multiple
    }
    return $sql;
}
0
Victor

Si j'ai bien compris, c'est parce que PDO traitera le contenu de $ in_values ​​comme un élément unique et le modifiera en conséquence. PDO verra 1,2,3 comme une seule chaîne et la requête ressemblera à 

SELECT * FROM table WHERE my_value IN ("1,2,3")

Vous pensez peut-être que le fait de changer l'implosion pour avoir des guillemets et des virgules résoudra le problème, mais ce ne sera pas le cas. PDO verra les guillemets et changera comment il cite la chaîne.

Quant à savoir pourquoi votre requête correspond à la première valeur, je n’ai aucune explication.

0
PCaligari