Passer un tableau à une requête à l'aide d'une clause WHERE

314

Étant donné un tableau d'ID, $galleries = array(1,2,5)je veux avoir une requête SQL qui utilise les valeurs du tableau dans sa clause WHERE comme:

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

Comment puis-je générer cette chaîne de requête à utiliser avec MySQL?

Braiam
la source
5
@trante celui-ci est le plus ancien (2009).
Fabián
Existe-t-il une solution similaire à un problème comme SELECT * FROM TABLE WHERE NAME LIKE ('ABC%' ou 'ABD%' OR ..)
Eugine Joseph

Réponses:

332

IL FAUT SE MÉFIER! Cette réponse contient une vulnérabilité d' injection SQL grave . N'utilisez PAS les exemples de code présentés ici, sans vous assurer que toute entrée externe est aseptisée.

$ids = join("','",$galleries);   
$sql = "SELECT * FROM galleries WHERE id IN ('$ids')";
Flavius ​​Stef
la source
7
Les identifiants sont toujours une liste entre guillemets, donc il apparaît comme "WHERE id IN ('1,2,3,4')", par exemple. Vous devez citer chaque identificateur séparément, ou bien abandonner les guillemets entre parenthèses.
Rob
22
J'ajoute juste l'avertissement qui $galleriesdevrait être validé en entrée avant cette déclaration! Les instructions préparées ne peuvent pas gérer les tableaux AFAIK, donc si vous avez l'habitude de lier des variables, vous pouvez facilement rendre possible l'injection SQL ici.
leemes
3
pour les débutants en PHP comme moi, quelqu'un peut-il expliquer, ou me diriger vers une ressource pour expliquer, pourquoi cela est sujet à l'injection et comment cela doit être fait correctement pour éviter cela? Que se passe-t-il si la liste des ID est générée à partir d'une requête immédiatement avant l'exécution de la prochaine requête, est-ce toujours dangereux?
ministe2003
3
@ ministe2003 Imaginez si $galleriesa la valeur suivante: array('1); SELECT password FROM users;/*'). Si vous ne nettoyez pas cela, la requête se lira SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Remplacez les noms de table et de colonne par quelque chose que vous avez dans votre base de données et essayez cette requête, consultez les résultats. Vous trouverez une liste de mots de passe comme résultat, plutôt qu'une liste de galeries. Selon la façon dont les données sont sorties, ou ce que le script fait avec un tableau de données inattendues, cela pourrait obtenir la sortie dans la vue publique ... aïe.
Chris Baker
18
Pour la question posée, c'est une réponse parfaitement valable et sûre. Celui qui se plaint n'est pas sûr - que diriez-vous d'un accord que j'ai mis en place ce code particulier avec le $galleriescomme prévu dans la question et vous l'exploitez en utilisant la "vulnérabilité d'injection sql" susmentionnée. Si vous ne pouvez pas - vous me payez 200 USD. Et ça?
zerkms
307

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

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

Explication:

Utilisez l' IN()opérateur SQL pour vérifier si une valeur existe dans une liste donnée.

En général, cela ressemble à ceci:

expr IN (value,...)

Nous pouvons construire une expression à placer à l'intérieur ()de notre tableau. Notez qu'il doit y avoir au moins une valeur entre parenthèses ou MySQL retournera une erreur; cela revient à s'assurer que notre tableau d'entrée a au moins une valeur. Pour éviter les attaques par injection SQL, générez d'abord un ?pour chaque élément d'entrée afin de créer une requête paramétrée. Ici, je suppose que le tableau contenant vos identifiants est appelé $ids:

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

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

Étant donné un tableau d'entrée de trois éléments $selectressemblera à:

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

Notez à nouveau qu'il y a un ?pour chaque élément dans le tableau d'entrée. Ensuite, nous utiliserons PDO ou MySQLi pour préparer et exécuter la requête comme indiqué ci-dessus.

Utilisation de l' IN()opérateur avec des chaînes

Il est facile de basculer entre chaînes et entiers en raison des paramètres liés. Pour PDO, aucun changement n'est requis; pour le changement MySQLi str_repeat('i',à str_repeat('s',si vous devez vérifier les chaînes.

[1]: J'ai omis certaines erreurs de vérification de la brièveté. Vous devez vérifier les erreurs habituelles pour chaque méthode de base de données (ou définir votre pilote de base de données pour lever des exceptions).

[2]: Nécessite PHP 5.6 ou supérieur. Encore une fois, j'ai omis de vérifier la brièveté des erreurs.

Levi Morrison
la source
7
Que fait ... $ ids? J'obtiens "erreur de syntaxe, '.'" Inattendu.
Marcel
Je les vois, j'utilise MySQLi et j'ai php 5.6
Marcel
1
Si vous vous référez $statement->bind_param(str_repeat('i', count($ids)), ...$ids);alors, le ...développe les identifiants d'un tableau en plusieurs paramètres. Si vous faites référence à expr IN (value,...)cela, cela signifie simplement qu'il peut y avoir plus de valeurs, par exemple WHERE id IN (1, 3, 4). Il doit juste y en avoir au moins un.
Levi Morrison
1
J'ai été confus sur ce que <<< était mais j'ai trouvé une référence: php.net/manual/en/…
Tsangares
1
Aussi, voici la référence pour le ...: wiki.php.net/rfc/argument_unpacking
Tsangares
58

ints:

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

cordes:

$query = "SELECT * FROM `$table` WHERE `$column` IN('".implode("','",$array)."')";
user542568
la source
1
Pourquoi '\' ?? S'il vous plaît dites-moi
zloctb
29

En supposant que vous désinfectiez correctement vos entrées au préalable ...

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

Ensuite, ajustez simplement votre requête:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Donnez des valeurs appropriées en fonction de votre ensemble de données.

AvatarKava
la source
J'ai essayé ce que vous proposez, mais il vient de récupérer la première valeur de clé. Je sais que cela n'a pas de sens, mais si je le fais en utilisant l'exemple user542568, la fichue chose fonctionne.
Samuel Ramzan
12

Utilisation:

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

Une simple for eachboucle fonctionnera.

La manière de Flavius ​​/ AvatarKava est meilleure, mais assurez-vous qu'aucune des valeurs du tableau ne contient de virgules.

Matthew Flaschen
la source
9

Comme la réponse de Flavius ​​Stef , vous pouvez utiliser intval()pour vous assurer que toutes idsont des valeurs int:

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

Pour MySQLi avec une fonction d'échappement:

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

Pour AOP avec déclaration préparée:

$qmarks = implode(',', array_fill(0, count($ids), '?'));
$sth = $dbh->prepare("SELECT * FROM galleries WHERE id IN ($qmarks)");
$sth->execute($ids);
artoodetoo
la source
c'est sympa, bref et évite la vulnérabilité d'insertion de code! +1
Stephan Richter
MySQLi a également préparé des déclarations. N'échappez pas à votre saisie, cela est potentiellement encore vulnérable à l'injection SQL.
Dharman
6

Nous devons prendre soin des vulnérabilités d' injection SQL et d'une condition vide . Je vais gérer les deux comme ci-dessous.

Pour un tableau numérique pur, utilisez la conversion de type appropriée à savoir intvalou floatvalou doublevalsur chaque élément. Pour les types de chaîne mysqli_real_escape_string()qui peuvent également être appliqués aux valeurs numériques si vous le souhaitez. MySQL autorise les nombres ainsi que les variantes de date sous forme de chaîne .

Pour échapper de manière appropriée les valeurs avant de passer à la requête, créez une fonction similaire à:

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

Une telle fonction serait probablement déjà à votre disposition dans votre application, ou peut-être que vous en avez déjà créé une.

Désinfectez le tableau de chaînes comme:

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

Un tableau numérique peut être nettoyé en utilisant intvalou floatvalou à la doublevalplace comme approprié:

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

Enfin, créez la condition de requête

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

ou

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

Étant donné que le tableau peut également être vide parfois, comme $galleries = array();nous devons donc noter que IN ()cela ne permet pas une liste vide. On peut également utiliser à la ORplace, mais le problème demeure. Donc, la vérification ci-dessus count($values), est d'assurer la même chose.

Et ajoutez-le à la requête finale:

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

CONSEIL : Si vous souhaitez afficher tous les enregistrements (pas de filtrage) dans le cas d'un tableau vide au lieu de masquer toutes les lignes, remplacez simplement 0 par 1 dans la fausse partie du ternaire.

Izhar Aazmi
la source
Pour faire de ma solution une ligne (et laide) , juste au cas où quelqu'un aurait besoin de:$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Izhar Aazmi
5

Plus sûr.

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

La bibliothèque SafeMySQL de Col. Shrapnel pour PHP fournit des espaces réservés avec indication de type dans ses requêtes paramétrées et inclut quelques espaces réservés pratiques pour travailler avec des tableaux. L' ?aespace réservé développe un tableau en une liste de chaînes d'échappement séparées par des virgules *.

Par exemple:

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

* Notez que puisque MySQL effectue une coercition de type automatique, peu importe que SafeMySQL convertisse les identifiants ci-dessus en chaînes - vous obtiendrez toujours le résultat correct.

Mark Amery
la source
4

Nous pouvons utiliser cette clause "WHERE id IN" si nous filtrons correctement le tableau d'entrée. Quelque chose comme ça:

$galleries = array();

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

Comme l'exemple ci-dessous:entrez la description de l'image ici

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

C'est à dire maintenant que vous devez utiliser en toute sécurité $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";

Supratim Roy
la source
@ levi-morrison a publié une bien meilleure solution à cela.
Supratim Roy
4

Vous pouvez avoir une table texts (T_ID (int), T_TEXT (text))et une tabletest (id (int), var (varchar(255)))

Dans insert into test values (1, '1,2,3') ;ce qui suit, les lignes des textes de table seront affichées où 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

De cette façon, vous pouvez gérer une relation de base de données n2m simple sans table supplémentaire et en utilisant uniquement SQL sans avoir besoin d'utiliser PHP ou un autre langage de programmation.

SERJOU
la source
3

Plus un exemple:

$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
la source
2

Outre l'utilisation de la requête IN, vous avez deux options pour le faire car dans une requête IN, il existe un risque de vulnérabilité d'injection SQL. Vous pouvez utiliser le bouclage pour obtenir les données exactes que vous souhaitez ou vous pouvez utiliser la requête avec le cas OU

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
la source
2

Manière sûre sans AOP:

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

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$idsConvertir une $idsvariable en tableau
  • array_map Transformez toutes les valeurs du tableau en entiers
  • array_unique Supprimer les valeurs répétées
  • array_filter Supprimer les valeurs nulles
  • implode Joignez toutes les valeurs à la sélection IN
Lito
la source
1

Parce que la question d'origine concerne un tableau de nombres et que j'utilise un tableau de chaînes, je n'ai pas pu faire fonctionner les exemples donnés.

J'ai trouvé que chaque chaîne devait être encapsulée entre guillemets simples pour fonctionner avec la IN()fonction.

Voici ma solution

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

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

Comme vous pouvez le voir, la première fonction enveloppe chaque variable de tableau single quotes (\')puis implose le tableau.

REMARQUE: $statusn'a pas de guillemets simples dans l'instruction SQL.

Il existe probablement une meilleure façon d'ajouter les citations, mais cela fonctionne.

RJaus
la source
Ou$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo
1
Ceci est vulnérable aux injections.
Mark Amery
Où s'échappe les cordes? Par exemple 'à l'intérieur de la chaîne? Injection SQL vulnérable. Utilisez PDO :: quote ou mysqli_real_escape_string.
18
1

Voici la méthode que j'ai utilisée, en utilisant PDO avec des espaces réservés nommés pour d'autres données. Pour surmonter l'injection SQL, je filtre le tableau pour n'accepter que les valeurs qui sont des entiers et je rejette toutes les autres.

$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
la source
-3

Les méthodes de base pour empêcher l'injection SQL sont les suivantes:

  • Utiliser des instructions préparées et des requêtes paramétrées
  • Échapper les caractères spéciaux dans votre variable dangereuse

L'utilisation de requêtes préparées et de requêtes paramétrées est considérée comme la meilleure pratique, mais si vous choisissez la méthode des caractères d'échappement, vous pouvez essayer mon exemple ci-dessous.

Vous pouvez générer les requêtes en utilisant array_mappour ajouter une citation unique à chacun des éléments dans $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 . ");";

La var $ sql générée sera:

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

Remarque: mysql_real_escape_string , comme décrit dans sa documentation ici , a été déprécié en PHP 5.5.0, et il a été supprimé en PHP 7.0.0. À la place, l'extension MySQLi ou PDO_MySQL doit être utilisée. Voir aussi MySQL: choisir un guide API et la FAQ associée pour plus d'informations. Les alternatives à cette fonction incluent:

  • mysqli_real_escape_string ()

  • AOP :: quote ()

David
la source
4
Non seulement cela n'ajoute rien de nouveau par rapport aux autres réponses, mais il est vulnérable à l'injection, malgré la réponse acceptée ayant des avertissements sur l'injection SQL publiés sous elle pendant des années.
Mark Amery