2009-10-19 5 views
63

Ich habe ein Problem mit PDO, auf das ich wirklich gerne eine Antwort bekommen würde, nachdem ich es seit einiger Zeit geplagt habe.PDO-Bindungswerte für MySQL IN-Anweisung

Nehmen Sie dieses Beispiel:

I für die Verwendung in einer MySQL-IN-Anweisung zu einer PDO-Anweisung ein Array von IDs bin verbindlich.

Das Array würde sagen: $ Werte = Array (1,2,3,4,5,6,7,8);

Die datenbanksichere Variable wäre $ products = implode (',' $ values);

So Produkte $ wäre dann ein STRING mit einem Wert von: '1,2,3,4,5,6,7,8'

Die Aussage aussehen würde wie: Produkte:

SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products IN (:products) 

natürlich $ Produkte würden auf die Erklärung als gebunden.

Wenn jedoch die Anweisung kompiliert wird und Werte gebunden, wäre es tatsächlich so aussehen:

SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products IN ('1,2,3,4,5,6,7,8') 

Das Problem ist es alles in der IN-Anweisung als einzelne Zeichenfolge ausgeführt wird, da ich habe es als durch Komma getrennte Werte vorbereitet, um an die Anweisung zu binden.

Was ich wirklich brauchen, ist:

SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products IN (1,2,3,4,5,6,7,8) 

Die einzige Art, wie ich dies tatsächlich tun kann, ist, indem die Werte innerhalb der Zeichenfolge selbst platzieren, ohne sie zu binden, aber ich weiß sicher, hat es einen einfacheren Weg sein um dies zu tun.

+0

mögliches Duplikat von [PHP PDO: Kann ich ein Array an eine IN() Bedingung binden?] (Http: // stackoverflow.com/questions/920353/php-pdo-kann-ich-binden-ein-Array-zu-einem-in-Bedingung) – PhoneixS

Antwort

33

Dies ist das gleiche wie in dieser Frage gestellt wurde: Can I bind an array to an IN() condition?

Die Antwort war, dass für eine variable Größe Liste in der in Klausel, werden Sie die Abfrage selbst konstruieren müssen.

jedoch Sie zitiert, durch Kommata getrennte Liste verwenden können find_in_set verwenden, obwohl für große Datenmengen, dies erhebliche Auswirkungen auf die Leistung haben würde, da jeder Wert in der Tabelle auf einen char-Typ gegossen werden muss.

Zum Beispiel:

select users.id 
from users 
join products 
on products.user_id = users.id 
where find_in_set(cast(products.id as char), :products) 

Oder als dritte Option, Sie eine benutzerdefinierte Funktion erstellen können, die die durch Kommata getrennte Liste für Sie spaltet (vgl http://www.slickdev.com/2008/09/15/mysql-query-real-values-from-delimiter-separated-string-ids/). Dies ist wahrscheinlich die beste Option der drei, besonders wenn Sie viele Abfragen haben, die auf in(...) Klauseln beruhen.

+63

Geez, PDO ist so eine ordentliche Bibliothek, die einige der grundlegendsten Funktionalitäten vermisst. Schade, wirklich .. – hd82

+0

Wenn Sie eine kleine Menge von Daten haben, könnten Sie vielleicht mit find_in_set davonkommen (ich habe die Antwort mit einem Beispiel aktualisiert), oder Sie versuchen vielleicht eine benutzerdefinierte Funktion, um das Komma zu analysieren. getrennte Liste für Sie. (Sorry, um Ideen hinzuzufügen, ha ha; nachdem ich den Link zu der anderen Frage gepostet habe, dachte ich: "Es muss einen Weg geben, das zu tun ...") –

+1

Danke für das FIND_IN_SET-Heads-Up. Ich habe meinen Tag wirklich gerettet. Aber ich fand, dass es funktioniert, ohne ein CAST zu machen, also find_in_set (products.id,: products) ist genug. – Andy

7

Die besten vorbereitete Anweisung Sie wahrscheinlich mit in einer Situation wie dieser kommen könnte, ist das so etwas wie folgt vor:

SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products IN (?,?,?,?,?,?,?,?)

würden Sie dann die Schleife Ihre Werte durch und binden sie in die vorbereitete Erklärung dafür sorgen, dass es sind die gleiche Anzahl von Fragezeichen wie Werte, die Sie binden.

+6

Logische Lösung, wenn Sie wissen, wie viele Variablen zu binden, aber es wird ein bisschen chaotisch, wenn Sie eine unbestimmte haben Anzahl der Parameter. – hd82

+0

@ hd82: Nun, dann musst du sprintf intelligent kombinieren und zählen. – Boldewyn

1

Sie können dies sehr einfach tun. Wenn Sie ein Array von Werten für die IN() Anweisung Beispiel:

$test = array(1,2,3); 

können Sie einfach tun

$test = array(1,2,3); 
$values = count($test); 
$criteria = sprintf("?%s", str_repeat(",?", ($values ? $values-1 : 0))); 
//Returns ?,?,? 
$sql = sprintf("DELETE FROM table where column NOT IN(%s)", $criteria); 
//Returns DELETE FROM table where column NOT IN(?,?,?) 
$pdo->sth = prepare($sql); 
$pdo->sth->execute($test); 
+2

Dies wurde bereits in Asaphs Antwort oben erwähnt und funktioniert nicht, wenn Sie benannte Parameter verwenden, da Sie sie nicht mischen können. – cincodenada

26

Ein guter Weg, um diese Situation zu handhaben ist str_pad zu verwenden, um ein ? zu platzieren für jeden Wert in der SQL-Abfrage. Dann können Sie das Array von Werten (in Ihrem Fall $values) als Argument übergeben auszuführen:

$sql = ' 
SELECT users.id 
FROM users 
JOIN products 
ON products.user_id = users.id 
WHERE products.id IN ('.str_pad('',count($values)*2-1,'?,').') 
'; 

$sth = $dbh->prepare($sql); 
$sth->execute($values); 
$user_ids = $sth->fetchAll(); 

diese Weise können Sie mehr Nutzen aus mit Prepared Statements bekommen, anstatt die Werte direkt in die SQL einfügen.

PS - Die Ergebnisse geben doppelte Benutzer-IDs zurück, wenn die Produkte mit den angegebenen IDs Benutzer-IDs teilen. Wenn Sie nur eindeutige Benutzer-IDs möchten, schlage ich vor, die erste Zeile der Abfrage in SELECT DISTINCT users.id

+0

Mann, das ist Genie! :-D –

+0

Warum muss '' 2-1' anstatt nur '-1' auf' count() 'gesetzt werden? – Nalum

+1

@Nalum: Da der zweite Parameter von str_pad die "Pad-Länge" ist, die in Zeichen ist. Da Sie für jeden Wert ein Fragezeichen und ein Komma dazwischen haben möchten, ist das "count ($ values) * 2-1". Du könntest alternativ etwas wie 'str_repeat ('?,', Count ($ values) -1) verwenden. '?'' Wenn du willst. – ghbarratt

1

zu ändern. Wenn der Ausdruck auf Benutzereingaben basiert, ohne die Werte mit bindValue() zu verknüpfen, ist experimentelles SQL möglicherweise keine gute Wahl. Aber, können Sie es sicher machen, indem Sie die Syntax der Eingabe mit MySQL REGEXP überprüfen.

Zum Beispiel:

SELECT * 
FROM table 
WHERE id IN (3,156) 
AND '3,156' REGEXP '^([[:digit:]]+,?)+$' 
0

Hier ist ein Beispiel für einen Einsatz in eine unbekannte Anzahl von Aufzeichnungs Spalten auf Werte verbindlich.

public function insert($table, array $data) 
{ 
    $sql = "INSERT INTO $table (" . join(',', array_keys($data)) . ') VALUES (' 
     . str_repeat('?,', count($data) - 1). '?)'; 

    if($sth = $this->db->prepare($sql)) 
    { 
     $sth->execute(array_values($data)); 
     return $this->db->lastInsertId(); 
    } 
} 

$id = $db->insert('user', array('name' => 'Bob', 'email' => '[email protected]')); 
-1

Bitte versuchen Sie es wie folgt aus:?

WHERE products IN(REPLACE(:products, '\'', '')) 

Grüße

+3

Diese Frage wurde vor 5 Jahren gestellt und beantwortet, und diese Antwort löst das Problem nicht. – OGHaza

4

Sie gleiche Anzahl von s in IN als die Anzahl der Werte in Ihrer $ Werte zur Verfügung stellen müssen Array

Dies kann einfach durchgeführt werden b y die Schaffung einer Reihe von? s als

$in = join(',', array_fill(0, count($values), '?')); 

und dieses $ in Array verwenden in Ihrem In-Klausel

Dies wird Ihnen dynamisch gemacht Array Schneider bieten von ? s wie pro Ihre changiing $ Werte Array