【问题标题】:PDO binding values for MySQL IN statement [duplicate]MySQL IN 语句的 PDO 绑定值 [重复]
【发布时间】:2010-12-07 21:06:42
【问题描述】:

我有一个关于 PDO 的问题,在被它困扰了很长一段时间后,我真的很想得到一个答案。

举个例子:

我将一个 ID 数组绑定到一个 PDO 语句,以便在 MySQL IN 语句中使用。

数组会说:$values = array(1,2,3,4,5,6,7,8);

数据库安全变量是 $products = implode(',' $values);

因此,$products 将是一个 STRING,其值为:'1,2,3,4,5,6,7,8 '

语句如下所示:

SELECT users.id
FROM users
JOIN products
ON products.user_id = users.id
WHERE products IN (:products)

当然,$products 将绑定到声明为 :products

但是,当语句被编译并绑定值时,它实际上看起来像这样:

SELECT users.id
FROM users
JOIN products
ON products.user_id = users.id
WHERE products IN ('1,2,3,4,5,6,7,8')

问题在于它将 IN 语句中的所有内容作为单个字符串执行,因为我已将其准备为逗号分隔值以绑定到语句。

我真正需要的是:

SELECT users.id
FROM users
JOIN products
ON products.user_id = users.id
WHERE products IN (1,2,3,4,5,6,7,8)

我实际上可以做到这一点的唯一方法是将值放在字符串本身而不绑定它们,但是我肯定必须有一种更简单的方法来做到这一点。

【问题讨论】:

    标签: php mysql pdo sql-in


    【解决方案1】:

    您需要在 IN 中提供与 $values 数组中值的数量相同数量的 ?s

    这可以通过创建一个 ?s 数组来轻松完成

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

    并在你的 IN 子句中使用这个 $in 数组

    这将根据您不断变化的 $values 数组动态为您提供量身定制的 ?s 数组

    【讨论】:

      【解决方案2】:

      请尝试这样:

      WHERE products IN(REPLACE(:products, '\'', ''))
      

      问候

      【讨论】:

      • 这个问题是5年前问和回答的,这个回答并没有解决问题..
      【解决方案3】:

      这是一个将未知数量的记录列绑定到插入值的示例。

      public function insert($table, array $data)
      {
          $sql = "INSERT INTO $table (" . join(',', array_keys($data)) . ') VALUES ('
              . str_repeat('?,', count($data) - 1). '?)';
      
          if($sth = $this->db->prepare($sql))
          {
              $sth->execute(array_values($data));
              return $this->db->lastInsertId();
          }
      }
      
      $id = $db->insert('user', array('name' => 'Bob', 'email' => 'foo@example.com'));
      

      【讨论】:

        【解决方案4】:

        如果表达式基于用户输入而不使用 bindValue() 绑定值,那么实验性 SQL 可能不是一个好选择。 但是,您可以通过使用 MySQL 的 REGEXP 检查输入的语法来使其安全。

        例如:

        SELECT *
        FROM table
        WHERE id IN (3,156)
        AND '3,156' REGEXP '^([[:digit:]]+,?)+$'
        

        【讨论】:

          【解决方案5】:

          处理这种情况的一个好方法是使用str_pad 为SQL 查询中的每个值放置一个?。然后您可以将值数组(在您的情况下为 $values)作为要执行的参数传递:

          $sql = '
          SELECT users.id
          FROM users
          JOIN products
          ON products.user_id = users.id
          WHERE products.id IN ('.str_pad('',count($values)*2-1,'?,').')
          ';
          
          $sth = $dbh->prepare($sql);
          $sth->execute($values);
          $user_ids = $sth->fetchAll();
          

          通过这种方式,您可以从使用准备好的语句而不是直接将值插入 SQL 中获得更多好处。

          PS - 如果具有给定 ID 的产品共享用户 ID,结果将返回重复的用户 ID。如果您只想要唯一的用户 ID,我建议将查询的第一行更改为 SELECT DISTINCT users.id

          【讨论】:

          • 为什么需要在count() 上做*2-1 而不仅仅是-1
          • @Nalum:因为 str_pad 的第二个参数是以字符为单位的“填充长度”。由于您需要为每个值加上一个问号,并在它们之间加一个逗号,因此结果为count($values)*2-1。如果你愿意,你也可以使用str_repeat('?,',count($values)-1).'?' 之类的东西。
          • 我更喜欢使用implode(',', array_fill(1,count($values),'?')),因为出现一对一错误的可能性较小,而且您不必以不同的方式处理最后一个元素。
          • 你也可以使用 str_repeat() ,这可能是列出的最有效的方法:rtrim( str_reapeat('?,', count($values)), ',')
          • @Dominique 你读错了代码。它不会将变量直接放在查询中。这确实是解决方案的重点。它放了一个“?”为每个变量,以便 PDO 可以准备实际的语句。是的,我们喜欢 PDO 的保护措施,并且此解决方案提供了一种使用它们的方法。
          【解决方案6】:

          你可以很容易地做到这一点。 如果您的 IN() 语句有一组值 例如:

          $test = array(1,2,3);
          

          你可以这样做

          $test = array(1,2,3);
          $values = count($test);
          $criteria = sprintf("?%s", str_repeat(",?", ($values ? $values-1 : 0)));
          //Returns ?,?,?
          $sql = sprintf("DELETE FROM table where column NOT IN(%s)", $criteria);
          //Returns DELETE FROM table where column NOT IN(?,?,?)
          $pdo->sth = prepare($sql);
          $pdo->sth->execute($test);
          

          【讨论】:

          • 这在上面 Asaph 的回答中已经提到过,如果您使用命名参数则不起作用,因为您不能混合使用它们。
          【解决方案7】:

          这与这个问题中提出的问题相同:Can I bind an array to an IN() condition?

          答案是,对于in 子句中的可变大小列表,您需要自己构造查询。

          但是,您可以使用 find_in_set 使用带引号的逗号分隔列表,但对于大型数据集,这会产生相当大的性能影响,因为表中的每个值都必须强制转换为 char 类型。

          例如:

          select users.id
          from users
          join products
          on products.user_id = users.id
          where find_in_set(cast(products.id as char), :products)
          

          或者,作为第三种选择,您可以创建一个用户定义的函数来为您拆分逗号分隔的列表(参见http://www.slickdev.com/2008/09/15/mysql-query-real-values-from-delimiter-separated-string-ids/)。这可能是三者中最好的选择,尤其是当您有很多依赖于in(...) 子句的查询时。

          【讨论】:

          • 天啊,PDO 是一个如此简洁的库,但却遗漏了一些最基本的功能。真的有点可惜..
          • 如果您有一小部分数据,您可能可以使用 find_in_set (我用一个示例更新了答案),或者您可以尝试使用用户定义的函数来解析逗号-为您单独列出清单。 (很抱歉继续添加想法,哈哈;在我发布另一个问题的链接后,我想,“必须有办法做到这一点......”)
          • 感谢 FIND_IN_SET 提醒。真的拯救了我的一天。但是我发现它不需要做 CAST 就可以工作,所以 find_in_set(products.id, :products) 就足够了。
          • 一个非常有用的答案。您可以将此答案的版本添加到您链接到的问题吗?因为它获得了更多的选票并且更有可能出现在搜索结果的顶部。
          • 我只会添加一件事:当您将 param :products 绑定到字符串时,它不能有任何空格,例如1,2,3,4 是正确的,但 1, 2, 3, 4 不是。
          【解决方案8】:

          在这种情况下,您可能想出的最好的准备语句类似于以下内容:

          SELECT users.id
          FROM users
          JOIN products
          ON products.user_id = users.id
          WHERE products IN (?,?,?,?,?,?,?,?)

          然后,您将遍历您的值并将它们绑定到准备好的语句,确保问号的数量与您绑定的值相同。

          【讨论】:

          • 当你知道要绑定多少个变量时的逻辑解决方案,但是当你有无限数量的参数时它会变得有点混乱。
          • @hd82:那么,你必须巧妙地结合 sprintf 和 count。
          猜你喜欢
          • 2015-09-07
          • 2011-02-10
          • 2018-06-06
          • 1970-01-01
          • 2015-06-27
          • 1970-01-01
          • 1970-01-01
          • 2014-03-12
          • 2011-03-23
          相关资源
          最近更新 更多