8

使用bind_param我所有的查询,我现在想使用IN(?)列表中元素数量可以变化的地方。

我在这里使用的 SQLout 函数基本上做了一个$sql_db->prepare, ->bind_param, ->execute(), ->store_result(),->bind_result

// the code below does not work as the query only matches on element 'a':
$locations = ('a','b','c','d','e');

SQLout ("SELECT Name FROM Users WHERE Locations IN (?)",
    array('s', $locations), array(&$usrName));


// the code below does work as a brute-force method,
// but is not a viable solution as I can't anticipate the number of elements in $locations going forward:

SQLout ("SELECT Name FROM Users WHERE Locations IN (?,?,?,?,?)",
    array('sssss', $locations[0],$locations[1],$locations[2],$locations[3],$locations[4]), array(&$usrName));

有没有人想出一个更优雅的解决方案?

4

5 回答 5

8

这是一个占位符落在他们脸上的地方。减去自动转义,它们几乎实际上只是内部的字符串替换操作,这意味着如果你有WHERE Locations IN (?), 并传入1,2,3,4,你将得到相当于

WHERE Locations IN ('1,2,3,4')  // note, it's a string, not individual comma-separated integers

逻辑上等价于

WHERE Locations = '1,2,3,4' // again, just a string

而不是预期的

WHERE Locations = 1 OR Locations = 2 OR Locations = 3 OR Locations = 4

唯一实用的解决方案是构建您自己的逗号分隔占位符 ( ?) 列表,例如:

$placeholders = implode(',', array_fill(0, count($values), '?'));
$sql = "SELECT Name FROM Users WHERE Locations IN ($placeholders)";

然后绑定你的参数是通常的。

于 2013-02-19T15:07:23.577 回答
2

正如 Hazmat 所说,您需要建立参数,然后通过调用call_user_func_array准备好的语句来传递它们,但比他的示例更接近工作代码:)

//In the calling code
$queryString = "SELECT Name FROM Users WHERE Locations IN (";
$queryString .= getWhereIn($locations);
$queryString .= " )";

$parametersArray = array();

foreach($locations as $location){
    $parameter = array();
    $parameter[0] = 's'; //It's a string
    $parameter[1] = $location;

    $parametersArray[] = $parameter;
}



//This is a function in a class that wraps around the class mysqli_statement
function    bindParameterArray($parameterArray){

    $typesString = '';
    $parameterValuesArray = array();

    foreach($parameterArray as $parameterAndType){
        $typesString .= $parameterAndType[0];
        $parameterValuesArray[] = $parameterAndType[1];
    }

    $finalParamArray = array($typesString);
    $finalParamArray = array_merge($finalParamArray, $parametersArray);
    call_user_func_array(array($this->statement, "bind_param"), $finalParamArray);
}

function getWhereIn($inArray){
    $string = "";
    $separator = "";
    for($x=0 ; $x<count($inArray) ; $x++){
        $string .= $separator."?";
        $separator = ", ";
    }
    return  $string;
}
于 2013-02-19T15:38:31.910 回答
1

IN您可以在准备/绑定之前“构建”子句。

$sql = 'SELECT Name FROM Users WHERE Locations IN (' . implode(array_fill(0, count($locations), '?')) . ')';

然后您可以使用call_user_func_array绑定参数,而无需知道有多少。

// Parameters for SQLOut
$params = array(
    # The SQL Query
    $sql,
    # The params for bind_param
    array(str_repeat('s', count($locations))),
    # The params for bind_result
    array(&$usrName)
);

// Add the locations into the parameter list
foreach($locations as &$loc){
    // not sure if this is needed, but bind_param
    // expects its parameters to be references
    $params[1][] = &$loc;
}

// Call your function
call_user_func_array('SQLout', $params);

注意:这是未经测试的

于 2013-02-19T15:11:27.390 回答
-1

IN 通常很慢,而且对准备好的语句不友好。更好的解决方案是构建将在 IN 中的项目的表并使用 JOIN 来获得相同的效果。

于 2013-02-19T15:05:53.710 回答
-2

有没有人想出一个更优雅的解决方案?

当然。我有。

Mysqli 实际上不能用于准备好的语句,尤其是在这种复杂的情况下。
因此,最好摆脱准备好的语句并实现自己的占位符,并支持开发人员可以遇到的所有真实案例。

safeMysql(我的一个创作)有一个您正在寻找的解决方案(以及其他十几个令人头疼的解决方案)。

在您的特定情况下,它就像这行代码一样简单

// the code works alright:
$locations = array('a', 'b', 'c', 'd', 'e');
$usrName = $db->getCol("SELECT Name FROM Users WHERE Locations IN (?a)", $locations);

与您在使用某些 PHP 和 API 函数时可能会遇到的丑陋代码不同(并且仍然会收到令人不安的警告取决于您目前使用的 PHP 版本),此代码简洁易读。这是一件重要的事情。即使在一年过去了,你也可以知道这段代码做了什么。

于 2013-02-19T15:14:58.513 回答