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

Lesezeit: 10 Minuten

Kann ich ein Array an eine IN Bedingung binden
Andru

Ich bin neugierig zu wissen, ob es möglich ist, ein Array von Werten mit PDO an einen Platzhalter zu binden. Der Anwendungsfall hier versucht, ein Array von Werten zur Verwendung mit an zu übergeben IN() Zustand.

Ich würde gerne 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();
?>

Und lassen Sie PDO alle Werte im Array binden und zitieren.

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

Was sicherlich den Job macht, aber ich frage mich nur, ob es eine eingebaute Lösung gibt, die ich vermisse?

  • Eine vollständige Anleitung zum Binden eines Arrays an eine IN()-Bedingungeinschließlich des Falls, wenn Sie andere Platzhalter in der Abfrage haben

    – Ihr gesunder Menschenverstand

    18. April 2017 um 14:10 Uhr


  • Die Frage wurde als Duplikat dieser Frage geschlossen. Ich habe das Duplikat-Flag umgekehrt, weil diese Frage 4 Jahre älter ist, 4-mal so viele Aufrufe, 3-mal so viele Antworten und 12-mal so viele Punkte hat. Es ist eindeutig das übergeordnete Ziel.

    – Mike32

    7. März 2020 um 1:58 Uhr


  • Wer sich das 2020 anschaut: Man könnte es versuchen github.com/morris/dop dafür.

    – Morris4

    9. Mai 2020 um 16:55 Uhr

Kann ich ein Array an eine IN Bedingung binden
stefs

Sie müssen die Abfragezeichenfolge konstruieren.

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

Sowohl Chris (Kommentare) als auch Someoneisintrouble schlugen vor, 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 überflüssig sein, so die foreach Schleife und die $stmt->execute könnte einfach ersetzt werden durch …

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

  • Das ist eine interessante Lösung, und obwohl ich es vorziehe, über die IDs zu iterieren und PDO::quote() aufzurufen, denke ich, dass der Index des ‘?’ Platzhalter werden durcheinander gebracht, wenn zuerst andere Platzhalter an anderer Stelle in der Abfrage vorkommen, richtig?

    – Andru

    28. Mai 2009 um 12:49 Uhr

  • ja das wäre ein problem. aber in diesem Fall könnten Sie benannte Parameter anstelle von ? erstellen.

    – Stefs

    28. Mai 2009 um 13:31 Uhr

  • Alte Frage, aber erwähnenswert, glaube ich, ist das die $foreach und bindValue() ist nicht erforderlich – führen Sie einfach mit dem Array aus. Z.B: $stmt->execute($ids);

    – Chris

    30. Mai 2012 um 3:47 Uhr

  • Das Generieren der Platzhalter sollte so erfolgen str_repeat('?,', count($array) - 1). '?';

    – Xeoncross

    24. November 2012 um 20:13 Uhr

  • Nur ein Tipp für diejenigen, die es nicht wissen, Sie können benannte und unbenannte Parameter nicht mischen. Wenn Sie benannte Parameter in Ihrer Abfrage verwenden, tauschen Sie sie daher gegen ? aus und erweitern Sie dann Ihren bindValue-Index-Offset, um die Position der IN ? mit der Stelle abzugleichen, an der sie sich relativ zu Ihrem anderen ? befinden. Parameter.

    – justinl

    22. Oktober 2014 um 1:25 Uhr

Kann ich ein Array an eine IN Bedingung binden
uɥƃnɐʌuop

Für was 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);

  • Ausgezeichnet, ich hatte nicht daran gedacht, das input_parameters-Argument auf diese Weise zu verwenden. Für diejenigen, deren Abfragen mehr Parameter als die IN-Liste haben, können Sie array_unshift und array_push verwenden, um die erforderlichen Argumente am Anfang und am Ende des Arrays hinzuzufügen. Außerdem bevorzuge ich $input_list = substr(str_repeat(',?', count($ids)), 1);

    – Orca

    31. Mai 2012 um 2:34 Uhr


  • Du könntest es auch versuchen str_repeat('?,', count($ids) - 1) . '?'. Ein Funktionsaufruf weniger.

    – Orca

    1. Juni 2012 um 23:30 Uhr

  • @erfling, das ist eine vorbereitete Aussage, wo soll die Spritze herkommen? Ich werde mehr als glücklich sein, Korrekturen vorzunehmen, wenn Sie das mit einem tatsächlichen Beweis dafür untermauern können.

    – uɥƃnɐʌuop

    30. November 2015 um 18:06 Uhr

  • @erfling, ja, das ist richtig, und das Binden der Params ist genau das, was wir in diesem Beispiel tun, indem wir senden execute ein Array von IDs

    – uɥƃnɐʌuop

    1. Dezember 2015 um 18:18 Uhr

  • Ach ja. Irgendwie übersehen, dass Sie das Array passiert haben. Dies scheint in der Tat sicher und eine gute Antwort zu sein. Entschuldigen Sie.

    – Erfling

    1. Dezember 2015 um 23:28 Uhr

1647090249 728 Kann ich ein Array an eine IN Bedingung binden
Tim Tonkonogow

Ist es so wichtig zu verwenden IN Aussage? 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 mit Kommas implodiert sind, wie dieses hier

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

und fertig.

UPD: Wie einige Leute in Kommentaren zu dieser Antwort darauf hingewiesen haben, gibt es einige Probleme, die ausdrücklich erwähnt werden sollten.

  1. FIND_IN_SET verwendet keinen Index in einer Tabelle und ist noch nicht implementiert – siehe diesen Eintrag im MYSQL-Bugtracker. Danke 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 danach richtig zu analysieren implode da Sie das Komma-Symbol als Trennzeichen verwenden. Danke an @VaL für den Hinweis.

Kurz gesagt, 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.

  • IN() kann einen Index verwenden und zählt als Bereichsscan. FIND_IN_SET() kann keinen Index verwenden.

    – Bill Karwin

    3. Oktober 2013 um 0:59 Uhr

  • Das ist ein Argument. Ich wusste das nicht. Aber wie auch immer, es gibt keine Anforderungen an die Leistung in der Frage. Für nicht so große Tabellen ist es viel besser und sauberer als eine separate Klasse zum Generieren von Abfragen mit einer unterschiedlichen Anzahl von Platzhaltern.

    – Tim Tonkonogow

    8. Oktober 2013 um 0:03 Uhr

  • Ja, aber wer hat heutzutage einen nicht so großen Tisch? 😉

    – Bill Karwin

    8. Oktober 2013 um 0:17 Uhr

  • Ein weiteres Problem bei diesem Ansatz ist, was passiert, wenn eine Zeichenfolge mit Komma darin enthalten ist? Zum Beispiel... FIND_IN_SET(description,'simple,search') wird funktionieren, aber FIND_IN_SET(description,'first value,text, with coma inside') wird versagen. Die Funktion wird also suchen "first value", "text", "with coma inside" statt erwünscht "first value", "text, with coma inside"

    – Val

    17. Februar 2015 um 15:41 Uhr


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

  • Dies ist eine viel bessere Lösung, da sie nicht gegen die Regel zum Binden von Parametern verstößt. Dies ist viel sicherer als Inline-SQL, wie von einigen anderen hier vorgeschlagen.

    – Dimitar Darazhanski

    6. Dezember 2015 um 21:04 Uhr

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

  • Das funktioniert nicht: ERROR: operator does not exist: integer = text. Zumindest müssen Sie explizites Casting hinzufügen.

    – Collimarco

    15. Dezember 2013 um 17:39 Uhr

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. Dadurch können Probleme bei der Schlüsselbestellung behoben werden.

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

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

Und das scheiterte.

  • Das funktioniert nicht: ERROR: operator does not exist: integer = text. Zumindest müssen Sie explizites Casting hinzufügen.

    – Collimarco

    15. Dezember 2013 um 17:39 Uhr

Ich habe PDO erweitert, um etwas Ähnliches zu tun, was stefs vorschlägt, 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;
    }
}

Sie können es wie folgt verwenden:

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

  • Ich habe den ersten Teil von Marks Kommentar angesprochen, aber wie er betonte, ist es immer noch nicht sicher, ob ein Token gefällt :array ist in einer Zeichenfolge in der Abfrage.

    – Chris

    25. Februar 2013 um 21:24 Uhr

  • Ein Hinweis für alle zukünftigen Leser: Diese Lösung sollte niemals verwendet werden. Asserts sind nicht für den Produktionscode vorgesehen

    – Ihr gesunder Menschenverstand

    30. März 2013 um 1:56 Uhr


  • YCS: danke für das Feedback, interessiere mich für deine Meinung zum Ansatz außerhalb der Eignung von Asserts.

    – Chris

    30. März 2013 um 13:55 Uhr

  • Die Idee ist ziemlich dieselbe, nur ohne Asserts und auf eine direktere und explizitere Weise – nicht als Ausnahme für nur einen einzelnen Fall, sondern als allgemeine Methode zum Erstellen jeder Abfrage. Jeder Platzhalter ist mit seinem Typ gekennzeichnet. Es macht Vermutungen (wie if (is_array($data)) one) unnötig, macht die Datenverarbeitung jedoch viel genauer.

    – Ihr gesunder Menschenverstand

    30. März 2013 um 14:31 Uhr

  • An alle Leute, die die Kommentare lesen: Das von @Your Common Sense erwähnte Problem wurde in Revision 4 behoben.

    – Benutzer2428118

    5. Dezember 2014 um 13:37 Uhr

993680cookie-checkKann ich ein Array an eine IN()-Bedingung binden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy