I have an array $authors with some numbers I want to insert into a table.
I can have a prepared statement to execute multiple times for each element from the array:
$stmt = $pdo->prepare('INSERT INTO authors (article_id, user_id) VALUES(?, ?)');
$stmt->bindParam(1, $article_id);
$stmt->bindParam($author);
foreach($authors as $author) {
$stmt->execute();
}
However, I can do a trick using implode() and execute the statement only once:
// here probably $authors = array_map('intval', $authors);
$stmt = $pdo->prepare(
'INSERT INTO authors (article_id, user_id)
VALUES ('.implode(', :article_id), (', $authors).', :article_id)');
$stmt->execute([':article_id' => $article_id]);
The first solution is more conventional and looks more securely.
The second (I think) is faster because there is only one query to the database. (and is shorter - there are no loops except in implode)
I don't see any security issues here but it looks safer (to me) when there are no string concatenations in queries.
Which is the proper way in this situation?
Edit: echo of the second query gives this:
INSERT INTO authors (article_id, student_id)
VALUES (121, :article_id), (50, :article_id)
And executes with no errors.
According to the PDO's doc "You cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on.". So that alone makes your "implode" solution bad.
That being said, I'll answer on the theory. The point of prepared statements is to compile the query only once, so repeated executions are faster. So prepared statements are meant to be used as in your first example : one simple "template" query, repeated many times.
In your second example, you make a custom query, that will hardly ever be repeated (since it's based on the content of your $authors array). Therefore, prepared statement in this case is completely useless, you have the overhead of the PREPARE without the benefits of repeated executions. It's not the way it's supposed to be used.
Extended insert is a perfectly valid solution, and a good one with that, but use it with normal query (i.e. exec()), and be sure to use quote() to protect against SQL-injection!
Related
I have read a lot of PHP sql examples that use MySQL. I often see queries like this that have placeholders for values:
$query = “INSERT INTO users
(username, firstname, lastname, salutation, countrycode)
VALUES(:1, :2, :3, :4, :5)”;
Notice the values are :1, :2 etc.
I use the standard PHP module for postgres in PHP. My queries inject variables names like this:
$query = “INSERT INTO users(username)
VALUES($hello)”;
How do I use the same placebolder technique with postgres in PHP? Is it possible?
Prepared queries cause two database communication roundtrips instead of one: one for prepare, one for execute. If you want to reuse the same query many times, yet only parse it once, they will be faster, but if you want to use the query once, they will be slower. Basically, prepared queries are the wrong answer to your problem ; the correct answer is parameterized queries.
$result = pg_query_params($dbconn,
'SELECT * FROM users WHERE first_name = $1 AND last_name=$2',
array($firstname,$lastname));
This pg-specific function makes a parameterized query where placeholders of the form $number are replaced by the parameters with the corresponding one-based index in the array. Parameters are properly handled, and there is no risk of SQL injection.
I am trying to remove running MySQL queries in for loops inside some code.
But I am not sure how best to achieve that.
using PHP PDO, with named parameters, how do I store the queries and then run them as a batch after the loop? so the only thing that happens in the for loop is the queries get built, but not executed until after the loop has finished?
here is example code:
for($i=$rowcount; $i>0; $i--){
$id = $array[$i];
$query = "DELETE FROM table WHERE ID=:id AND Order=:order";
$stmt = $conn->prepare($query);
$stmt->execute(array('id' => $id, 'order' => $i));
}
My initial reaction would be: Why do this? What is your motivation? Simply avoiding SQL in loops, or do you have some sort of operational problems you want to avoid?
Your exact query would not be that easy to convert into a single query because you do have tuples of ID and Order values that have to match in order to be deleted. Also notice that prepared statements usually do not accept an arbitrary number of parameters, so even if you'd be able to transform your query into the form DELETE FROM table WHERE (ID=1 AND Order=1000) OR (ID=4 AND Order=1234)..., you'd have to somehow work out how to fill the first, second, third ... placeholder. Additionally, you'd be forced to generate that prepared statement dynamically, which is probably the opposite of how prepared statements should be done when it comes to security.
If you have performance problems because deleting 1000 entries one after the other has a big impact, there are alternatives: If you wrap the deletion inside one single transaction, then it probably doesn't matter that much how many entries you delete - ALL of them will be deleted once the transaction is committed. Also note that using prepared statements is one way to speed up database operations - but only if you prepare them only once before you loop, and inside the loop you'd only pass new parameters again and again.
So to wrap it up: Undoing SQL in loops is not the best thing if the programming problem you want to solve is better solved using a loop, and there is no other problem related to it. If however there is such a problem, it has to be analyzed and mentioned - removing the loops isn't an automatic success story.
I suppose you need to ensure that all queries are executed, in other words you need a transaction:
$conn->beginTransaction();
try{
$query = "DELETE FROM table WHERE ID=:id AND Order=:order";
$stmt = $conn->prepare($query);
for($i=$rowcount;$i>0;$i--){
$id = $array[$i];
$stmt->execute(['id' => $id,'order'=>$i]);
}
$conn->commit();
}
catch(Exception $ex){
$conn->rollBack();
}
Our application in PHP / MySQL uses PDO prepared statements to insert data from user without any sanitizing. In the later stage user can copy created entries, thousand at the time.
For copying part we have tested:
$stmt = $pdo->prepare("INSERT INTO files (`a`,`b`,`c`) VALUES (?,?,?)");
$pdo->beginTransaction();
foreach ($data as $row) {
$stmt->execute($row);
}
$pdo->commit();
vs
$pdo->exec("INSERT INTO files (`a`,`b`,`c`) VALUES . implode(', ', $loop_query));
$row represent rows from database.
First one is 3 times slower than second. We would like to implement second approach.
How safe is using data from database without prepared statements?
It is not safe. As you mention, data on DB is raw.
If you retrieve it to the programming language (php in this case) and use again in a sql sentence it must be protected again against sql injecton.
can't you do a insert (fields) select values instead?
It is not safe at all. Entirely.
I'm going to be switching my database class that I use in several sites/projects, from using a custom mysql_query method*, to using PDO and prepared statements. However I have a question first - do I want to use prepared statements everywhere? Even in places where the query will only be ran once? What about situations where I need to do something like:
INSERT INTO `table` (`column`, `column`) VALUES ('value','value'), ('value','value'),('value','value'), etc.
Should I use a single prepared statement (And a single VALUE), but execute it with different variables each time, or should I use the style above? If I do use a prepared statement here, how bad of a performance hit are we talking? Do I need to use transactions in this situation?
*My mysql_query method is similar to a prepared statement in that the user can call $mysql->Query("SELECT * FROM%sWHERE '%s'='%s'", $var, $var, $var), and the method auto-escapes everything with mysql_real_escape_string.
Prepared statements provide a good degree of protection against SQL injection, and they also provide a performance benefit for some types of query. Personally, I would use them everywhere.
If you discover that a particular query is causing performance problems, you can do some profiling to track down the cause of the problem, and then optimise the code or query as required. But don't try to micro-optimise before you have a problem.
As for transactions, just use them when you need them. For example, when you need to perform a sequence of all-or-nothing updates, where if one fails, the whole lot must fail. These can be useful for things like many-to-many relationships, where three tables must be updated, and you don't want partial relationships remaining if a failure occurs.
Use only PDO parameters to pass variables into the query.
You can use prepared statement for multiple insert as well:
$insertQuery = 'INSERT INTO table (col1, col2) VALUES ';
$insertQueryData = array();
$insertData = array();
foreach ($data as $record) {
$insertQueryData[] = '(?, ?)';
$insertData[] = $record['col1'];
$insertData[] = $record['col2'];
}
$insertQuery .= implode(', ', $insertQueryData);
$statement = $db->prepare($insertQuery);
$statement->execute($insertData);
You should do the prepared statement every time. But, you may want to write a small helper that: prepares, binds, and runs, the query in one shot without multiple lines of code to do it.
I have done my research and have decided to use prepared statements in my queries, all I ask if there is anything I should know, good or bad about switching to normal mysqli queries to prepared statements.
Also I don't understand the logic how the need for escaping bad characters is not needed?
Escaping bad characters is still needed, but the library does it automatically for all parameters you bind. It's just slightly more convenient, and prevents the programmer from forgetting to sanitize a value.
However, note that this automatism is limited to parameters!
The following query is safe, because bind_param() takes care of escaping:
$code = $_GET["code"];
$name= $_GET["name"];
$percentage= $_GET["percentage"];
$stmt = $mysqli->prepare("INSERT INTO items VALUES (?, ?, ?)");
$stmt->bind_param('iss', code, $name, $percentage);
$stmt->execute();
the following query is unsafe, because anything you put directly into the query will not be escaped automatically:
$tablename = $_GET["prefix"]."_items";
$code = $_GET["code"];
$name= $_GET["name"];
$percentage= $_GET["percentage"];
---- UNSAFE! ----
$stmt = $mysqli->prepare("INSERT INTO `$tablename` VALUES (?, ?, ?)");
$stmt->bind_param('iss', $code, $name, $percentage);
$stmt->execute();
that said, one shouldn't be using dynamic table names like shown in this example anyway. But the point stands: Be careful, even with parametrized queries!
The only downside I can think of is that you can't see the final query any more for debugging (because it gets assembled only on server side).
There are at least two advantages :
You don't have to deal with escaping values : it's done automatically (when using bound parameters, of course)
The statement is sent to the SQL server, prepared only once ; and, then, can be executed several times -- which is great for performances (the statement is parsed only once, even if executed lots of times)
If you use prepared statements with placeholders (? unnamed, or :name named) the values you insert there are automatically quoted.
Prepared statements get pre-compiled by the dbms-engine. So the query is only parsed once and on later calls it just replace the placeholders with the values.
Most people do confuse prepared statements with placeholders.
It's general idea of using placeholders is really great, while prepared statements is just a subset of placeholders with limited functionality.
Placeholders are great because:
they are safer because they do all the proper formatting (not silly "escaping"!)
they are easier to use because they do all the proper formatting automatically.
they are convenient as they do all the proper formatting only on the value that goes right into query, but not on the source variable.
As for the performance issues everyone is talking about, most of time prepared statements are slower than regular query. However the difference going to be unnoticeable in both cases.