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

Большинство баз данных поддерживают концепцию подготовленных запросов. Что это такое? Это можно описать, как некий вид скомпилированного шаблона SQL запроса, который будет запускаться приложением и настраиваться с помощью входных параметров. У подготовленных запросов есть два главных преимущества:

  • Запрос необходимо однажды подготовить и затем его можно запускать столько раз, сколько нужно, причем как с теми же, так и с отличающимися параметрами. Когда запрос подготовлен, СУБД анализирует его, компилирует и оптимизирует план его выполнения. В случае сложных запросов этот процесс может занимать ощутимое время и заметно замедлить работу приложения, если потребуется много раз выполнять запрос с разными параметрами. При использовании подготовленного запроса СУБД анализирует/компилирует/оптимизирует запрос любой сложности только один раз, а приложение запускает на выполнение уже подготовленный шаблон. Таким образом подготовленные запросы потребляют меньше ресурсов и работают быстрее.
  • Параметры подготовленного запроса не требуется экранировать кавычками; драйвер это делает автоматически. Если в приложении используются исключительно подготовленные запросы, разработчик может быть уверен, что никаких SQL инъекций случиться не может (однако, если другие части текста запроса записаны с неэкранированными символами, SQL инъекции все же возможны; здесь речь идет именно о параметрах).

Подготовленные запросы также полезны тем, что PDO может эмулировать их, если драйвер базы данных не имеет подобного функционала. Это значит, что приложение может пользоваться одной и той же методикой доступа к данным независимо от возможностей СУБД.

Пример #1 Повторяющиеся вставки в базу с использованием подготовленных запросов

В этом примере 2 раза выполняется INSERT запрос с разными значениями name и value, которые подставляются вместо соответствующих псевдопеременных:

<?php
$stmt 
$dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name'$name);
$stmt->bindParam(':value'$value);

// вставим одну строку
$name 'one';
$value 1;
$stmt->execute();

// теперь другую строку с другими значениями
$name 'two';
$value 2;
$stmt->execute();
?>

Пример #2 Повторяющиеся вставки в базу с использованием подготовленных запросов

В этом примере 2 раза выполняется INSERT запрос с разными значениями name и valueкоторые подставляются вместо псевдопеременных ?.

<?php
$stmt 
$dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
$stmt->bindParam(1$name);
$stmt->bindParam(2$value);

// вставим одну строку
$name 'one';
$value 1;
$stmt->execute();

// теперь другую строку с другими значениями
$name 'two';
$value 2;
$stmt->execute();
?>

Пример #3 Выборка данных с использованием подготовленных запросов

В этом примере производится выборка из базы по ключу, который вводит пользователь через форму. Пользовательский ввод автоматически заключается в кавычки, поэтому нет риска SQL иньекции.

<?php
$stmt 
$dbh->prepare("SELECT * FROM REGISTRY where name = ?");
if (
$stmt->execute(array($_GET['name']))) {
  while (
$row $stmt->fetch()) {
    
print_r($row);
  }
}
?>

Если СУБД поддерживает выходные параметры, приложение может пользоваться ими также как и входными. Выходные параметры обычно используют для получения данных из хранимых процедур. Пользоваться выходными параметрами несколько сложнее, так как разработчику необходимо знать максимальный размер извлекаемых значений еще на этапе задания этих параметров. Если извлекаемое значение окажется больше, чем предполагалось, будет вызвана ошибка.

Пример #4 Вызов хранимой процедуры с выходными параметрами

<?php
$stmt 
$dbh->prepare("CALL sp_returns_string(?)");
$stmt->bindParam(1$return_valuePDO::PARAM_STR4000); 

// вызов хранимой процедуры
$stmt->execute();

print 
"процедура вернула $return_value\n";
?>

Можно задать параметр одновременно входным и выходным; синтаксис при этом тот же, что и для выходных параметров. В следующем примере строка 'привет' передается в хранимую процедуру, а затем эта строка будет заменена возвращаемым значением.

Пример #5 Вызов хранимой процедуры с входным/выходным параметром

<?php
$stmt 
$dbh->prepare("CALL sp_takes_string_returns_string(?)");
$value 'привет';
$stmt->bindParam(1$valuePDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT4000); 

// вызов хранимой процедуры
$stmt->execute();

print 
"процедура вернула $value\n";
?>

Пример #6 Неправильное использование псевдопеременной

<?php
$stmt 
$dbh->prepare("SELECT * FROM REGISTRY where name LIKE '%?%'");
$stmt->execute(array($_GET['name']));

// псевдопеременная может использоваться только в виде отдельного значения
$stmt $dbh->prepare("SELECT * FROM REGISTRY where name LIKE ?");
$stmt->execute(array("%$_GET[name]%"));
?>

Коментарии

Note that when using name parameters with bindParam, the name itself, cannot contain a dash '-'. 

example:
<?php
$stmt 
$dbh->prepare ("INSERT INTO user (firstname, surname) VALUES (:f-name, :s-name)");
$stmt -> bindParam(':f-name''John');
$stmt -> bindParam(':s-name''Smith');
$stmt -> execute();
?>

The dashes in 'f-name' and 's-name' should be replaced with an underscore or no dash at all.

See http://bugs.php.net/43130

Adam
2010-04-05 21:38:53
http://php5.kiev.ua/manual/ru/pdo.prepared-statements.html
Insert a multidimensional array into the database through a prepared query:
We have an array to write the form:

$dataArr:
Array
(
    [0] => Array
        (
            [0] => 2020
            [1] => 23
            [2] => 111111
        )
 
    [1] => Array
        (
            [0] => 2020
            [1] => 24
            [2] => 222222222
        )
....

Task: prepare a request and pass through binds
$array = [];
foreach ($dataArr as $k=>$v) {
// $x = 2020, the variable is predetermined in advance, does not change the essence
$array[] = [$x, $k, $v];
}
$sql = ("INSERT INTO `table` (`field`,`field`,`field`) VALUES (?,?,?)");

$db->queryBindInsert($sql,$array);

public function queryBindInsert($sql,$bind) {
        $stmt = $this->pdo->prepare($sql);
 
        if(count($bind)) {
            foreach($bind as $param => $value) {
                $c = 1;
                for ($i=0; $i<count($value); $i++) {
                    $stmt->bindValue($c++, $value[$i]);
                }
                $stmt->execute();
            }
        }
    }
2020-02-06 20:11:09
http://php5.kiev.ua/manual/ru/pdo.prepared-statements.html
it is a good practice not using double quotes in sql strings. This way you can ensure that no variable is injected in query.
a simple query with parameters should be;
'INSERT INTO REGISTRY (name, value) VALUES (?, ?)'
not
"INSERT INTO REGISTRY (name, value) VALUES (?, ?)"
2021-01-28 07:38:41
http://php5.kiev.ua/manual/ru/pdo.prepared-statements.html
Example #5 gives an 1414 wenn tried on MariaDB. Use this function to call a stored procedure with the last parameter as INOUT returning a value like a (uu)id or a count;

<?php
/**
 * call_sp Call the specified stored procedure with the given parameters.
 * The first parameter is the name of the stored procedure.
 * The remaining parameters are the (in) parameters to the stored procedure.
 * the last (out) parameter should be an int like state or number of affected rows.
 *
 * @param  mixed $sp_name The name of the stored procedure to call.
 * @param  mixed $params The parameters to pass to the stored procedure.
 * @return int The number of affected rows.
 */
function call_sp\PDO $dbstring $sp_name, ...$params ): mixed
{
 
$placeholders   array_fill0count$params ), "?" );
 
$placeholders[] = "@new_id";

 
$sql "CALL $sp_name( " implode", "$placeholders ) . " ); SELECT @new_id AS `new_id`";

  try {
   
LOG->debug"calling Stored Procedure", [ "sql" => $sql ] );

   
$stmt $db->prepare$sql );
   
$i    0;
    foreach( 
$params as $param ) {
     
$stmt->bindValue( ++$i$param );
    }
   
$stmt->execute();
   
$new_id $stmt->fetchPDO::FETCH_ASSOC )['new_id'];

    return 
$new_id;

  } catch ( 
\Exception $e ) {
   
LOG->error"Error calling Stored Procedure", [ "sql" => $sql"params" => $params"error" => $e->getMessage() ] );
    throw 
$e;
  }
2024-02-27 13:30:01
http://php5.kiev.ua/manual/ru/pdo.prepared-statements.html

    Поддержать сайт на родительском проекте КГБ