Passando uma matriz para uma consulta usando uma cláusula WHERE

314

Dada uma matriz de IDs $galleries = array(1,2,5), quero ter uma consulta SQL que use os valores da matriz em sua cláusula WHERE, como:

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

Como posso gerar essa string de consulta para usar com o MySQL?

Braiam
fonte
5
@trante, este é o mais antigo (2009).
Fabián
Existe alguma solução semelhante para o problema como SELECT * FROM TABLE WHERE NOME COMO ( 'ABC%' ou 'ABD%' OR ..)
Eugine Joseph

Respostas:

332

Cuidado! Esta resposta contém uma grave vulnerabilidade de injeção SQL . NÃO use as amostras de código como apresentadas aqui, sem ter certeza de que qualquer entrada externa seja higienizada.

$ids = join("','",$galleries);   
$sql = "SELECT * FROM galleries WHERE id IN ('$ids')";
Flavius ​​Stef
fonte
7
Os identificadores ainda são uma lista entre aspas, então são exibidos como "WHERE id IN ('1,2,3,4')", por exemplo. Você precisa citar cada identificador separadamente, ou colocar as aspas entre parênteses.
252 Rob
22
Acabei de adicionar o aviso que $galleriesdeve ser validado antes desta declaração! As instruções preparadas não podem manipular matrizes AFAIK; portanto, se você estiver acostumado a vincular variáveis, poderá facilmente tornar possível a injeção de SQL aqui.
Leem
3
para iniciantes no PHP como eu, alguém pode explicar ou me indicar um recurso para explicar por que isso é propenso a injeção e como isso deve ser feito corretamente para evitar isso? E se a lista de IDs for gerada a partir de uma consulta imediatamente antes da execução da próxima consulta, isso ainda é perigoso?
ministe2003
3
@ ministe2003 Imagine se $galleriestinha o seguinte valor: array('1); SELECT password FROM users;/*'). Se você não higienizar isso, a consulta será lida SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Altere os nomes de tabela e coluna para algo que você tem no seu banco de dados e tente essa consulta, confira os resultados. Você encontrará uma lista de senhas como resultado, em vez de uma lista de galerias. Dependendo de como os dados são gerados, ou o que o script faz com uma matriz de dados inesperados, isso pode gerar resultados para a exibição pública ... ai.
Chris Baker
18
Para a pergunta feita , é uma resposta perfeitamente válida e segura. Quem quer que reclame não é seguro - que tal um acordo em que eu configurei esse código específico com o $galleriesfornecido na pergunta e você o explora usando a "vulnerabilidade de injeção de sql" acima mencionada. Se você não pode - você me paga USD200. Que tal isso?
Zerkms 15/04
307

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

Explicação:

Use o IN()operador SQL para verificar se existe um valor em uma determinada lista.

Em geral, é assim:

expr IN (value,...)

Podemos construir uma expressão para colocar dentro ()do nosso array. Observe que deve haver pelo menos um valor entre parênteses ou o MySQL retornará um erro; isso equivale a garantir que nossa matriz de entrada tenha pelo menos um valor. Para ajudar a prevenir ataques de injeção SQL, primeiro gere um ?para cada item de entrada para criar uma consulta parametrizada. Aqui, eu assumo que a matriz que contém seus IDs é chamada $ids:

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

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

Dada uma matriz de entrada de três itens, $selectserá semelhante a:

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

Observe novamente que existe um ?para cada item na matriz de entrada. Em seguida, usaremos o DOP ou o MySQLi para preparar e executar a consulta conforme observado acima.

Usando o IN()operador com strings

É fácil mudar entre seqüências de caracteres e números inteiros devido aos parâmetros associados. Para o DOP, não é necessária nenhuma alteração; para MySQLi, mude str_repeat('i',para str_repeat('s',se você precisar verificar as strings.

[1]: omiti a verificação de erros por questões de concisão. Você precisa verificar os erros usuais para cada método de banco de dados (ou definir seu driver de DB para lançar exceções).

[2]: Requer PHP 5.6 ou superior. Mais uma vez, omiti a verificação de erros por questões de concisão.

Levi Morrison
fonte
7
O que ... $ ids faz? Eu recebo "erro de sintaxe, inesperado '.'".
Marcel
Eu os vejo, estou usando o MySQLi e tenho o php 5.6
Marcel
1
Se você está se referindo $statement->bind_param(str_repeat('i', count($ids)), ...$ids);, o ...item está expandindo os IDs de uma matriz para vários parâmetros. Se você está se referindo expr IN (value,...), significa que pode haver mais valores, por exemplo WHERE id IN (1, 3, 4). Só precisa haver pelo menos um.
Levi Morrison
1
Eu estava confuso o que <<< era, mas eu encontrei uma referência: php.net/manual/en/...
Tsangares
1
Além disso, aqui é a referência para a ...: wiki.php.net/rfc/argument_unpacking
Tsangares
58

ints:

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

cordas:

$query = "SELECT * FROM `$table` WHERE `$column` IN('".implode("','",$array)."')";
user542568
fonte
1
Por '\' ?? Por favor, diga-me
zloctb
29

Supondo que você desinfete corretamente suas entradas de antemão ...

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

Em seguida, basta ajustar sua consulta:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Cite os valores adequadamente, dependendo do seu conjunto de dados.

AvatarKava
fonte
Eu tentei o que você está propondo, mas ele apenas buscou o primeiro valor-chave. Eu sei que não faz sentido, mas se eu fizer isso usando o exemplo user542568, a maldita coisa funciona.
Samuel Ramzan
12

Usar:

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

Um for eachloop simples funcionará.

O caminho do Flavius ​​/ AvatarKava é melhor, mas verifique se nenhum dos valores da matriz contém vírgulas.

Matthew Flaschen
fonte
9

Como resposta de Flavius ​​Stef , você pode usar intval()para garantir que todos idsejam valores int:

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

Para o MySQLi com uma função 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 DOP com declaração preparada:

$qmarks = implode(',', array_fill(0, count($ids), '?'));
$sth = $dbh->prepare("SELECT * FROM galleries WHERE id IN ($qmarks)");
$sth->execute($ids);
apoiado
fonte
isso é legal, curto e evita a vulnerabilidade de inserção de código! 1
Stephan Richter
O MySQLi também preparou declarações. Não escape da sua entrada, pois isso ainda é potencialmente vulnerável à injeção de SQL.
Dharman
6

Devemos cuidar das vulnerabilidades de injeção de SQL e de uma condição vazia . Eu vou lidar com ambos como abaixo.

Para uma matriz numérica pura, use a conversão de tipo apropriada viz intvalou floatvalou doublevalsobre cada elemento. Para tipos de string mysqli_real_escape_string()que também podem ser aplicados a valores numéricos, se desejar. O MySQL permite números, bem como variantes de data, como string .

Para escapar adequadamente dos valores antes de passar para a consulta, crie uma função semelhante a:

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

Provavelmente, essa função já estará disponível para você no seu aplicativo ou talvez você já tenha criado um.

Desinfete a matriz de strings como:

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

Uma matriz numérica pode ser higienizado com intvalou floatvalou doublevalem vez como apropriado:

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

Finalmente, crie a condição de consulta

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

ou

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

Como o array também pode estar vazio às vezes, como $galleries = array();devemos observar, isso IN ()não permite uma lista vazia. Também se pode usar OR, mas o problema permanece. Portanto, a verificação acima count($values), é garantir o mesmo.

E adicione-o à consulta final:

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

DICA : Se você deseja mostrar todos os registros (sem filtragem) no caso de uma matriz vazia, em vez de ocultar todas as linhas, substitua 0 por 1 na parte falsa do ternário.

Izhar Aazmi
fonte
Para fazer com que a minha solução de um one-liner (e feio) , apenas no caso de alguém precisa:$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Izar Aazmi
5

Mais seguro.

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

A biblioteca SafeMySQL da Col. Shrapnel para PHP fornece espaços reservados com sugestões de tipo em suas consultas parametrizadas e inclui alguns espaços reservados convenientes para trabalhar com matrizes. O ?aespaço reservado expande uma matriz para uma lista separada por vírgula de seqüências de caracteres de escape *.

Por exemplo:

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

* Observe que, como o MySQL executa coerção automática de tipo, não importa que o SafeMySQL converta os IDs acima em strings - você ainda obterá o resultado correto.

Mark Amery
fonte
4

Podemos usar esta cláusula "WHERE id IN" se filtrarmos adequadamente a matriz de entrada. Algo assim:

$galleries = array();

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

Como no exemplo abaixo:insira a descrição da imagem aqui

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

Ou seja, agora você deve usar com segurança $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";

Supratim Roy
fonte
@ levi-morrison postou uma solução muito melhor para isso.
Supratim Roy 18/04
4

Você pode ter mesa texts (T_ID (int), T_TEXT (text))e mesatest (id (int), var (varchar(255)))

A insert into test values (1, '1,2,3') ;seguir, serão exibidas linhas dos textos da tabela em que 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

Dessa forma, você pode gerenciar uma relação simples de banco de dados n2m sem uma tabela extra e usar apenas SQL sem a necessidade de usar PHP ou alguma outra linguagem de programação.

SERJOU
fonte
3

Mais um exemplo:

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

Além de usar a consulta IN, você tem duas opções para fazê-lo, pois em uma consulta IN existe o risco de uma vulnerabilidade de injeção SQL. Você pode usar o loop para obter os dados exatos que deseja ou pode usar a consulta com o 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
fonte
2

Maneira segura sem DOP:

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

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$idsConverter $idsvariável em matriz
  • array_map Transforme todos os valores da matriz em números inteiros
  • array_unique Remova valores repetidos
  • array_filter Remover valores zero
  • implode Associe todos os valores à seleção IN
Lito
fonte
1

Como a pergunta original está relacionada a uma matriz de números e eu estou usando uma matriz de seqüências de caracteres, não pude fazer os exemplos fornecidos funcionarem.

Eu descobri que cada string precisava ser encapsulada em aspas simples para trabalhar com a IN()função.

Aqui está a minha solução

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

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

Como você pode ver, a primeira função envolve cada variável da matriz single quotes (\')e depois implode a matriz.

NOTA: $statusnão possui aspas simples na instrução SQL.

Provavelmente existe uma maneira melhor de adicionar aspas, mas isso funciona.

RJaus
fonte
Ou$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo
1
Isso é vulnerável a injeção.
Mark Amery
Onde está escapando das cordas? Por exemplo, 'dentro da string? Injeção de SQL vulnerável. Use PDO :: quote ou mysqli_real_escape_string.
18C 21/12
1

Abaixo está o método que usei, usando o PDO com espaços reservados nomeados para outros dados. Para superar a injeção de SQL, estou filtrando a matriz para aceitar apenas os valores inteiros e rejeitando todos os outros.

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

Os métodos básicos para impedir a injeção de SQL são:

  • Use instruções preparadas e consultas parametrizadas
  • Escapando os caracteres especiais na sua variável não segura

Usando instruções preparadas e consultas com parâmetros, a consulta é considerada a melhor prática, mas se você escolher o método de caracteres de escape, poderá tentar meu exemplo abaixo.

Você pode gerar as consultas usando array_mappara adicionar uma única citação a cada um dos elementos no $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 . ");";

O $ sql var gerado será:

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

Nota: mysql_real_escape_string , conforme descrito em sua documentação aqui , foi descontinuado no PHP 5.5.0 e foi removido no PHP 7.0.0. Em vez disso, a extensão MySQLi ou PDO_MySQL deve ser usada. Veja também MySQL: escolhendo um guia de API e FAQ relacionadas para obter mais informações. Alternativas para esta função incluem:

  • mysqli_real_escape_string ()

  • DOP :: quote ()

David
fonte
4
Isso não apenas adiciona nada de novo em comparação com outras respostas, como também é vulnerável à injeção, apesar da resposta aceita ter recebido avisos sobre a injeção de SQL postada nela há anos.
Mark # Amery