web-dev-qa-db-de.com

Kann ich ein Array an eine IN () - Bedingung binden?

Ich bin gespannt, ob es möglich ist, ein Array von Werten mit PDO an einen Platzhalter zu binden. In diesem Anwendungsfall wird versucht, ein Array von Werten für die Verwendung mit einer IN()-Bedingung zu übergeben.

Ich möchte in der Lage sein, so etwas zu tun:

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

Bitten Sie PDO, alle Werte im Array zu binden und in Anführungszeichen zu setzen.

Im Moment mache ich:

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

Welches ist mit Sicherheit das Richtige für Sie, aber fragen Sie sich, ob ich eine integrierte Lösung vermisse?

544
Andru

ich denke, Soulmerge ist richtig. Sie müssen die Abfragezeichenfolge erstellen.

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

fix: Dan, du hattest recht. Code korrigiert (habe ihn aber nicht getestet)

edit: sowohl chris (kommentare) als auch jemand anderes haben vorgeschlagen, dass die foreach-schleife ...

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

$stmt->execute();

... könnte redundant sein, also die foreach Schleife und die $stmt->execute könnte ersetzt werden durch nur ...

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

(wieder habe ich es nicht getestet)

250
stefs

Für etwas schnelles:

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

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

Ist es so wichtig, die Anweisung IN zu verwenden? Versuchen zu benutzen FIND_IN_SET op.

Zum Beispiel gibt es eine solche Abfrage in PDO

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

Dann müssen Sie nur ein Array von Werten binden, die wie dieses mit Komma implodiert sind

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

und es ist geschafft.

UPD: Wie einige in Kommentaren zu dieser Antwort betonten, gibt es einige Punkte, die ausdrücklich erwähnt werden sollten.

  1. FIND_IN_SET verwendet keinen Index in einer Tabelle und ist noch nicht implementiert - siehe dieser Eintrag im MYSQL-Bugtracker . Vielen Dank an @BillKarwin für den Hinweis.
  2. Sie können keine Zeichenfolge mit Komma als Wert des Arrays für die Suche verwenden. Es ist unmöglich, eine solche Zeichenfolge nach implode richtig zu analysieren, da Sie das Komma als Trennzeichen verwenden. Danke an @VaL für den Hinweis.

In Ordnung, wenn Sie nicht stark von Indizes abhängig sind und keine Zeichenfolgen mit Komma für die Suche verwenden, ist meine Lösung viel einfacher, einfacher und schneller als die oben aufgeführten Lösungen.

46

Da ich viele dynamische Abfragen mache, ist dies eine supereinfache Hilfsfunktion, die ich erstellt habe.

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

Benutze es so:

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

Liefert einen String :id1,:id2,:id3 und aktualisiert auch Ihr $bindArray von Bindungen, die Sie benötigen, wenn Ihre Abfrage ausgeführt werden soll. Einfach!

32
prograhammer

ein sehr sauberer Weg für Postgres ist die Verwendung des Postgres-Arrays ("{}"):

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

Die Lösung von EvilRygy hat bei mir nicht funktioniert. In Postgres können Sie eine andere Problemumgehung durchführen:


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

Ich habe PDO erweitert, um etwas zu tun, das den Vorschlägen von stefs ähnelt, und es war auf lange Sicht einfacher für mich:

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;
    }
}

Du kannst es so benutzen:

$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
Chris

Hier ist meine Lösung:

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

Beachten Sie die Verwendung von array_values. Hierdurch können Probleme mit der Schlüsselbestellung behoben werden.

Ich habe ID-Arrays zusammengeführt und dann doppelte Elemente entfernt. Ich hatte so etwas wie:

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

Und das scheiterte.

12
Progrock

Wenn Sie andere Parameter haben, können Sie dies tun:

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

Für mich ist die sexy Lösung, ein dynamisches assoziatives Array zu konstruieren und es zu verwenden

// A dirty array sent by user
$dirtyArray = ['Cecile', 'Gilles', 'Andre', 'Claude'];

// we construct an associative array like this
// [ ':name_0' => 'Cecile', ... , ':name_3' => 'Claude' ]
$params = array_combine(
    array_map(
        // construct param name according to array index
        function ($v) {return ":name_{$v}";},
        // get values of users
        array_keys($dirtyArray)
    ),
    $dirtyArray
);

// construct the query like `.. WHERE name IN ( :name_1, .. , :name_3 )`
$query = "SELECT * FROM user WHERE name IN( " . implode(",", array_keys($params)) . " )";
// here we go
$stmt  = $db->prepare($query);
$stmt->execute($params);
11

Betrachtet man PDO: Vordefinierte Konstanten gibt es kein PDO :: PARAM_ARRAY, das Sie benötigen würden, wie auf PDOStatement-> bindParam aufgeführt

bool PDOStatement :: bindParam (gemischter $ Parameter, gemischte & $ Variable [ int $ Datentyp [ int $ Länge [ gemischte $ Treiberoptionen]]] )

Ich denke also nicht, dass es erreichbar ist.

10
user83632

Mir ist auch klar, dass dieser Thread alt ist, aber ich hatte ein einzigartiges Problem, bei dem ich beim Konvertieren des bald veralteten mysql-Treibers in den PDO-Treiber eine Funktion erstellen musste, die sowohl normale Parameter als auch INs dynamisch aus denselben erstellen konnte param Array. Also habe ich das schnell gebaut:

/**
 * 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;
}

Es ist noch nicht getestet, aber die Logik scheint da zu sein.

Hoffe, es hilft jemandem in der gleichen Position,

Edit: Nach einigen Tests fand ich heraus:

  • PDO mag nicht "." in ihren Namen (was irgendwie dumm ist, wenn du mich fragst)
  • bindParam ist die falsche Funktion, bindValue ist die richtige Funktion.

Code auf Arbeitsversion bearbeitet.

9
Sammaye

Eine kleine Überarbeitung zum Code von Schnalle

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

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

foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();
?>

//implode(',', array_fill(0, count($ids)-1), '?')); 
//'?' this should be inside the array_fill
//$stmt->bindValue(($k+1), $in); 
// instead of $in, it should be $id
8

Welche Datenbank verwenden Sie? In PostgreSQL verwende ich gerne ANY (Array). Um Ihr Beispiel wiederzuverwenden:

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

Leider ist das ziemlich nicht portabel.

In anderen Datenbanken müssen Sie Ihre eigene Magie erfinden, wie andere bereits erwähnt haben. Sie sollten diese Logik in eine Klasse/Funktion einordnen, damit sie im gesamten Programm wiederverwendbar ist. Schauen Sie sich die Kommentare auf der Seite mysql_query In PHP.NET an, um weitere Gedanken zum Thema und Beispiele für dieses Szenario zu erhalten.

7
Ryan Bair

Nachdem ich dasselbe Problem durchlaufen hatte, ging ich zu einer einfacheren Lösung (obwohl immer noch nicht so elegant wie ein PDO::PARAM_ARRAY Wäre):

angesichts des Arrays $ids = array(2, 4, 32):

$newparams = array();
foreach ($ids as $n => $val){ $newparams[] = ":id_$n"; }

try {
    $stmt = $conn->prepare("DELETE FROM $table WHERE ($table.id IN (" . implode(", ",$newparams). "))");
    foreach ($ids as $n => $val){
        $stmt->bindParam(":id_$n", intval($val), PDO::PARAM_INT);
    }
    $stmt->execute();

... und so weiter

Wenn Sie also ein Array mit gemischten Werten verwenden, benötigen Sie mehr Code, um Ihre Werte zu testen, bevor Sie den Typ param zuweisen:

// inside second foreach..

$valuevar = (is_float($val) ? floatval($val) : is_int($val) ? intval($val) :  is_string($val) ? strval($val) : $val );
$stmt->bindParam(":id_$n", $valuevar, (is_int($val) ? PDO::PARAM_INT :  is_string($val) ? PDO::PARAM_STR : NULL ));

Aber ich habe diesen nicht getestet.

4
alan_mm

Wie ich weiß, gibt es keine Möglichkeit, ein Array in eine PDO-Anweisung zu binden.

Es gibt aber 2 gängige Lösungen:

  1. Verwenden Sie Positionsplatzhalter (?,?,?,?) Oder benannte Platzhalter (: id1,: id2,: id3)

    $ whereIn = implode (',', array_fill (0, count ($ ids), '?'));

  2. Array früher zitieren

    $ whereIn = array_map (array ($ db, 'quote'), $ ids);

Beide Optionen sind gut und sicher. Ich bevorzuge die zweite, weil sie kürzer ist und ich var_dump-Parameter verwenden kann, wenn ich sie brauche. Mit Platzhaltern müssen Sie Werte binden, und am Ende ist Ihr SQL-Code derselbe.

$sql = "SELECT * FROM table WHERE id IN ($whereIn)";

Und das letzte und wichtigste für mich ist, Fehler zu vermeiden "Anzahl der gebundenen Variablen stimmt nicht mit der Anzahl der Token überein"

Doctrine ist ein großartiges Beispiel für die Verwendung von Positionsplatzhaltern, nur weil es die interne Kontrolle über eingehende Parameter hat.

4
Oleg Matei

Wenn die Spalte nur Ganzzahlen enthalten kann, können Sie dies wahrscheinlich ohne Platzhalter tun und die IDs direkt in die Abfrage einfügen. Sie müssen nur alle Werte des Arrays in ganze Zahlen umwandeln. So was:

$listOfIds = implode(',',array_map('intval', $ids));
$stmt = $db->prepare(
    "SELECT *
     FROM table
     WHERE id IN($listOfIds)"
);
$stmt->execute();

Dies sollte nicht für SQL-Injection anfällig sein.

4
Kodos Johnson

hier ist meine Lösung. Ich habe auch die PDO-Klasse erweitert:

class Db extends PDO
{

    /**
     * SELECT ... WHERE fieldName IN (:paramName) workaround
     *
     * @param array  $array
     * @param string $prefix
     *
     * @return string
     */
    public function CreateArrayBindParamNames(array $array, $prefix = 'id_')
    {
        $newparams = [];
        foreach ($array as $n => $val)
        {
            $newparams[] = ":".$prefix.$n;
        }
        return implode(", ", $newparams);
    }

    /**
     * Bind every array element to the proper named parameter
     *
     * @param PDOStatement $stmt
     * @param array        $array
     * @param string       $prefix
     */
    public function BindArrayParam(PDOStatement &$stmt, array $array, $prefix = 'id_')
    {
        foreach($array as $n => $val)
        {
            $val = intval($val);
            $stmt -> bindParam(":".$prefix.$n, $val, PDO::PARAM_INT);
        }
    }
}

Hier ist ein Anwendungsbeispiel für den obigen Code:

$idList = [1, 2, 3, 4];
$stmt = $this -> db -> prepare("
  SELECT
    `Name`
  FROM
    `User`
  WHERE
    (`ID` IN (".$this -> db -> CreateArrayBindParamNames($idList)."))");
$this -> db -> BindArrayParam($stmt, $idList);
$stmt -> execute();
foreach($stmt as $row)
{
    echo $row['Name'];
}

Lass mich wissen was du denkst

3
Lippai Zoltan

Es ist nicht möglich, ein solches Array in PDO zu verwenden.

Sie müssen für jeden Wert eine Zeichenfolge mit einem Parameter erstellen (oder verwenden?), Zum Beispiel:

:an_array_0, :an_array_1, :an_array_2, :an_array_3, :an_array_4, :an_array_5

Hier ist ein Beispiel:

<?php
$ids = array(1,2,3,7,8,9);
$sqlAnArray = join(
    ', ',
    array_map(
        function($index) {
            return ":an_array_$index";
        },
        array_keys($ids)
    )
);
$db = new PDO(
    'mysql:dbname=mydb;Host=localhost',
    'user',
    'passwd'
);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN('.$sqlAnArray.')'
);
foreach ($ids as $index => $id) {
    $stmt->bindValue("an_array_$index", $id);
}

Wenn Sie weiterhin bindParam verwenden möchten, können Sie dies stattdessen tun:

foreach ($ids as $index => $id) {
    $stmt->bindParam("an_array_$index", $ids[$id]);
}

Wenn Sie Platzhalter ? Verwenden möchten, können Sie dies folgendermaßen tun:

<?php
$ids = array(1,2,3,7,8,9);
$sqlAnArray = '?' . str_repeat(', ?', count($ids)-1);
$db = new PDO(
    'mysql:dbname=dbname;Host=localhost',
    'user',
    'passwd'
);
$stmt = $db->prepare(
    'SELECT *
     FROM phone_number_lookup
     WHERE country_code IN('.$sqlAnArray.')'
);
$stmt->execute($ids);

Wenn Sie nicht wissen, ob $ids Leer ist, sollten Sie es testen und diesen Fall entsprechend behandeln (ein leeres Array zurückgeben oder ein Null-Objekt zurückgeben oder eine Ausnahme auslösen, ...).

1

Ich bin etwas weiter gegangen, um der ursprünglichen Frage, Platzhalter zum Binden der Parameter zu verwenden, näher zu kommen.

Diese Antwort muss zwei Schleifen durch das Array machen, um in der Abfrage verwendet zu werden. Damit ist das Problem behoben, dass andere Spaltenplatzhalter für selektivere Abfragen verwendet werden.

//builds placeholders to insert in IN()
foreach($array as $key=>$value) {
    $in_query = $in_query . ' :val_' . $key . ', ';
}

//gets rid of trailing comma and space
$in_query = substr($in_query, 0, -2);

$stmt = $db->prepare(
    "SELECT *
     WHERE id IN($in_query)";

//pind params for your placeholders.
foreach ($array as $key=>$value) {
    $stmt->bindParam(":val_" . $key, $array[$key])
}

$stmt->execute();
0
Joseph_J

sie haben zuerst die Zahl "?" in query und dann durch ein "for" senden Parameter wie folgt:

require 'dbConnect.php';
$db=new dbConnect();
$array=[];
array_Push($array,'value1');
array_Push($array,'value2');
$query="SELECT * FROM sites WHERE kind IN (";

foreach ($array as $field){
    $query.="?,";
}
$query=substr($query,0,strlen($query)-1);
$query.=")";
$tbl=$db->connection->prepare($query);
for($i=1;$i<=count($array);$i++)
    $tbl->bindParam($i,$array[$i-1],PDO::PARAM_STR);
$tbl->execute();
$row=$tbl->fetchAll(PDO::FETCH_OBJ);
var_dump($row);
0
Ali Chegini