Pasar una matriz a una consulta utilizando una cláusula WHERE

314

Dada una matriz de identificadores $galleries = array(1,2,5), quiero tener una consulta SQL que use los valores de la matriz en su cláusula WHERE como:

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

¿Cómo puedo generar esta cadena de consulta para usar con MySQL?

Braiam
fuente
55
@trante este es el más antiguo (2009).
Fabián
¿Hay alguna solución similar para un problema como SELECT * FROM TABLE WHERE NAME LIKE ('ABC%' or 'ABD%' OR ..)
Eugine Joseph

Respuestas:

332

¡TENER CUIDADO! Esta respuesta contiene una grave vulnerabilidad de inyección SQL . NO use los ejemplos de código que se presentan aquí, sin asegurarse de que se desinfecte cualquier entrada externa.

$ids = join("','",$galleries);   
$sql = "SELECT * FROM galleries WHERE id IN ('$ids')";
Flavius ​​Stef
fuente
77
Los identificadores siguen siendo una lista entre comillas, por lo que aparece como "WHERE id IN ('1,2,3,4')", por ejemplo. Debe citar cada identificador por separado o, de lo contrario, elimine las comillas dentro de los paréntesis.
Rob
22
¡Acabo de agregar la advertencia que $galleriesdebe validarse antes de esta declaración! Las declaraciones preparadas no pueden manejar matrices AFAIK, por lo que si está acostumbrado a variables enlazadas, podría hacer posible la inyección de SQL aquí.
Leemes
3
Para los novatos en PHP como yo, ¿alguien puede explicarme o indicarme un recurso para explicar por qué esto es propenso a la inyección y cómo se debe hacer esto correctamente para evitarlo? ¿Qué sucede si la lista de ID se genera a partir de una consulta inmediatamente antes de que se ejecute la siguiente consulta, es todavía peligroso?
ministe2003
3
@ ministe2003 Imagínese si $galleriestenía el valor siguiente: array('1); SELECT password FROM users;/*'). Si no desinfecta eso, la consulta se leería SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Cambie los nombres de tabla y columna a algo que tenga en su base de datos e intente esa consulta, consulte los resultados. Encontrará una lista de contraseñas como resultado, en lugar de una lista de galerías. Dependiendo de cómo se emiten los datos, o de lo que hace el script con una matriz de datos inesperados, eso podría obtener la salida a la vista pública ... ¡ay!
Chris Baker
18
Para la pregunta formulada es una respuesta perfectamente válida y segura. Quien se queja no es seguro, ¿qué tal un acuerdo en el que configuré este código en particular con el $galleriesque se proporciona en la pregunta y lo explota usando la "vulnerabilidad de inyección sql" mencionada anteriormente. Si no puede, me paga USD200. ¿Qué hay sobre eso?
zerkms
307

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

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

Explicación:

Use el IN()operador SQL para verificar si existe un valor en una lista dada.

En general se ve así:

expr IN (value,...)

Podemos construir una expresión para colocar dentro ()de nuestra matriz. Tenga en cuenta que debe haber al menos un valor dentro del paréntesis o MySQL devolverá un error; esto equivale a asegurarse de que nuestra matriz de entrada tenga al menos un valor. Para ayudar a prevenir ataques de inyección SQL, primero genere un ?para cada elemento de entrada para crear una consulta parametrizada. Aquí supongo que la matriz que contiene sus identificadores se llama $ids:

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

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

Dada una matriz de entrada de tres elementos $selectse verá así:

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

Nuevamente, tenga en cuenta que hay un ?para cada elemento en la matriz de entrada. Luego usaremos PDO o MySQLi para preparar y ejecutar la consulta como se indicó anteriormente.

Usando el IN()operador con cuerdas

Es fácil cambiar entre cadenas y enteros debido a los parámetros enlazados. Para PDO no se requiere cambio; para MySQLi cambie str_repeat('i',a str_repeat('s',si necesita verificar cadenas.

[1]: He omitido alguna comprobación de errores por brevedad. Debe verificar los errores habituales para cada método de base de datos (o configurar su controlador de base de datos para que arroje excepciones).

[2]: Requiere PHP 5.6 o superior. Nuevamente, he omitido algunos errores de comprobación de brevedad.

Levi Morrison
fuente
77
¿Qué hace ... $ ids? Me sale "error de sintaxis, inesperado '.'".
Marcel
Los veo, estoy usando MySQLi y tengo php 5.6
Marcel
1
Si se está refiriendo a, $statement->bind_param(str_repeat('i', count($ids)), ...$ids);entonces ...está expandiendo los id de una matriz a múltiples parámetros. Si se refiere a expr IN (value,...)eso, eso solo significa que puede haber más valores, por ejemplo WHERE id IN (1, 3, 4). Solo tiene que haber al menos uno.
Levi Morrison
1
Estaba confundido qué era <<< pero encontré una referencia: php.net/manual/en/…
Tsangares
1
Además, aquí está la referencia para ...: wiki.php.net/rfc/argument_unpacking
Tsangares
58

entradas:

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

instrumentos de cuerda:

$query = "SELECT * FROM `$table` WHERE `$column` IN('".implode("','",$array)."')";
usuario542568
fuente
1
¿Por qué? Por favor, dime
zloctb
29

Suponiendo que desinfecte adecuadamente sus entradas de antemano ...

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

Luego solo ajusta tu consulta:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Cotice los valores de manera apropiada según su conjunto de datos.

AvatarKava
fuente
Intenté lo que está proponiendo, pero solo obtuvo el primer valor clave. Sé que no tiene sentido, pero si lo hago usando el ejemplo user542568, la maldita cosa funciona.
Samuel Ramzan
12

Utilizar:

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

Un for eachbucle simple funcionará.

La forma de Flavius ​​/ AvatarKava es mejor, pero asegúrese de que ninguno de los valores de la matriz contenga comas.

Matthew Flaschen
fuente
9

Como respuesta de Flavius ​​Stef , puede usar intval()para asegurarse de que todos idsean valores int:

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

Para MySQLi con una función de 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)");

Para PDO con declaración preparada:

$qmarks = implode(',', array_fill(0, count($ids), '?'));
$sth = $dbh->prepare("SELECT * FROM galleries WHERE id IN ($qmarks)");
$sth->execute($ids);
artoodetoo
fuente
¡Esto es agradable, breve y evita la vulnerabilidad de inserción de código! +1
Stephan Richter
MySQLi también ha preparado declaraciones. No escape su entrada, esto aún es potencialmente vulnerable a la inyección de SQL.
Dharman
6

Debemos ocuparnos de las vulnerabilidades de inyección SQL y una condición vacía . Voy a manejar ambos como a continuación.

Para una matriz numérica pura, utilice el viz conversión tipo apropiado intvalo floatvalo doublevalmás de cada elemento. Para tipos de cadena mysqli_real_escape_string()que también pueden aplicarse a valores numéricos si lo desea. MySQL permite números y variantes de fecha como cadena .

Para escapar adecuadamente de los valores antes de pasar a la consulta, cree una función similar a:

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

Es probable que dicha función ya esté disponible en su aplicación, o tal vez ya haya creado una.

Desinfecte el conjunto de cadenas como:

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

Una matriz numérica puede ser desinfectados utilizando intvalo floatvalo doublevalen lugar como adecuados:

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

Luego, finalmente construya la condición de consulta

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

o

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

Dado que la matriz también puede estar vacía a veces, como $galleries = array();deberíamos tener en cuenta que IN ()no permite una lista vacía. También se puede usar OR, pero el problema persiste. Por lo tanto, la comprobación anterior count($values)es para garantizar lo mismo.

Y agréguelo a la consulta final:

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

SUGERENCIA : si desea mostrar todos los registros (sin filtro) en caso de una matriz vacía en lugar de ocultar todas las filas, simplemente reemplace 0 con 1 en la parte falsa del ternario.

Izhar Aazmi
fuente
Para hacer que mi solución sea de una sola línea (y fea) , en caso de que alguien necesite:$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Izhar Aazmi
5

Más seguro

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

La biblioteca SafeMySQL de Col. Shrapnel para PHP proporciona marcadores de posición con sugerencias de tipo en sus consultas parametrizadas e incluye un par de marcadores de posición convenientes para trabajar con matrices. los?a marcador de posición expande una matriz a una lista separada por comas de cadenas escapadas *.

Por ejemplo:

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

* Tenga en cuenta que, dado que MySQL realiza una coerción de tipo automática, no importa que SafeMySQL convierta los identificadores anteriores en cadenas; de todos modos, obtendrá el resultado correcto.

Mark Amery
fuente
4

Podemos usar esta cláusula "WHERE id IN" si filtramos la matriz de entrada correctamente. Algo como esto:

$galleries = array();

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

Como el siguiente ejemplo:ingrese la descripción de la imagen aquí

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

Es decir, ahora debe usar con seguridad $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";

Supratim Roy
fuente
@ levi-morrison publicó una solución mucho mejor para esto.
Supratim Roy
4

Puedes tener mesa texts (T_ID (int), T_TEXT (text))y mesatest (id (int), var (varchar(255)))

A insert into test values (1, '1,2,3') ;continuación, se mostrarán filas de los textos de la tabla donde 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 esta manera, puede administrar una relación de base de datos n2m simple sin una tabla adicional y usando solo SQL sin la necesidad de usar PHP u otro lenguaje de programación.

SERJOU
fuente
3

Más un ejemplo:

$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
fuente
2

Además de usar la consulta IN, tiene dos opciones para hacerlo, ya que en una consulta IN existe el riesgo de una vulnerabilidad de inyección SQL. Puede usar el bucle para obtener los datos exactos que desea o puede usar la consulta con el 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;
   }
Gaurav Singh
fuente
2

Forma segura sin PDO:

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

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$idsConvertir $idsvariable en matriz
  • array_map Transforme todos los valores de la matriz en enteros
  • array_unique Eliminar valores repetidos
  • array_filter Eliminar valores cero
  • implode Unir todos los valores a la selección IN
Lito
fuente
1

Debido a que la pregunta original se relaciona con un conjunto de números y estoy usando un conjunto de cadenas, no pude hacer que los ejemplos dados funcionen.

Descubrí que cada cadena necesitaba ser encapsulada entre comillas simples para trabajar con la IN()función.

Aqui esta mi solucion

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

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

Como puede ver, la primera función envuelve cada variable de matriz single quotes (\')y luego implosiona la matriz.

NOTA: $statusno tiene comillas simples en la instrucción SQL.

Probablemente haya una mejor manera de agregar las comillas, pero esto funciona.

RJaus
fuente
O bien$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo
1
Esto es vulnerable a la inyección.
Mark Amery
¿Dónde está el escape de cuerdas? Por ejemplo 'dentro de la cadena? Inyección SQL vulnerable. Utilice PDO :: quote o mysqli_real_escape_string.
18C
1

A continuación se muestra el método que he utilizado, utilizando PDO con marcadores de posición con nombre para otros datos. Para superar la inyección SQL, estoy filtrando la matriz para aceptar solo los valores que son enteros y rechazando todos los demás.

$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
fuente
-3

Los métodos básicos para evitar la inyección de SQL son:

  • Use declaraciones preparadas y consultas parametrizadas
  • Escapar de los caracteres especiales en su variable insegura

El uso de declaraciones preparadas y consultas parametrizadas se considera la mejor práctica, pero si elige el método de caracteres de escape, puede probar mi ejemplo a continuación.

Puede generar las consultas utilizando array_mappara agregar una comilla simple a cada uno de los elementos en $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 . ");";

El $ sql var generado será:

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

Nota: mysql_real_escape_string , como se describe en su documentación aquí , fue obsoleto en PHP 5.5.0 y se eliminó en PHP 7.0.0. En su lugar, se debe usar la extensión MySQLi o PDO_MySQL. Consulte también MySQL: elegir una guía API y preguntas frecuentes relacionadas para obtener más información. Las alternativas a esta función incluyen:

  • mysqli_real_escape_string ()

  • PDO :: quote ()

David
fuente
44
Esto no solo no agrega nada nuevo en comparación con otras respuestas, es vulnerable a la inyección, a pesar de que la respuesta aceptada ha tenido advertencias sobre la inyección SQL publicada durante años.
Mark Amery