使用WHERE子句将数组传递给查询


314

给定一个ID数组,$galleries = array(1,2,5)我想拥有一个SQL查询,该查询使用其WHERE子句中的数组值,例如:

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

如何生成此查询字符串以用于MySQL?



5
@trante这是最古老的(2009)。
法比安

是否有类似问题的解决方案,例如SELECT * FROM TABLE WHERE NAME LIKE('ABC%'或'ABD%'OR ..)
Eugine Joseph

Answers:


332

谨防!该答案包含一个严重的SQL注入漏洞。在不确保任何外部输入都经过消毒的情况下,请勿使用此处介绍的代码示例。

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

7
标识符仍然是带引号的列表,因此例如以“ WHERE id IN('1,2,3,4')”形式出现。您需要分别为每个标识符加上引号,否则请将括号内的引号引起来。
罗布

22
我只添加$galleries在此语句之前应输入验证的警告!预处理语句无法处理数组AFAIK,因此,如果习惯于绑定变量,则可以在此处轻松地进行SQL注入。
leemes 2012年

3
对于像我这样使用PHP的新手来说,有人可以解释一下,还是可以向我指向要解释的资源,这为什么易于注入,以及应该如何正确地进行预防?如果ID列表是在下一个查询运行之前立即从查询生成的,那仍然很危险吗?
ministe2003年

3
@ ministe2003想象一下是否$galleries具有以下值:array('1); SELECT password FROM users;/*')。如果您不进行清理,查询将显示为SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*)。将表和列名更改为数据库中的名称,然后尝试查询,并检查结果。您将找到结果密码列表,而不是画廊列表。根据数据的输出方式或脚本对一系列意外数据的处理方式,可能会将其输出到公共视图中……哎呀。
克里斯·贝克

18
对于所提问题,这是一个完全有效和安全的答案。谁抱怨这并不安全-我将如何使用问题中提供的来设置此特定代码,$galleries然后使用前面提到的“ sql注入漏洞”来利用它呢?如果不能,请给我200美元。那个怎么样?
zerkms 2015年

307

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

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

说明:

使用SQL IN()运算符检查给定列表中是否存在值。

一般来说,它看起来像这样:

expr IN (value,...)

我们可以构建一个表达式以放置在()数组中。请注意,圆括号内必须至少有一个值,否则MySQL将返回错误。这等于确保我们的输入数组至少具有一个值。为了帮助防止SQL注入攻击,请首先?为每个输入项生成一个,以创建参数化查询。在这里,我假设包含您的ID的数组称为$ids

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

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

给定一个包含三个项目的输入数组,$select如下所示:

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

再次注意,?输入数组中的每个项目都有一个。然后,我们将使用PDO或MySQLi来准备和执行上述查询。

IN()字符串使用运算符

由于绑定了参数,因此很容易在字符串和整数之间进行更改。对于PDO,无需更改;对于MySQLi,更改str_repeat('i',str_repeat('s',是否需要检查字符串。

[1]:为简洁起见,我省略了一些错误检查。您需要检查每种数据库方法的常见错误(或将数据库驱动程序设置为引发异常)。

[2]:需要PHP 5.6或更高版本。同样,为了简洁起见,我省略了一些错误检查。


7
... $ ids有什么作用?我收到“语法错误,意外的'。'”。
Marcel 2014年

我看到了他们,我正在使用MySQLi,并且我有php 5.6
Marcel

1
如果您指$statement->bind_param(str_repeat('i', count($ids)), ...$ids);...是ID,则将ID从数组扩展为多个参数。如果您所指的是,expr IN (value,...)那意味着可以有更多的值,例如WHERE id IN (1, 3, 4)。至少需要有一个。
维·莫里森

1
我很困惑<< <<是什么,但是我找到了参考文献:php.net/manual/en/…–
Tsangares

1
此外,以下是以下内容的参考...wiki.php.net/rfc/argument_unpacking
桑加雷斯

58

整数:

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

字符串:

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

1
为什么'\'??请告诉我
-zloctb

29

假设您事先正确清理了输入内容...

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

然后只需调整您的查询:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

根据您的数据集适当地引用值。


我尝试了您的建议,但它只是获取了第一个键值。我知道这没有意义,但是如果我使用user542568示例进行操作,那么该死的事情就可以了。
塞缪尔·拉姆赞


9

作为Flavius Stef的答案,您可以intval()用来确保所有id都是int值:

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

7

对于具有转义功能的MySQLi

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

对于具有准备好的语句的PDO:

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

这很好,简短,可以避免代码插入漏洞!+1
史蒂芬·里希特

MySQLi也准备了语句。不要逃避您的输入,这可能仍然容易受到SQL注入的攻击。
达曼

6

我们应该注意SQL注入漏洞和空条件。我将按以下方式处理这两个问题。

对于纯数字数组,使用适当类型的转换即intvalfloatvaldoubleval在每个元件。mysqli_real_escape_string()如果需要,还可以将字符串类型应用于数字值。MySQL允许数字和日期变体作为字符串

要在传递给查询之前适当地转义值,请创建类似于以下内容的函数:

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

这样的功能很可能已经在您的应用程序中提供给您,或者您可能已经创建了。

像这样清理字符串数组:

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

可以使用intvalfloatvaldoubleval适当地清除数字数组:

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

然后最终建立查询条件

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

要么

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

由于有时数组也可以为空,$galleries = array();因此我们应该注意,IN ()不允许有空列表。也可以使用它OR,但是问题仍然存在。因此,上述检查,count($values)是为了确保相同。

并将其添加到最终查询中:

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

提示:如果要在空数组的情况下显示所有记录(不过滤),而不是隐藏所有行,只需在三元组的假部分将0替换为1


为了使我的解决方案变得$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
单线

5

更安全

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

5

Col. Shrapnel的PHP SafeMySQL库在其参数化查询中提供了带类型提示的占位符,并包括几个方便的用于处理数组的占位符。该?a占位符膨胀出的阵列,以逗号分隔的转义字符串*的列表。

例如:

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

*请注意,由于MySQL执行自动类型强制,因此SafeMySQL将上面的id转换为字符串并不重要-您仍将获得正确的结果。


4

如果我们正确过滤输入数组,则可以使用此“ WHERE id IN”子句。像这样:

$galleries = array();

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

像下面的例子:在此处输入图片说明

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

即现在您应该安全使用 $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";


@ levi-morrison发布了很多更好的解决方案。
Supratim Roy 2015年

4

你可能有桌子texts (T_ID (int), T_TEXT (text))和桌子test (id (int), var (varchar(255)))

insert into test values (1, '1,2,3') ;从表中的文本下面将输出行,其中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

这样,您可以管理简单的n2m数据库关系,而无需使用额外的表,而仅使用SQL,而无需使用PHP或其他某种编程语言。


3

更多示例:

$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

除了使用IN查询外,您还有两种选择,因为在IN查询中存在SQL注入漏洞的风险。您可以使用循环获取所需的确切数据,也可以将查询与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

没有PDO的安全方法:

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

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$ids$ids变量转换为数组
  • array_map 将所有数组值转换为整数
  • array_unique 删除重复的值
  • array_filter 删除零值
  • implode 将所有值连接到IN选择

1

因为原始问题与数字数组有关,并且我使用的是字符串数组,所以无法使给定的示例起作用。

我发现每个字符串都需要封装在单引号中才能使用该IN()功能。

这是我的解决方案

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

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

如您所见,第一个函数将每个数组变量包装在其中single quotes (\'),然后内嵌到数组中。

注意:$statusSQL语句中没有单引号。

可能有一种更好的添加引号的方法,但这可行。


$filter = "'" . implode("','",$status) . "'";
亚历杭德罗·萨拉曼卡·马祖埃洛

1
这是易受攻击的。
马克·阿默里

字符串转义在哪里?例如'在字符串里面?SQL注入易受攻击。使用PDO :: quote或mysqli_real_escape_string。
18C

1

下面是我使用的方法,将PDO与命名占位符一起用于其他数据。为了克服SQL注入,我正在过滤数组以仅接受整数值,并拒绝所有其他值。

$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

防止SQL注入的基本方法是:

  • 使用准备好的语句和参数化查询
  • 转义不安全变量中的特殊字符

使用准备好的语句和参数化查询是更好的做法,但是如果选择转义字符方法,则可以尝试下面的示例。

您可以通过在的array_map每个元素上添加单引号来生成查询$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 . ");";

生成的$ sql var将为:

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

注意:mysql_real_escape_string 如其文档所述,在PHP 5.5.0中已弃用,在PHP 7.0.0中已删除。相反,应使用MySQLi或PDO_MySQL扩展。另请参见MySQL:选择API指南和相关的FAQ,以获取更多信息。此功能的替代方法包括:

  • mysqli_real_escape_string()

  • PDO :: quote()


4
与其他答案相比,这不仅没有添加任何新内容,而且尽管存在接受了多年的关于SQL注入的警告,但它容易受到注入的攻击。
Mark Amery
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.