Подготовленные операторы MySQL с переменным переменным
Как вы напишете подготовленный оператор MySQL в PHP, который каждый раз принимает различное количество аргументов? Пример такого запроса:
SELECT `age`, `name` FROM `people` WHERE id IN (12, 45, 65, 33)
Предложение IN
будет иметь различное количество id
при каждом запуске.
У меня есть два возможных решения, но я хочу посмотреть, есть ли лучший способ.
Возможное решение 1 Сделать выражение принятым 100 переменными и заполнить остальными фиктивными значениями, гарантированными, что они не находятся в таблице; сделать несколько вызовов более 100 значений.
Возможное решение 2 Не используйте подготовленный оператор; строит и запускает проверку запросов для возможных инъекционных атак.
Ответы
Ответ 1
Я могу придумать пару решений.
Одним из решений может быть создание временной таблицы. Сделайте вставку в таблицу для каждого параметра, который у вас был бы в разделе 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);
Ответ 2
Существует также функция FIND_IN_SET
, второй параметр которой представляет собой строку значений, разделенных запятыми:
SELECT age, name FROM people WHERE FIND_IN_SET(id, '12,45,65,33')
Ответ 3
Положительные sql-оболочки поддерживают привязку к значениям массива.
т.е.
$sql = "... WHERE id IN (?)";
$values = array(1, 2, 3, 4);
$result = $dbw -> prepare ($sql, $values) -> execute ();
Ответ 4
Пожалуйста, возьмите № 2 со стола. Подготовленные утверждения - это единственный способ защитить себя от внедрения SQL.
Однако вы можете создать динамический набор переменных привязки. т.е. не создавать 100, если вам нужно 7 (или 103).
Ответ 5
Я получил свой ответ от: http://bugs.php.net/bug.php?id=43568
это мое рабочее решение для моей проблемы. Теперь я могу динамически использовать столько параметров, сколько хочу. Они будут того же числа, что и у меня в массиве, или как в этом случае я передаю идентификаторы из последнего запроса (который нашел все идентификаторы, где email = '[email protected]'), в динамический запрос, чтобы получить все информацию о каждом из этих идентификаторов, сколько бы я ни нуждался.
<?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 ? 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 ? 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();
?>
Ответ 6
Если вы используете только целые значения в предложении 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)
Но без сомнения решение здесь является более общим подходом к этой проблеме.
Ответ 7
Сегодня у меня была подобная проблема, и я нашел эту тему. Глядя на ответы и поиск по Google, я нашел довольно подходящее решение.
Хотя, моя проблема немного сложнее. Поскольку у меня есть фиксированные значения привязки и динамические значения.
Это решение.
$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 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=49946, PHP добавить один массив к другому (не array_push или +), [PHP]: Ошибка → Слишком мало аргументов в sprintf();, http://no2.php.net/manual/en/mysqli-stmt.bind-param.php#89171, Передать по ссылке проблема с PHP 5.3.1