2008-11-29 118 views
21

您将如何在PHP中编写一个准备好的MySQL语句,每次都使用不同数量的参数?一例这样的查询是:MySQL使用可变大小变量列表准备的语句

SELECT `age`, `name` FROM `people` WHERE id IN (12, 45, 65, 33) 

IN的子句将具有不同数量的每次运行时间id秒。

我在脑海中有两种可能的解决方案,但想看看是否有更好的方法。

可能的解决方案1 ​​使该语句接受100个变量,并填充其余的虚拟值以保证不在表格中;多次调用超过100个值。

可能的解决方案2不要使用准备好的语句;建立并运行查询,严格检查可能的注入攻击。

+0

我怀疑我会避免两者的你建议的解决方案。准备好的语句让您的代码更安全。如果你有一个小列表,你的第一个解决方案似乎非常低效和浪费。 – Zoredache 2008-11-29 09:40:03

+0

我同意我认为这些想法可能会刺激别人的想法。 :) – smarthall 2008-11-29 12:17:58

+0

对于SQL Server,请参阅[参数化SQL IN子句?](http://stackoverflow.com/q/337704/90527) – outis 2012-04-04 12:02:27

回答

21

我能想到几个解决方案。

一种解决方案可能是创建一个临时表。对in子句中的每个参数执行插入操作。然后对临时表进行简单的连接。

另一种方法可能是做这样的事情。

$dbh=new PDO($dbConnect, $dbUser, $dbPass); 
$parms=array(12, 45, 65, 33); 
$parmcount=count($parms); // = 4 
$inclause=implode(',',array_fill(0,$parmcount,'?')); // = ?,?,?,? 
$sql='SELECT age, name FROM people WHERE id IN (%s)'; 
$preparesql=sprintf($sql,$inclause); // = example statement used in the question 
$st=$dbh->prepare($preparesql); 
$st->execute($parms); 

我怀疑,但没有证据,第一解决方案可能较大的列表会更好,而后者将工作较小的列表。


使@orrd快乐在这里是一个简洁的版本。

$dbh=new PDO($dbConnect, $dbUser, $dbPass); 
$parms=array(12, 45, 65, 33); 
$st=$dbh->prepare(sprintf('SELECT age, name FROM people WHERE id IN (%s)', 
          implode(',',array_fill(0,count($parms),'?')))); 
$st->execute($parms); 

+1

我喜欢你的第二个建议。做到这一点,并忘记它,直到性能是一个问题。那时可能需要调查第一个选项。 – benlumley 2008-11-29 09:46:16

+0

如果只有我想到了!你的第一个解决方案听起来像我正在寻找的确切的东西。 – smarthall 2008-11-29 12:16:43

+0

我经常使用模式#2。 Perl的DBI有一个prepare_cached()函数,所以如果你用相似数量的占位符查询,它将重用语句句柄。不知道PHP虽然.. – 2008-11-29 23:33:58

2

请把#2假表。准备好的语句是您应该考虑保护自己免受SQL注入攻击的唯一方法。

但是,您可以做的是生成一组动态绑定变量。即如果你需要7(或103),则不要制造100。

4

体面的sql包装器支持绑定到数组值。 即

$sql = "... WHERE id IN (?)"; 
$values = array(1, 2, 3, 4); 
$result = $dbw -> prepare ($sql, $values) -> execute(); 
1

如果你只在你IN条款使用整数,没有什么是反驳了动态地构造查询,而无需使用SQL参数。

function convertToInt(&$value, $key) 
{ 
    $value = intval($value); 
} 

$ids = array('12', '45', '65', '33'); 
array_walk($ids, 'convertToInt'); 
$sql = 'SELECT age, name FROM people WHERE id IN (' . implode(', ', $ids) . ')'; 
// $sql will contain SELECT age, name FROM people WHERE id IN (12, 45, 65, 33) 

但毫无疑问的解决方案here是解决这个问题的更通用的方法。

2

我得到了我的答案:http://bugs.php.net/bug.php?id=43568
这是我的工作解决方案,我的问题。现在我可以动态地使用尽可能多的参数。它们的数量与我在数组中的数量相同,或者在这种情况下,我将最后一个查询(其中包含email ='[email protected]'的所有ID)的ID传递给动态查询以获取所有数据关于每个这些id的信息,无论我最终需要多少。

<?php $NumofIds = 2; //this is the number of ids i got from the last query 
    $parameters=implode(',',array_fill(0,$NumofIds,'?')); 
    // = ?,? the same number of ?'s as ids we are looking for<br /> 
    $paramtype=implode('',array_fill(0,$NumofIds,'i')); // = ii<br/> 
    //make the array to build the bind_param function<br/> 
    $idAr[] = $paramtype; //'ii' or how ever many ?'s we have<br/> 
    while($statement->fetch()){ //this is my last query i am getting the id out of<br/> 
     $idAr[] = $id; 
    } 

    //now this array looks like this array:<br/> 
    //$idAr = array('ii', 128, 237); 

    $query = "SELECT id,studentid,book_title,date FROM contracts WHERE studentid IN ($parameters)"; 
    $statement = $db->prepare($query); 
    //build the bind_param function 
    call_user_func_array (array($statement, "bind_param"), $idAr); 
    //here is what we used to do before making it dynamic 
    //statement->bind_param($paramtype,$v1,$v2); 
    $statement->execute(); 
?> 
10

也有它的第二个参数的FIND_IN_SET function是逗号分隔值的字符串:

SELECT age, name FROM people WHERE FIND_IN_SET(id, '12,45,65,33') 
0

今天我有一个类似的问题,我发现这个话题。看着答案和搜索谷歌我找到了一个漂亮的解决方案。

虽然,我的问题有点复杂。 因为我有固定的绑定值和动态

这是解决方案。

$params = array() 
$all_ids = $this->get_all_ids(); 

for($i = 0; $i <= sizeof($all_ids) - 1; $i++){ 
    array_push($params, $all_ids[$i]['id']); 
} 

$clause = implode(',', array_fill(0, count($params), '?')); // output ?, ?, ? 
$total_i = implode('', array_fill(0, count($params), 'i')); // output iiii 

$types = "ss" . $total_i; // will reproduce : ssiiii ..etc 

// %% it's necessary because of sprintf function 
$query = $db->prepare(sprintf("SELECT * 
           FROM clients  
           WHERE name LIKE CONCAT('%%', ?, '%%') 
           AND IFNULL(description, '') LIKE CONCAT('%%', ?, '%%') 
           AND id IN (%s)", $clause)); 

$thearray = array($name, $description); 
$merge = array_merge($thearray, $params); // output: "John", "Cool guy!", 1, 2, 3, 4 

// We need to pass variables instead of values by reference 
// So we need a function to that 
call_user_func_array('mysqli_stmt_bind_param', array_merge (array($query, $types), $this->makeValuesReferenced($merge))); 

而且功能makeValuesreferenced

public function makeValuesReferenced($arr){ 
    $refs = array(); 
    foreach($arr as $key => $value) 
     $refs[$key] = &$arr[$key]; 
    return $refs; 
} 

链接获得此 '诀窍':https://bugs.php.net/bug.php?id=49946PHP append one array to another (not array_push or +)[PHP]: Error -> Too few arguments in sprintf();http://no2.php.net/manual/en/mysqli-stmt.bind-param.php#89171Pass by reference problem with PHP 5.3.1