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?



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

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.


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)."')";

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.


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



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

¡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.


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

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.


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


@ 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.


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

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

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

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.


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

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


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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.