Passare un array a una query utilizzando una clausola WHERE


314

Dato un array di ID $galleries = array(1,2,5)voglio avere una query SQL che utilizza i valori dell'array nella sua clausola WHERE come:

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

Come posso generare questa stringa di query da utilizzare con MySQL?



5
@trante questo è il più vecchio (2009).
Fabián,

Esiste una soluzione analoga per problemi come SELEZIONA * DA TABELLA DOVE MI PIACE IL NOME ("ABC%" o "ABD%" O ..)
Eugine Joseph,

Risposte:


332

ATTENZIONE! Questa risposta contiene una grave vulnerabilità nell'iniezione SQL . NON utilizzare gli esempi di codice presentati qui, senza accertarsi che qualsiasi input esterno sia disinfettato.

$ids = join("','",$galleries);   
$sql = "SELECT * FROM galleries WHERE id IN ('$ids')";

7
Gli identificatori sono ancora un elenco tra virgolette, quindi viene visualizzato come "WHERE id IN ('1,2,3,4')", ad esempio. Devi citare ogni identificatore separatamente, altrimenti abbandonare le virgolette tra parentesi.
Rob,

22
Aggiungo solo l'avvertimento che $galleriesdovrebbe essere inserito validato prima di questa affermazione! Le istruzioni preparate non possono gestire le matrici AFAIK, quindi se si è abituati a associare variabili, è possibile rendere facilmente possibile l'iniezione di SQL qui.
saluta il

3
per i neofiti di PHP come me, qualcuno può spiegare o indicarmi una risorsa per spiegare, perché questo è soggetto a iniezione e come dovrebbe essere fatto correttamente per impedirlo? Cosa succede se l'elenco di ID viene generato da una query immediatamente prima dell'esecuzione di questa query successiva, è ancora pericoloso?
ministe2003,

3
@ ministe2003 Immaginate se $galleriesha avuto il seguente valore: array('1); SELECT password FROM users;/*'). Se non lo si disinfetta, la query verrà letta SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Cambia i nomi di tabella e colonna in qualcosa che hai nel tuo database e prova quella query, controlla i risultati. Troverai un elenco di password come risultato, anziché un elenco di gallerie. A seconda di come vengono emessi i dati o di cosa fa lo script con una matrice di dati imprevisti, questo potrebbe far sì che l'output venga visualizzato in pubblico ...
Chris Baker,

18
Per la domanda posta è una risposta perfettamente valida e sicura. Chiunque si lamenta non è sicuro - che ne dici di un accordo che ho impostato questo particolare codice con il $galleriescome previsto nella domanda e lo sfrutti usando la summenzionata "vulnerabilità dell'iniezione sql". Se non puoi, mi paghi 200 USD. Che ne dici di quello?
zerkms,

307

Utilizzo di DOP: [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);

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

Spiegazione:

Utilizzare l' IN()operatore SQL per verificare se esiste un valore in un determinato elenco.

In generale sembra così:

expr IN (value,...)

Possiamo creare un'espressione da inserire all'interno del ()nostro array. Si noti che all'interno della parentesi deve esserci almeno un valore o MySQL restituirà un errore; questo equivale a garantire che il nostro array di input abbia almeno un valore. Per evitare attacchi di iniezione SQL, innanzitutto generare un ?per ogni elemento di input per creare una query con parametri. Qui presumo che l'array contenente i tuoi ID sia chiamato $ids:

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

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

Dato un array di input di tre elementi $selectsarà simile a:

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

Notare ancora che c'è un ?per ogni elemento nella matrice di input. Quindi useremo PDO o MySQLi per preparare ed eseguire la query come indicato sopra.

Utilizzo IN()dell'operatore con stringhe

È facile cambiare tra stringhe e numeri interi a causa dei parametri associati. Per DOP non è richiesto alcun cambiamento; per MySQLi cambiare str_repeat('i',in str_repeat('s',se è necessario controllare le stringhe.

[1]: Ho omesso alcuni errori nel controllo della brevità. È necessario verificare i soliti errori per ciascun metodo di database (o impostare il driver DB per generare eccezioni).

[2]: richiede PHP 5.6 o versioni successive. Ancora una volta ho omesso qualche errore verificando la brevità.


7
Cosa fa ... $ ids? Ottengo "errore di sintassi, imprevisto '.'".
Marcel,

Li vedo, sto usando MySQLi e ho php 5.6
Marcel,

1
Se ti riferisci a, $statement->bind_param(str_repeat('i', count($ids)), ...$ids);allora ...sta espandendo gli ID da un array in più parametri. Se ti riferisci a expr IN (value,...)ciò significa che possono esserci più valori, ad es WHERE id IN (1, 3, 4). Deve essercene solo uno.
Levi Morrison,

1
Ero confuso quello che era <<< ma ho trovato un riferimento: php.net/manual/en/...
Tsangares

1
Inoltre, ecco il riferimento per ...: wiki.php.net/rfc/argument_unpacking
Tsangares,

58

int:

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

stringhe:

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

1
Perché '\' ?? Per favore dimmi
zloctb

29

Supponendo che prima disinfetti correttamente i tuoi input ...

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

Quindi regola la tua query:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Citare i valori in modo appropriato a seconda del set di dati.


Ho provato quello che stai proponendo, ma ha appena recuperato il primo valore chiave. So che non ha senso, ma se lo faccio usando l'esempio user542568, la cosa maledetta funziona.
Samuel Ramzan,



7

Per MySQLi con una funzione di escape:

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

Per DOP con dichiarazione preparata:

$qmarks = implode(',', array_fill(0, count($ids), '?'));
$sth = $dbh->prepare("SELECT * FROM galleries WHERE id IN ($qmarks)");
$sth->execute($ids);

questo è carino, breve ed evita la vulnerabilità di inserimento del codice! +1
Stephan Richter,

Anche MySQLi ha preparato delle dichiarazioni. Non sfuggire al tuo input, questo è potenzialmente ancora vulnerabile all'iniezione SQL.
Dharman,

6

Dovremmo occuparci delle vulnerabilità dell'iniezione SQL e di una condizione vuota . Ho intenzione di gestire entrambi come di seguito.

Per una matrice numerica pura, utilizzare il tipo appropriato conversione viz intvalo floatvalo doublevalsu ciascun elemento. Per tipi di stringa mysqli_real_escape_string()che possono essere applicati anche a valori numerici se lo si desidera. MySQL consente numeri e varianti di data come stringa .

Per evitare in modo appropriato i valori prima di passare alla query, creare una funzione simile a:

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

Una tale funzione sarebbe molto probabilmente già disponibile nella tua applicazione, o forse ne hai già creata una.

Disinfetta l'array di stringhe come:

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

Un array numerico può essere disinfettato usando intvalo floatvalo doublevalinvece come adatto:

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

Quindi crea finalmente la condizione della query

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

o

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

Poiché l'array può anche essere vuoto a volte, come $galleries = array();dovremmo quindi notare che IN ()non consente un elenco vuoto. Si può anche usare ORinvece, ma il problema rimane. Quindi il controllo di cui sopra count($values), è quello di garantire lo stesso.

E aggiungilo alla query finale:

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

SUGGERIMENTO : se si desidera mostrare tutti i record (nessun filtro) in caso di un array vuoto invece di nascondere tutte le righe, sostituire semplicemente 0 con 1 nella parte falsa del ternario.


Per rendere la mia soluzione unica (e brutta) , nel caso in cui qualcuno debba:$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Izhar Aazmi,

5

Più sicuro.

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

5

La libreria SafeMySQL di Col. Shrapnel per PHP fornisce segnaposto con suggerimenti di tipo nelle sue query parametrizzate e include un paio di segnaposto convenienti per lavorare con le matrici. Il ?asegnaposto espande un array in un elenco separato da virgole di stringhe con escape *.

Per esempio:

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

* Nota che poiché MySQL esegue la coercizione automatica dei tipi, non importa che SafeMySQL convertirà gli ID sopra in stringhe: otterrai comunque il risultato corretto.


4

Possiamo usare questa clausola "WHERE id IN" se filtriamo correttamente l'array di input. Qualcosa come questo:

$galleries = array();

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

Come nell'esempio seguente:inserisci qui la descrizione dell'immagine

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

Cioè ora dovresti usare in sicurezza $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";


@ levi-morrison ha pubblicato una soluzione molto migliore a questo.
Supratim Roy,

4

Potresti avere tabella texts (T_ID (int), T_TEXT (text))e tabellatest (id (int), var (varchar(255)))

Di insert into test values (1, '1,2,3') ;seguito verranno visualizzate righe da testi di tabelle in cui 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

In questo modo è possibile gestire una semplice relazione di database n2m senza una tabella aggiuntiva e utilizzare solo SQL senza la necessità di utilizzare PHP o altri linguaggi di programmazione.


3

Più un esempio:

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

2

Oltre a utilizzare la query IN, sono disponibili due opzioni in quanto in una query IN esiste il rischio di una vulnerabilità nell'iniezione SQL. È possibile utilizzare il loop per ottenere i dati esatti desiderati oppure è possibile utilizzare la query con il caso OR

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

2

Modo sicuro senza DOP:

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

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$idsTrasmetti $idsvariabile in array
  • array_map Trasforma tutti i valori di array in numeri interi
  • array_unique Rimuovi valori ripetuti
  • array_filter Rimuovi i valori zero
  • implode Unisci tutti i valori alla selezione IN

1

Poiché la domanda originale riguarda una matrice di numeri e sto usando una matrice di stringhe, non ho potuto far funzionare gli esempi forniti.

Ho scoperto che ogni stringa doveva essere incapsulata tra virgolette singole per funzionare con la IN()funzione.

Ecco la mia soluzione

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

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

Come puoi vedere, la prima funzione avvolge ogni variabile dell'array single quotes (\')e quindi implode l'array.

NOTA: $statusnon ha virgolette singole nell'istruzione SQL.

C'è probabilmente un modo migliore per aggiungere le virgolette ma questo funziona.


Oppure$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo,

1
Questo è vulnerabile all'iniezione.
Mark Amery,

Dov'è la fuga delle stringhe? Ad esempio 'all'interno della stringa? Iniezione SQL vulnerabile. Usa PDO :: quote o mysqli_real_escape_string.
18C

1

Di seguito è riportato il metodo che ho usato, usando DOP con segnaposto nominati per altri dati. Per superare l'iniezione SQL sto filtrando l'array per accettare solo i valori che sono numeri interi e rifiuto tutti gli altri.

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

-3

I metodi di base per prevenire l'iniezione SQL sono:

  • Utilizzare istruzioni preparate e query con parametri
  • Sfuggire ai caratteri speciali nella variabile non sicura

L'uso di istruzioni preparate e query con parametri è considerato la migliore pratica, ma se scegli il metodo di escape dei caratteri, puoi provare il mio esempio di seguito.

È possibile generare le query utilizzando array_mapper aggiungere una singola citazione a ciascuno degli elementi 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 . ");";

Il $ sql var generato sarà:

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

Nota: mysql_real_escape_string , come descritto nella sua documentazione qui , è stato deprecato in PHP 5.5.0 ed è stato rimosso in PHP 7.0.0. Invece, è necessario utilizzare l'estensione MySQLi o PDO_MySQL. Vedi anche MySQL: scelta di una guida API e relative FAQ per ulteriori informazioni. Le alternative a questa funzione includono:

  • mysqli_real_escape_string ()

  • PDO :: citazione ()


4
Non solo ciò non aggiunge nulla di nuovo rispetto ad altre risposte, è vulnerabile all'iniezione, nonostante la risposta accettata abbia avuto avvisi sull'iniezione SQL pubblicati per anni.
Mark Amery,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.