Как бы вы написали подготовленный оператор MySQL на PHP, который каждый раз принимает разное количество аргументов? Пример такого запроса:
SELECT `age`, `name` FROM `people` WHERE id IN (12, 45, 65, 33)
Пункт IN будет иметь разное количество id при каждом запуске.
У меня есть два возможных решения, но я хочу посмотреть, есть ли лучший способ.
Возможное решение 1 Заставляет оператор принимать 100 переменных и заполнять остальные фиктивными значениями, которых гарантированно не будет в таблице; сделать несколько вызовов для более 100 значений.
Возможное решение 2 Не использовать подготовленный оператор; построить и запустить запрос, строго проверяя возможные атаки с использованием инъекций.
Я согласен, я думал, что эти идеи могут стимулировать умы других. :)
Для SQL Server см. Параметризация предложения SQL IN?






Я могу придумать пару решений.
Одним из решений может быть создание временной таблицы. Сделайте вставку в таблицу для каждого параметра, который у вас будет в предложении 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. DBI Perl имеет функцию prepare_cached (), поэтому, если вы запрашиваете аналогичное количество заполнителей, он будет повторно использовать дескрипторы операторов. Хотя насчет PHP не уверен ...
Создание временной таблицы было бы излишним, если набор данных не огромен. Пример кода слишком сложен (половину этих переменных можно исключить, а код можно значительно упростить, выполнив большую часть работы в режиме реального времени). Но основная идея хороша, используя implode (',', array_fill (0, count ($ params), '?')) Для генерации знаков "?", А затем просто передайте $ params в качестве данных для привязки .
@orrd, meh ... Когда я писал этот ответ, я старался сделать его подробным, чтобы его было легко понять. Я согласен с тем, что его можно упростить, но я не думаю, что предложенное вами упрощение упростит понимание ответа или окажет какое-либо существенное влияние на производительность. В любом случае я обновлю свой ответ и добавлю для вас компактную версию.
Спустя столько лет нет лучшего способа, чем обойти это? Почему он не принимает массив ... Насколько я понял, call_user_func_array по-прежнему является наиболее "менее хакерским" способом сделать это ...
Пожалуйста, снимите №2 со стола. Подготовленные операторы - единственный способ защитить себя от SQL-инъекции.
Однако вы можете создать динамический набор переменных привязки. т.е. не делайте 100, если вам нужно 7 (или 103).
какие? это не имеет смысла. Он использует подготовленные операторы, но динамически устанавливает количество заполнителей.
В сценарии № 1 он статически определял запрос, принимающий 100 параметров, в сценарии № 2 он не использовал подготовленный оператор. Мое предложение заключалось в динамическом построении запроса с привязками, о чем вы говорите.
упс. Я читал №2 из stackoverflow.com/questions/327274/…. Извиняюсь!
достойные оболочки sql поддерживают привязку к значениям массива. т.е.
$sql = "... WHERE id IN (?)";
$values = array(1, 2, 3, 4);
$result = $dbw -> prepare ($sql, $values) -> execute ();
На самом деле я не знаю ни одной родной библиотеки доступа к базе данных PHP для MySQL (ни mysql, ни mysqli, ни PDO), которая позволяет связывать параметры типа массива.
Когда несколько лет назад я разрабатывал php, adodb мне очень помог. Я думаю, тебе стоит это проверить.
Любая структура, которая делает это, делает это, расширяя список и интерполируя его в запрос SQL перед подготовкой (). Это не то же самое, что и связанные параметры.
Если вы используете только целочисленные значения в предложении 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)
Но без сомнения решение здесь является более общим подходом к этой проблеме.
Почему решение «как сделать X с подготовленным оператором?» динамически построить другой запрос? Если вы используете подготовленные операторы для повторного использования кэшированных планов запросов, значит, вы это подорвали. Если вы делаете это, чтобы предотвратить SQL-инъекцию, ну, это другое.
я получил свой ответ от: 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 ?'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();
?>
Существует также FIND_IN_SET функция, второй параметр которого представляет собой строку значений, разделенных запятыми:
SELECT age, name FROM people WHERE FIND_IN_SET(id, '12,45,65,33')
Проблема в том, что он не будет использовать первичный индекс, по умолчанию используется полное сканирование таблицы при выполнении FIND_IN_SET для каждой строки.
У меня была похожая проблема сегодня, и я нашел эту тему. Глядя на ответы и ища в 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'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=49946, PHP добавляет один массив к другому (не array_push или +), [PHP]: Ошибка -> Слишком мало аргументов в sprintf ();, http://no2.php.net/manual/en/mysqli-stmt.bind-param.php#89171, Проблема передачи по ссылке в PHP 5.3.1
Я подозреваю, что я бы избегал обоих предлагаемых вами решений. Подготовленные операторы значительно упрощают защиту вашего кода. И ваше первое решение кажется довольно неэффективным и расточительным, если у вас есть небольшой список.