【问题标题】:Passing an array to a query using a WHERE clause使用 WHERE 子句将数组传递给查询
【发布时间】:2018-07-17 19:02:23
【问题描述】:

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

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

如何生成此查询字符串以与 MySQL 一起使用?

【问题讨论】:

标签: php mysql arrays


【解决方案1】:

没有 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 选择中

【讨论】:

    【解决方案2】:

    以下是我使用的方法,使用带有命名占位符的 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);
    

    【讨论】:

    • 当你使用is_numeric()时,请注意0x539也是一个数值,0b10100111001也是一个数值
    【解决方案3】:

    对于带有转义函数的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 注入的攻击。
    【解决方案4】:

    注意!此答案包含严重的SQL injection 漏洞。不要使用此处提供的代码示例,除非确保任何外部输入都经过清理。

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

    【讨论】:

    • 标识符仍然是一个带引号的列表,因此它显示为“WHERE id IN ('1,2,3,4')”,例如。您需要单独引用每个标识符,否则将括号内的引号去掉。
    • 我只是在此语句之前添加了$galleries 应该输入验证的警告! Prepared statements 不能处理数组 AFAIK,所以如果你习惯于绑定变量,你可以在这里轻松地进行 SQL 注入。
    • 对于像我这样的 PHP 新手,有人可以解释一下,或者指点我一个资源来解释一下,为什么这很容易被注入以及如何正确地做到这一点以防止这种情况发生?如果 ID 列表是在运行下一个查询之前立即从查询生成的,那仍然危险吗?
    • @ministe2003 想象一下如果$galleries 具有以下值:array('1); SELECT password FROM users;/*')。如果您不对其进行清理,则查询将显示为SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*)。将表和列名更改为数据库中的名称,然后尝试该查询,查看结果。您将找到密码列表作为结果,而不是画廊列表。根据数据的输出方式,或者脚本对一组意外数据的处理方式,可能会输出到公共视图中……哎哟。
    • 对于提出的问题,这是一个完全有效且安全的答案。谁抱怨它不安全 - 我使用问题中提供的$galleries 设置此特定代码并使用上述“sql 注入漏洞”利用它如何处理。如果你不能 - 你付给我 200 美元。那怎么样?
    【解决方案5】:

    作为Flavius Stef's answer,您可以使用intval() 确保所有id 都是int 值:

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

    【讨论】:

      【解决方案6】:

      用途:

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

      一个简单的for each 循环就可以了。

      Flavius/AvatarKava's way 更好,但请确保所有数组值都不包含逗号。

      【讨论】:

        【解决方案7】:

        再举个例子:

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

        【讨论】:

          【解决方案8】:

          我们应该注意SQL injection 漏洞和空条件。我将按以下方式处理两者。

          对于纯数值数组,对每个元素使用适当的类型转换,即 intval 或 floatval 或 doubleval。对于字符串类型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);
          

          数字数组可以使用intval 或floatval 或doubleval 进行适当的清理:

          $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);
          【解决方案9】:

          除了使用 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;
             }
          

          【讨论】:

            【解决方案10】:

            你可能有表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 或其他一些编程语言。

            【讨论】:

              【解决方案11】:

              因为最初的问题与数字数组有关,而我使用的是字符串数组,所以无法使给定的示例工作。

              我发现每个字符串都需要用单引号封装起来才能使用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 (\') 中,然后将数组内爆。

              注意:$status 在 SQL 语句中没有单引号。

              可能有更好的方法来添加引号,但这很有效。

              【讨论】:

              • 或$filter = "'" . implode("','",$status) . "'";
              • 这是易被注入的。
              • 字符串转义在哪里?例如字符串中的'? SQL 注入易受攻击。使用 PDO::quote 或 mysqli_real_escape_string。
              【解决方案12】:

              上校。 Shrapnel 的SafeMySQL PHP 库在其参数化查询中提供了类型提示的占位符,并包含几个方便的用于处理数组的占位符。 ?a 占位符将数组扩展为以逗号分隔的转义字符串列表*。

              例如:

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

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

              【讨论】:

                【解决方案13】:

                使用 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 或更高版本。为简洁起见,我再次省略了一些错误检查。

                【讨论】:

                • ...$ids 有什么作用?我得到“语法错误,意外的'。'”。
                • 我看到了,我正在使用 MySQLi,我有 php 5.6
                • 如果您指的是$statement-&gt;bind_param(str_repeat('i', count($ids)), ...$ids);,那么... 将id 从一个数组扩展为多个参数。如果您指的是expr IN (value,...),那么这只是意味着可以有更多的值,例如WHERE id IN (1, 3, 4)。至少需要一个。
                • 我很困惑 php.net/manual/en/…
                • 另外,这里是... 的参考:wiki.php.net/rfc/argument_unpacking
                【解决方案14】:

                如果我们正确过滤输入数组,我们可以使用这个“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 对此发布了更好的解决方案。
                【解决方案15】:

                更安全。

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

                【讨论】:

                  【解决方案16】:

                  整数:

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

                  字符串:

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

                  【讨论】:

                  • 为什么是 '\' ??请告诉我
                  【解决方案17】:

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

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

                  然后只需调整您的查询:

                  SELECT *
                  FROM galleries
                  WHERE id IN ( $matches ) 
                  

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

                  【讨论】:

                  • 我尝试了您的建议,但它只是获取了第一个键值。我知道这没有意义,但如果我使用 user542568 示例来做,那该死的东西会起作用。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-06-18
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多