Подготовленные операторы MySQL со списком переменных переменного размера

Как бы вы написали подготовленный оператор MySQL на PHP, который каждый раз принимает разное количество аргументов? Пример такого запроса:

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

Пункт IN будет иметь разное количество id при каждом запуске.

У меня есть два возможных решения, но я хочу посмотреть, есть ли лучший способ.

Возможное решение 1 Заставляет оператор принимать 100 переменных и заполнять остальные фиктивными значениями, которых гарантированно не будет в таблице; сделать несколько вызовов для более 100 значений.

Возможное решение 2 Не использовать подготовленный оператор; построить и запустить запрос, строго проверяя возможные атаки с использованием инъекций.

Я подозреваю, что я бы избегал обоих предлагаемых вами решений. Подготовленные операторы значительно упрощают защиту вашего кода. И ваше первое решение кажется довольно неэффективным и расточительным, если у вас есть небольшой список.

Zoredache 29.11.2008 12:40

Я согласен, я думал, что эти идеи могут стимулировать умы других. :)

smarthall 29.11.2008 15:17

Для SQL Server см. Параметризация предложения SQL IN?

outis 04.04.2012 16:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
24
3
17 962
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Я могу придумать пару решений.

Одним из решений может быть создание временной таблицы. Сделайте вставку в таблицу для каждого параметра, который у вас будет в предложении 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);

мне нравится ваше второе предложение. сделайте это и забудьте об этом, пока производительность не станет проблемой. в этот момент, возможно, стоит изучить первый вариант.

benlumley 29.11.2008 12:46

Если бы я только подумал об этом! Ваше первое решение похоже на то, что я искал.

smarthall 29.11.2008 15:16

Я часто использовал схему №2. DBI Perl имеет функцию prepare_cached (), поэтому, если вы запрашиваете аналогичное количество заполнителей, он будет повторно использовать дескрипторы операторов. Хотя насчет PHP не уверен ...

Gary Richardson 30.11.2008 02:33

Создание временной таблицы было бы излишним, если набор данных не огромен. Пример кода слишком сложен (половину этих переменных можно исключить, а код можно значительно упростить, выполнив большую часть работы в режиме реального времени). Но основная идея хороша, используя implode (',', array_fill (0, count ($ params), '?')) Для генерации знаков "?", А затем просто передайте $ params в качестве данных для привязки .

orrd 15.01.2014 03:49

@orrd, meh ... Когда я писал этот ответ, я старался сделать его подробным, чтобы его было легко понять. Я согласен с тем, что его можно упростить, но я не думаю, что предложенное вами упрощение упростит понимание ответа или окажет какое-либо существенное влияние на производительность. В любом случае я обновлю свой ответ и добавлю для вас компактную версию.

Zoredache 15.01.2014 12:12

Спустя столько лет нет лучшего способа, чем обойти это? Почему он не принимает массив ... Насколько я понял, call_user_func_array по-прежнему является наиболее "менее хакерским" способом сделать это ...

Kerwin Sneijders 09.09.2018 01:31

Пожалуйста, снимите №2 со стола. Подготовленные операторы - единственный способ защитить себя от SQL-инъекции.

Однако вы можете создать динамический набор переменных привязки. т.е. не делайте 100, если вам нужно 7 (или 103).

какие? это не имеет смысла. Он использует подготовленные операторы, но динамически устанавливает количество заполнителей.

Gary Richardson 30.11.2008 02:35

В сценарии № 1 он статически определял запрос, принимающий 100 параметров, в сценарии № 2 он не использовал подготовленный оператор. Мое предложение заключалось в динамическом построении запроса с привязками, о чем вы говорите.

Dustin 30.11.2008 02:46

упс. Я читал №2 из stackoverflow.com/questions/327274/…. Извиняюсь!

Gary Richardson 02.12.2008 09:59

достойные оболочки sql поддерживают привязку к значениям массива. т.е.

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

На самом деле я не знаю ни одной родной библиотеки доступа к базе данных PHP для MySQL (ни mysql, ни mysqli, ни PDO), которая позволяет связывать параметры типа массива.

Stefan Gehrig 29.11.2008 13:09

Когда несколько лет назад я разрабатывал php, adodb мне очень помог. Я думаю, тебе стоит это проверить.

Eimantas 29.11.2008 13:14

Любая структура, которая делает это, делает это, расширяя список и интерполируя его в запрос SQL перед подготовкой (). Это не то же самое, что и связанные параметры.

Bill Karwin 29.11.2008 20:53

Если вы используете только целочисленные значения в предложении 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-инъекцию, ну, это другое.

Brandon 11.07.2014 03:57

я получил свой ответ от: 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 для каждой строки.

Hugo Maxwell 26.01.2015 23:56

У меня была похожая проблема сегодня, и я нашел эту тему. Глядя на ответы и ища в 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

Другие вопросы по теме