2008-11-29 78 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