【问题标题】:How to bind an array of strings with mysqli prepared statement?如何使用 mysqli 准备语句绑定字符串数组?
【发布时间】:2022-11-22 15:24:38
【问题描述】:

我需要将一组值绑定到 WHERE IN(?) 子句。我怎样才能做到这一点?

这有效:

$mysqli = new mysqli("localhost", "root", "root", "db");
if(!$mysqli || $mysqli->connect_errno)
{
    return;
}
$query_str= "SELECT name FROM table WHERE city IN ('Nashville','Knoxville')";
$query_prepared = $mysqli->stmt_init();
if($query_prepared && $query_prepared->prepare($query_str))
{       
    $query_prepared->execute();

但这我无法像这样使用 bind_param :

$query_str= "SELECT name FROM table WHERE city IN (?)";
$query_prepared = $mysqli->stmt_init();
if($query_prepared && $query_prepared->prepare($query_str))
{       
    $cities= explode(",", $_GET['cities']);
    $str_get_cities=  "'".implode("','", $get_cities)."'"; // This equals 'Nashville','Knoxville'

    $query_prepared->bind_param("s", $cities);
    $query_prepared->execute();

我究竟做错了什么?

我也试过call_user_func_array,但似乎无法获得正确的语法。

【问题讨论】:

  • $str_get_cities= "'".implode("','", $get_cities)."'";。不要使用引号!这是由 bind_param 使用“s”选项完成的!
  • 正如其他人推荐的那样,使用call_user_func_array将所需参数绑定到参数化查询的函数。只是为了强调它接受通过引用传递的参数。我只能找到关于如何使用动态数量的参数执行参数化查询来绑定的代码片段,所以我最终做了我自己的函数(参见 this 帖子)。它接受任何参数化的 SELECT、UPDATE、INSERT 和 DELETE 查询,它帮助我在我的 PHP 代码中动态地进行任何 MySQL DB 交互。
  • 早些时候,清楚,完整的问题问完全相同的事情:Use an array in a mysqli prepared statement: WHERE .. IN(..) query

标签: php mysqli bindparam


【解决方案1】:

从 PHP 8.1 开始,您可以直接传递一个数组来执行:

$sql = "INSERT INTO users (email, password) VALUES (?,?)"; // sql
$stmt = $mysqli->prepare($sql); // prepare
$stmt->execute([$email, $password]); // execute with data! 

对于早期版本,任务有点复杂但可行。对于一个简单的情况,当您已经有一个带有占位符的查询时,代码将是

$sql = "INSERT INTO users (email, password) VALUES (?,?)"; // sql
$data = [$email, $password]; // put your data into array
$stmt = $mysqli->prepare($sql); // prepare
$stmt->bind_param(str_repeat('s', count($data)), ...$data); // bind array at once
$stmt->execute();

虽然像您的情况一样,我们有任意数量的占位符,但我们将不得不添加更多代码。我将从我的文章Mysqli prepared statement with multiple values for IN clause中获取解释:

  • 首先,我们需要创建一个字符串,其中? 标记的数量与数组中元素的数量相同。为此,我们将使用 str_repeat() 函数,它非常方便。
  • 然后必须将这个带有逗号分隔问号的字符串添加到查询中。虽然它是一个变量,但在这种情况下它是安全的,因为它只包含常量值
  • 那么这个查询必须像任何其他查询一样准备
  • 然后我们需要创建一个字符串,其类型与 bind_param() 一起使用。请注意,通常没有理由对绑定变量使用不同的类型——mysql 会很乐意将它们全部作为字符串接受。有一些极端情况,但极为罕见。对于日常使用,您始终可以保持简单并使用“s”来表示所有内容。 str_repeat() 再次来救援。
  • 然后我们需要将数组值绑定到语句。不幸的是,你不能把它写成一个单一的变量,就像$stmt->bind_param("s", $array),在bind_param()中只允许标量变量。幸运的是,argument unpacking operator 完全可以满足我们的需求——将一组值发送到一个函数中,就好像它是一组不同的变量一样!
  • 剩下的和往常一样 - 执行查询、获取结果并获取数据!

所以正确的示例代码是

$array = ['Nashville','Knoxville']; // our array
$in    = str_repeat('?,', count($array) - 1) . '?'; // placeholders
$sql   = "SELECT name FROM table WHERE city IN ($in)"; // sql
$stmt  = $mysqli->prepare($sql); // prepare
$types = str_repeat('s', count($array)); //types
$stmt->bind_param($types, ...$array); // bind array at once
$stmt->execute();
$result = $stmt->get_result(); // get the mysqli result
$data = $result->fetch_all(MYSQLI_ASSOC); // fetch the data   

尽管此代码相当大,但它比迄今为止本主题中提供的任何其他看似合理的解决方案都小得无法比拟。

【讨论】:

  • 我不明白你回答的第一段。我不认为一个问题应该提供答案,他正在寻找你和其他人回答的答案。我要找的不是我需要添加多少问号,我已经有了。我找不到 bind_param 的正确解释,因为 php.net 文档似乎并不关心它。我想知道我是否可以将数组作为参数传递给它。
  • @AaA 我的回答中还有另一段,其中提到了一个参数解包运算符。它回答了你的问题吗?
  • 谢谢,“参数解包运算符”是我的答案。这个答案通过非常详尽的解决方案解决了许多其他类似的问题。
  • 这行不通。$stmt->bind_param($paramstring, $params );每次都给我Array to string conversion
【解决方案2】:

您不能将两个变量与一个 question mark 绑定!

对于您绑定的每个变量,您都需要一个question mark

“bind_param”检查每个变量是否符合要求。之后,字符串值放在引号之间。

这是行不通的。

"SELECT name FROM table WHERE city IN (?)"; ( becomes too )
$q_prepared->bind_param("s", $cities);
"SELECT name FROM table WHERE city IN ('city1,city2,city3,city4')";

一定是。

"SELECT name FROM table WHERE city IN (?,?,?,?)"; ( becomes too )
$q_prepared->bind_param("ssss", $city1,$city2,$city3,$city4);
"SELECT name FROM table WHERE city IN ('city1','city2','city3','city4')";

$query_prepared->bind_param 一一引用字符串参数。
并且变量的数量和字符串类型的长度必须与语句中的参数相匹配。

$query_str= "SELECT name FROM table WHERE city IN ('Nashville','Knoxville')";

会变成

$query_str= "SELECT name FROM table WHERE city IN (?,?)";

现在bind_param 必须是

bind_param("ss",$arg1,$arg2)

有了这个

$query_str= "SELECT name FROM table WHERE city IN (?)";

bind_param

bind_param("s",$cities)

你得到

$query_str= "SELECT name FROM table WHERE city IN ('Nashville,Knoxville')";

这就是数组不起作用的原因。
这个事实的唯一解决方案是call_user_func_array

如果你发起一个声明,下面是不必要的

$query_prepared = $mysqli->stmt_init();
if($query_prepared && $query_prepared->prepare($query_str)) {

这是对的

$query_prepared = $mysqli->stmt_init();
if($query_prepared->prepare($query_str)) {

如果你不想使用call_user_func_array
你只有很少的论点
您可以使用以下代码来完成。

[...]
$cities= explode(",", $_GET['cities']);
if (count($cities)>3) { echo "too many arguments"; }
else
{ 
$count = count($cities); 
$SetIn = "(";
  for($i = 0; $i < $count; ++$i) {    
      $code.='s';
      if ($i>0) {$SetIn.=",?";} else {$SetIn.="?";}
  }
$SetIn.=")";
$query_str= "SELECT name FROM table WHERE city IN ".$SetIn;
// with 2 arguments $query_str will look like
// SELECT name FROM table WHERE city IN (?,?)
$query_prepared = $mysqli->stmt_init();
if($query_prepared->prepare($query_str))
  {       
    if ($count==1) { $query_prepared->bind_param($code, $cities[0]);}
    if ($count==2) { $query_prepared->bind_param($code, $cities[0],$cities[1]);}
    if ($count==3) { $query_prepared->bind_param($code, $cities[0],$cities[1],$cities[2]);
    // with 2 arguments $query_prepared->bind_param() will look like
    // $query_prepared->bind_param("ss",$cities[0],$cities[1])      
  }    

    $query_prepared->execute();
  } 
 [...]
 }

我建议你用call_user_func_array试试看。

寻找nick9v的解决方案
mysqli-stmt.bind-param

【讨论】:

  • 你建议使用call_user_func_array,但你从来没有在这里展示如何去做。 =_='
  • 这是 call_user_func_array 的一个用例,整个问题都解释了pontikis.net/blog/dynamically-bind_param-array-mysqli
  • “将 mysqli_stmt_bind_param() 与 call_user_func_array() 结合使用时必须小心。请注意,mysqli_stmt_bind_param() 需要通过引用传递参数,而 call_user_func_array() 可以接受可以表示引用或值的变量列表作为参数。 “来源 -> php.net/manual/en/mysqli-stmt.bind-param.php
【解决方案3】:

像这样使用 call_user_func_array:

$stmt = $mysqli->prepare("INSERT INTO t_file_result VALUES(?,?,?,?)");

$id = '1111';
$type = 2;
$result = 1;
$path = '/root';

$param = array('siis', &$id, &$type, &$result, &$path);
call_user_func_array(array($stmt, 'bind_param'), $param);

$stmt->execute();

printf("%d row inserted. 
", $stmt->effected_rows);
$stmt->close;

【讨论】:

  • 如果您在文档中添加更多解释和来源,那么这肯定会被接受!可爱的答案在这里!
  • 这行不通,所有参数都必须是引用。因此,您需要将格式定义为$format = "siis";,然后在数组中使用&amp;$format。所有参数都必须是引用。
【解决方案4】:

从 PHP 8.1 版开始,binding is no longer required。与 PDO 5.0 版本以来一样,您现在可以将参数作为数组直接传递给the execute method

$mysqli       = new mysqli("localhost", "root", "root", "db");
$params       = ['Nashville','Knoxville'];
$placeholders = str_repeat('?,', count($params) - 1) . '?'
$query        = "SELECT name FROM table WHERE city IN ($placeholders)";
$stmt         = $mysqli->prepare($query);

$stmt->execute($params);

另一个例子,如果你有一个关联数组,其键与列名匹配:

$mysqli       = new mysqli("localhost", "root", "root", "db");
$data         = ["bar" => 23, "baz" => "some data"];
$params       = array_values($data);
$placeholders = str_repeat('?,', count($params) - 1) . '?'
$columns      = implode("`,`", array_keys($data));
$query        = "INSERT INTO foo (`$columns`) VALUES ($placeholders)";
$stmt         = $mysqli->prepare($query);

$stmt->execute($params);

还值得一提的是库now defaults 在发生错误时抛出异常。在 8.1 版之前,情况并非如此。

【讨论】:

    【解决方案5】:

    我也遇到了这个问题,并在发现大多数人都在使用call_user_func_array之前使用eval让它工作

    $fields = array('model','title','price'); // fields in WHERE clause
    $values = array( // type and value for each field
        array('s','ABCD-1001'),
        array('s','[CD] Test Title'),
        array('d','16.00')
    );
    $sql = "SELECT * FROM products_info WHERE "; // start of query
    foreach ($fields as $current){ // build where clause from fields
        $sql .= '`' . $current . '` = ? AND ';
    }
    $sql = rtrim($sql,'AND '); // remove last AND 
    $stmt = $db->prepare($sql);
    $types = ''; $vals = '';
    foreach ($values as $index => $current_val){ // build type string and parameters
        $types .= $current_val[0];
        $vals .= '$values[' . $index . '][1],';
    }
    $vals = rtrim($vals,','); // remove last comma
    $sql_stmt = '$stmt->bind_param("' . $types . '",' . $vals . ');'; // put bind_param line together
    eval($sql_stmt); // execute bind_param
    $stmt->execute();
    $stmt->bind_result($col1,$col2,$col3,$col4,$col5,$col6); // this could probably also be done dynamically in the same way
    while ($stmt->fetch()){
        printf("%s %s %s %s %s %s
    ", $col1,$col2,$col3,$col4,$col5,$col6);
    }
    

    【讨论】:

    • 绝对需要一些玩弄,但我真的很喜欢这个答案。它可能比call_user_func_array 有点笨重,但老实说它更容易阅读和弄清楚发生了什么。
    【解决方案6】:

    我这样做的方式:准备查询及其所有单独的问号,以及类型字符串。

    $cities = array('Nashville','Knoxville');
    $dibs = '';
    $query = "SELECT name FROM table WHERE city IN (";
    $marks = array();
    
    foreach ($cities as $k => $city) {
        // i,s,b,d type based on the variables to bind.
        $dibs .= 's';
        array_push($marks, '?');
    }
    
    $query .= implode(',', $marks) .')';
    

    连接。

    $mysql = new mysqli($host, $user, $pass, $dbname);
    $statement =
        $mysql->prepare($query)
    OR  die(sprintf(
            'Query error (%s) %s', $mysql->errno, $mysql->error
        ))
    ;
    

    然后使用“...”标记/省略号 (documentation) 来绑定数组。

    if ($statement) {
        $statement->bind_param($dibs, ...$cities);
        $statement->execute();
    
        $statement->close();
    }
    $mysql->close();
    

    我知道它有点违背为了逃避而绑定的目的(但至少它适用于整数列表,即 ID)。如果您看到如何改进这种方法,请随时编辑/发表评论。

    【讨论】:

      猜你喜欢
      • 2022-06-15
      • 1970-01-01
      • 2013-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多