Übergeben eines Arrays an eine Abfrage mithilfe einer WHERE-Klausel

314

Bei einem Array von IDs $galleries = array(1,2,5)möchte ich eine SQL-Abfrage haben, die die Werte des Arrays in ihrer WHERE-Klausel verwendet, wie:

SELECT *
FROM galleries
WHERE id = /* values of array $galleries... eg. (1 || 2 || 5) */

Wie kann ich diese Abfragezeichenfolge für MySQL generieren?

Braiam
quelle
5
@trante dieser ist der älteste (2009).
Fabián
Gibt es eine ähnliche Lösung für ein Problem wie SELECT * FROM TABLE WHERE NAME LIKE ('ABC%' oder 'ABD%' OR ..)
Eugine Joseph

Antworten:

332

IN ACHT NEHMEN! Diese Antwort enthält eine schwerwiegende Sicherheitsanfälligkeit bezüglich SQL-Injection . Verwenden Sie die hier dargestellten Codebeispiele NICHT, ohne sicherzustellen, dass externe Eingaben bereinigt sind.

$ids = join("','",$galleries);   
$sql = "SELECT * FROM galleries WHERE id IN ('$ids')";
Flavius ​​Stef
quelle
7
Die Bezeichner sind immer noch eine Liste in Anführungszeichen, daher wird sie beispielsweise als "WHERE id IN ('1,2,3,4')" ausgegeben. Sie müssen jeden Bezeichner separat in Anführungszeichen setzen oder die Anführungszeichen in den Klammern streichen.
Rob
22
Ich füge nur die Warnung hinzu, $galleriesdie vor dieser Anweisung validiert werden soll! Vorbereitete Anweisungen können keine Arrays AFAIK verarbeiten. Wenn Sie also an gebundene Variablen gewöhnt sind, können Sie hier problemlos die SQL-Injection ermöglichen.
Leemes
3
Kann jemand für PHP-Neulinge wie mich erklären oder auf eine Ressource verweisen, um zu erklären, warum dies anfällig für Injektionen ist und wie dies richtig gemacht werden sollte, um dies zu verhindern? Was ist immer noch gefährlich, wenn die Liste der IDs unmittelbar vor der Ausführung dieser nächsten Abfrage aus einer Abfrage generiert wird?
Ministe2003
3
@ ministe2003 Stellen Sie sich vor, Sie $gallerieshätten den folgenden Wert : array('1); SELECT password FROM users;/*'). Wenn Sie das nicht bereinigen, würde die Abfrage lauten SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Ändern Sie die Tabellen- und Spaltennamen in etwas, das Sie in Ihrer Datenbank haben, und versuchen Sie diese Abfrage. Überprüfen Sie die Ergebnisse. Als Ergebnis finden Sie eine Liste mit Passwörtern und keine Liste mit Galerien. Abhängig davon, wie die Daten ausgegeben werden oder was das Skript mit einem Array unerwarteter Daten macht, wird dies möglicherweise in die öffentliche Ansicht ausgegeben ... autsch.
Chris Baker
18
Für die gestellte Frage ist es eine vollkommen gültige und sichere Antwort. Wer sich beschwert, ist nicht sicher - wie wäre es mit einem Deal, bei dem ich diesen bestimmten Code mit dem $galleriesin der Frage angegebenen Code eingerichtet habe und Sie ihn mit der oben genannten "SQL Injection-Schwachstelle" ausnutzen. Wenn Sie nicht können, zahlen Sie mir 200 USD. Wie ist es damit?
Zerkms
307

Verwenden von PDO: [1]

$in = join(',', array_fill(0, count($ids), '?'));
$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;
$statement = $pdo->prepare($select);
$statement->execute($ids);

Verwenden von MySQLi [2]

$in = join(',', array_fill(0, count($ids), '?'));
$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;
$statement = $mysqli->prepare($select);
$statement->bind_param(str_repeat('i', count($ids)), ...$ids);
$statement->execute();
$result = $statement->get_result();

Erläuterung:

Verwenden Sie den SQL- IN()Operator, um zu überprüfen, ob in einer bestimmten Liste ein Wert vorhanden ist.

Im Allgemeinen sieht es so aus:

expr IN (value,...)

Wir können einen Ausdruck erstellen, der innerhalb ()unseres Arrays platziert wird. Beachten Sie, dass mindestens ein Wert in der Klammer stehen muss. Andernfalls gibt MySQL einen Fehler zurück. Dies entspricht der Sicherstellung, dass unser Eingabearray mindestens einen Wert hat. Um SQL-Injection-Angriffe zu vermeiden, generieren Sie zunächst ?für jedes Eingabeelement ein, um eine parametrisierte Abfrage zu erstellen. Hier gehe ich davon aus, dass das Array mit Ihren IDs heißt $ids:

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

$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;

Bei einem Eingabearray von drei Elementen $selectsieht es folgendermaßen aus:

SELECT *
FROM galleries
WHERE id IN (?, ?, ?)

Beachten Sie erneut, dass es ?für jedes Element im Eingabearray ein gibt. Dann verwenden wir PDO oder MySQLi, um die Abfrage wie oben beschrieben vorzubereiten und auszuführen.

Verwenden des IN()Operators mit Zeichenfolgen

Aufgrund der gebundenen Parameter ist es einfach, zwischen Zeichenfolgen und Ganzzahlen zu wechseln. Für PDO ist keine Änderung erforderlich. für MySQLi ändern Sie str_repeat('i',zu, str_repeat('s',wenn Sie Zeichenfolgen überprüfen müssen.

[1]: Ich habe einige Fehlerprüfungen auf Kürze weggelassen. Sie müssen für jede Datenbankmethode nach den üblichen Fehlern suchen (oder Ihren DB-Treiber so einstellen, dass Ausnahmen ausgelöst werden).

[2]: Benötigt PHP 5.6 oder höher. Wieder habe ich einige Fehlerprüfungen auf Kürze weggelassen.

Levi Morrison
quelle
7
Was macht ... $ ids? Ich erhalte "Syntaxfehler, unerwartet '.'".
Marcel
Ich sehe sie, ich benutze MySQLi und ich habe PHP 5.6
Marcel
1
Wenn Sie sich beziehen $statement->bind_param(str_repeat('i', count($ids)), ...$ids);dann die ...ist die ID Erweiterung ist aus einem Array in mehrere Parameter. Wenn Sie sich darauf beziehen expr IN (value,...), bedeutet dies nur, dass es mehr Werte geben kann, z WHERE id IN (1, 3, 4). Es muss nur mindestens einen geben.
Levi Morrison
1
Ich war verwirrt, was <<< war, aber ich fand eine Referenz: php.net/manual/en/…
Tsangares
1
Hier ist auch die Referenz für ...: wiki.php.net/rfc/argument_unpacking
Tsangares
58

Ints:

$query = "SELECT * FROM `$table` WHERE `$column` IN(".implode(',',$array).")";

Zeichenfolgen:

$query = "SELECT * FROM `$table` WHERE `$column` IN('".implode("','",$array)."')";
user542568
quelle
1
Warum '\' ?? Bitte sag es mir
zloctb
es funktioniert für mich .....
prabhakaran7
29

Vorausgesetzt, Sie bereinigen Ihre Eingaben zuvor ordnungsgemäß ...

$matches = implode(',', $galleries);

Dann passen Sie einfach Ihre Abfrage an:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Geben Sie die Werte entsprechend Ihrem Datensatz entsprechend an.

AvatarKava
quelle
Ich habe versucht, was Sie vorschlagen, aber es hat nur den ersten Schlüsselwert abgerufen. Ich weiß, dass es keinen Sinn ergibt, aber wenn ich es am Beispiel von user542568 mache, funktioniert das verdammte Ding.
Samuel Ramzan
12

Verwenden:

select id from galleries where id in (1, 2, 5);

Eine einfache for eachSchleife wird funktionieren.

Der Weg von Flavius ​​/ AvatarKava ist besser, aber stellen Sie sicher, dass keiner der Array-Werte Kommas enthält.

Matthew Flaschen
quelle
9

Als Antwort von Flavius ​​Stef können Sie intval()sicherstellen, dass alle idint-Werte sind:

$ids = join(',', array_map('intval', $galleries));  
$sql = "SELECT * FROM galleries WHERE id IN ($ids)";
Van-Duyet Le
quelle
7

Für MySQLi mit Escape-Funktion:

$ids = array_map(function($a) use($mysqli) { 
    return is_string($a) ? "'".$mysqli->real_escape_string($a)."'" : $a;
  }, $ids);
$ids = join(',', $ids);  
$result = $mysqli->query("SELECT * FROM galleries WHERE id IN ($ids)");

Für gU mit vorbereiteter Erklärung:

$qmarks = implode(',', array_fill(0, count($ids), '?'));
$sth = $dbh->prepare("SELECT * FROM galleries WHERE id IN ($qmarks)");
$sth->execute($ids);
artoodetoo
quelle
Dies ist nett, kurz und vermeidet die Sicherheitsanfälligkeit beim Einfügen von Code! +1
Stephan Richter
MySQLi hat auch Anweisungen vorbereitet. Entgehen Sie nicht Ihrer Eingabe, dies ist möglicherweise immer noch anfällig für SQL-Injection.
Dharman
6

Wir sollten uns um SQL-Injection- Schwachstellen und einen leeren Zustand kümmern . Ich werde beide wie unten behandeln.

Für ein reines numerisches Array verwenden , um die geeignete Typkonvertierung viz intvaloder floatvaloder doublevalüber jedes Element. Für Zeichenfolgentypen, mysqli_real_escape_string()die auf Wunsch auch auf numerische Werte angewendet werden können. MySQL erlaubt sowohl Zahlen als auch Datumsvarianten als Zeichenfolge .

Erstellen Sie eine Funktion ähnlich der folgenden: Um die Werte vor dem Übergeben an die Abfrage angemessen zu umgehen:

function escape($string)
{
    // Assuming $db is a link identifier returned by mysqli_connect() or mysqli_init()
    return mysqli_real_escape_string($db, $string);
}

Eine solche Funktion steht Ihnen höchstwahrscheinlich bereits in Ihrer Anwendung zur Verfügung, oder Sie haben bereits eine erstellt.

Bereinigen Sie das String-Array wie folgt:

$values = array_map('escape', $gallaries);

Ein numerisches Array kann mit intvaloder floatvaloder doublevalstattdessen nach Bedarf bereinigt werden :

$values = array_map('intval', $gallaries);

Erstellen Sie dann schließlich die Abfragebedingung

$where  = count($values) ? "`id` = '" . implode("' OR `id` = '", $values) . "'" : 0;

oder

$where  = count($values) ? "`id` IN ('" . implode("', '", $values) . "')" : 0;

Da das Array manchmal auch leer sein kann, $galleries = array();sollten wir daher beachten, dass IN ()keine leere Liste zulässig ist. Man kann ORstattdessen auch verwenden , aber das Problem bleibt bestehen. Die obige Prüfung count($values)soll also das Gleiche sicherstellen.

Und fügen Sie es der endgültigen Abfrage hinzu:

$query  = 'SELECT * FROM `galleries` WHERE ' . $where;

TIPP : Wenn Sie bei einem leeren Array alle Datensätze anzeigen möchten (keine Filterung), anstatt alle Zeilen auszublenden, ersetzen Sie einfach 0 durch 1 im falschen Teil des Ternärs.

Izhar Aazmi
quelle
Um meine Lösung zu einem $query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Einzeiler
5

Sicherer.

$galleries = array(1,2,5);
array_walk($galleries , 'intval');
$ids = implode(',', $galleries);
$sql = "SELECT * FROM galleries WHERE id IN ($ids)";
Filipe
quelle
5

Die SafeMySQL- Bibliothek von Col. Shrapnel für PHP bietet typgesteuerte Platzhalter in den parametrisierten Abfragen sowie einige praktische Platzhalter für die Arbeit mit Arrays. Der ?aPlatzhalter erweitert ein Array zu einer durch Kommas getrennten Liste von maskierten Zeichenfolgen *.

Zum Beispiel:

$someArray = [1, 2, 5];
$galleries = $db->getAll("SELECT * FROM galleries WHERE id IN (?a)", $someArray);

* Da MySQL einen automatischen Typzwang ausführt, spielt es keine Rolle, dass SafeMySQL die oben genannten IDs in Zeichenfolgen konvertiert - Sie erhalten immer noch das richtige Ergebnis.

Mark Amery
quelle
4

Wir können diese "WHERE id IN" -Klausel verwenden, wenn wir das Eingabearray richtig filtern. Etwas wie das:

$galleries = array();

foreach ($_REQUEST['gallery_id'] as $key => $val) {
    $galleries[$key] = filter_var($val, FILTER_SANITIZE_NUMBER_INT);
}

Wie das folgende Beispiel:Geben Sie hier die Bildbeschreibung ein

$galleryIds = implode(',', $galleries);

Dh jetzt solltest du sicher benutzen $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";

Supratim Roy
quelle
@ Levi-Morrison hat eine viel bessere Lösung dafür gepostet.
Supratim Roy
4

Sie können Tisch texts (T_ID (int), T_TEXT (text))und Tisch habentest (id (int), var (varchar(255)))

Im insert into test values (1, '1,2,3') ;Folgenden werden Zeilen aus Tabellentexten ausgegeben, in denen T_ID IN (1,2,3):

SELECT * FROM `texts` WHERE (SELECT FIND_IN_SET( T_ID, ( SELECT var FROM test WHERE id =1 ) ) AS tm) >0

Auf diese Weise können Sie eine einfache n2m-Datenbankbeziehung ohne zusätzliche Tabelle verwalten und nur SQL verwenden, ohne PHP oder eine andere Programmiersprache verwenden zu müssen.

SERJOU
quelle
3

Mehr ein Beispiel:

$galleryIds = [1, '2', 'Vitruvian Man'];
$ids = array_filter($galleryIds, function($n){return (is_numeric($n));});
$ids = implode(', ', $ids);

$sql = "SELECT * FROM galleries WHERE id IN ({$ids})";
// output: 'SELECT * FROM galleries WHERE id IN (1, 2)'

$statement = $pdo->prepare($sql);
$statement->execute();
Ricardo Canelas
quelle
2

Neben der Verwendung der IN-Abfrage haben Sie zwei Möglichkeiten, da bei einer IN-Abfrage das Risiko einer SQL-Injection-Sicherheitsanfälligkeit besteht. Sie können eine Schleife verwenden , um genau die gewünschten Daten abzurufen, oder Sie können die Abfrage mit ODER- Groß- / Kleinschreibung verwenden

1. SELECT *
      FROM galleries WHERE id=1 or id=2 or id=5;


2. $ids = array(1, 2, 5);
   foreach ($ids as $id) {
      $data[] = SELECT *
                    FROM galleries WHERE id= $id;
   }
Gaurav Singh
quelle
2

Sicherer Weg ohne gU:

$ids = array_filter(array_unique(array_map('intval', (array)$ids)));

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$idsGussvariable$ids Array
  • array_map Transformieren Sie alle Array-Werte in Ganzzahlen
  • array_unique Wiederholte Werte entfernen
  • array_filter Nullwerte entfernen
  • implode Verbinden Sie alle Werte mit der IN-Auswahl
Lito
quelle
1

Da sich die ursprüngliche Frage auf ein Array von Zahlen bezieht und ich ein Array von Zeichenfolgen verwende, konnte ich die angegebenen Beispiele nicht zum Laufen bringen.

Ich fand heraus, dass jede Zeichenfolge in einfache Anführungszeichen eingeschlossen werden musste, um mit der IN()Funktion zu arbeiten.

Hier ist meine Lösung

foreach($status as $status_a) {
        $status_sql[] = '\''.$status_a.'\'';
    }
    $status = implode(',',$status_sql);

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

Wie Sie sehen können, umschließt die erste Funktion jede Arrayvariable single quotes (\')und implodiert dann das Array.

HINWEIS: $statusDie SQL-Anweisung enthält keine einfachen Anführungszeichen.

Es gibt wahrscheinlich eine schönere Möglichkeit, die Anführungszeichen hinzuzufügen, aber dies funktioniert.

RJaus
quelle
Oder$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo
1
Dies ist anfällig für Injektionen.
Mark Amery
Wo entkommt Saiten? Zum Beispiel 'innerhalb der Zeichenfolge? SQL Injection anfällig. Verwenden Sie PDO :: quote oder mysqli_real_escape_string.
18.
1

Unten ist die Methode aufgeführt, die ich verwendet habe, um PDO mit benannten Platzhaltern für andere Daten zu verwenden. Um die SQL-Injection zu überwinden, filtere ich das Array so, dass nur die Werte akzeptiert werden, die Ganzzahlen sind, und lehne alle anderen ab.

$owner_id = 123;
$galleries = array(1,2,5,'abc');

$good_galleries = array_filter($chapter_arr, 'is_numeric');

$sql = "SELECT * FROM galleries WHERE owner=:OWNER_ID AND id IN ($good_galleries)";
$stmt = $dbh->prepare($sql);
$stmt->execute(array(
    "OWNER_ID" => $owner_id,
));

$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
kojow7
quelle
-3

Grundlegende Methoden zum Verhindern der SQL-Injection sind:

  • Verwenden Sie vorbereitete Anweisungen und parametrisierte Abfragen
  • Escapezeichen für die Sonderzeichen in Ihrer unsicheren Variablen

Die Verwendung von vorbereiteten Anweisungen und parametrisierten Abfragen wird als die bessere Vorgehensweise angesehen. Wenn Sie jedoch die Methode der Escapezeichen wählen, können Sie mein Beispiel unten ausprobieren.

Sie können die Abfragen generieren, indem Sie array_mapjedem der Elemente in $galleries:

$galleries = array(1,2,5);

$galleries_str = implode(', ',
                     array_map(function(&$item){
                                   return "'" .mysql_real_escape_string($item) . "'";
                               }, $galleries));

$sql = "SELECT * FROM gallery WHERE id IN (" . $galleries_str . ");";

Die generierte $ sql-Variable lautet:

SELECT * FROM gallery WHERE id IN ('1', '2', '5');

Hinweis: mysql_real_escape_string , wie in der Dokumentation hier beschrieben , war in PHP 5.5.0 veraltet und wurde in PHP 7.0.0 entfernt. Stattdessen sollte die Erweiterung MySQLi oder PDO_MySQL verwendet werden. Weitere Informationen finden Sie unter MySQL: Auswahl eines API-Handbuchs und zugehöriger FAQ. Alternativen zu dieser Funktion sind:

  • mysqli_real_escape_string ()

  • PDO :: quote ()

David
quelle
4
Dies ist nicht nur nichts Neues im Vergleich zu anderen Antworten, sondern auch anfällig für Injektionen, obwohl in der akzeptierten Antwort seit Jahren Warnungen bezüglich der SQL-Injektion veröffentlicht wurden.
Mark Amery