PDO Binding in foreach fails at prepare - php

I would like to create a query that may or may not have more than one part. This means, I will be including an array and looping through it, and appending a query to the main SQL query, then finally prepare it.
First of all, I have defined the sql query
$sql = '';
then, I defined a foreach looping value
$arrayLoopValue = 0;
after that, I have created a foreach loop. In which I increased the arrayLoopValue, appended the sql with a new query based on the array's index.
foreach($questionsArray as $questionAnswerRow){
$arrayLoopValue = $arrayLoopValue + 1;
$sql = $sql .
'INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :survey_id_' . $arrayLoopValue .
', question_id = :question_id_' . $arrayLoopValue .
', user_email = :user_email_' . $arrayLoopValue .
', answer_type = :answer_type_' . $arrayLoopValue .
', question_answer = :question_answer_' . $arrayLoopValue .
', question_answer_creation_date = UTC_TIMESTAMP(); ';
}
The database / example for this query is NOT important, as all fields match and it's already empty. Only the structure, which is provided above, is required.
This fails at the following line.
$query = $this->conn->prepare($sql);
I tried to echo the query and see if there's something wrong. I got the following output:
INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :survey_id_1,
question_id = :question_id_1,
user_email = :user_email_1,
answer_type = :answer_type_1,
question_answer = :question_answer_1,
question_answer_creation_date = UTC_TIMESTAMP();
INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :survey_id_2,
question_id = :question_id_2,
user_email = :user_email_2,
answer_type = :answer_type_2,
question_answer = :question_answer_2,
question_answer_creation_date = UTC_TIMESTAMP();
Which is correct. After this prepare, there's a second foreach loop. But the function does NOT reach after the prepare statement.
I would like to know the reason. MYSQL says the following:
Uncaught exception 'PDOException' with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = ?, ques'

The name of the parameter is not the important thing.
If you want to run a query more than once then preparing it is a great idea but the parameter names are almost irrelevant. the important thing is the binding of new values to the paramters.
So lets assume this is your query
$sql = "INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :a,
question_id = :b,
user_email = :c,
answer_type = :d,
question_answer = :e,
question_answer_creation_date = UTC_TIMESTAMP()";
Notice the parameter names are irrelevant as long as they are something unique in the query string.
So now you prepare that query. This passes the basic query to the database where it is compiled and optimized, but is not actually run.
$stmt = $this->conn->prepare($sql);
Now within a loop that gets you the parameter you can run that prepared query 1000, 1,000,000 times if you like, all you have to do is bind new values to the parameters and execute the query, which passes the parameter values to the already prepared (compiled and optimized query) and runs it with the data you pass on the execute()
foreach($inArray as $array) {
// build the array of parameters and values
$params = [ ':a' => $array['field1']
':b' => $array['field2']
':c' => $array['field3']
':d' => $array['field4']
':e' => $array['field5']
];
// execute the prepared query using the new parameters
$stmt->execute($params);
}

here is the way to insert multiple rows of data, you prepare once and insert execute multiple times one prepared statement
$dbh = new PDO($dsn, $user, $pass, $options);
$arrayOfData = [...];
$stmt = $dbh->prepare('INSERT INTO table SET col = :val');
$dbh->beginTransaction();
foreach($arrayOfData as $data) {
$stmt->bindValue(':val', $data);
$stmt->execute();
}
$dbh->commit();

The idea of a prepared statement is that the statement is prepared once and then executed multiple times. Something like this:
$sql = 'INSERT INTO gosurveys_surveys_questions_answers
SET survey_id = :survey_id,
question_id = :question_id,
user_email = :user_email,
answer_type = :answer_type,
question_answer = :question_answer,
question_answer_creation_date = UTC_TIMESTAMP()';
$query = $this->conn->prepare($sql);
foreach($questionsArray as $questionAnswerRow) {
$query->execute([
":survey_id" => $questionAnswerRow["survey_id"],
// etc.
]);
}

Related

Move Data from one table to another table using PDO php

I know that this may been ask many times, I just can't find the proper keywords to search my problem, although I found several ways doing it on mysqli. Though I was wondering if anyone can help me how to this on PDO.
<?php
$dsn = 'mysql:host=localhost;dbname=dbsample';
$username = 'root';
$password = '';
$options = [];
try {
$connection = new PDO($dsn, $username, $password, $options);
}
catch(PDOException $e) {
$id = $_GET['id'];
$sql = 'INSERT INTO table2 SELECT * FROM table1 WHERE id=:id';
$sql. = 'DELETE FROM table1 WHERE id=:id';
$statement = $connection->prepare($sql);
if ($statement->execute([':id' => $id])) {
header("Location:.");
}
Update: here's the error i get
Parse error: syntax error, unexpected '='
I've tried removing $sql. = but only get another error at the end.
Also tried removing the ., and same error at end Parse error: syntax error, unexpected end of file in
PDO doesn't allow you to execute two queries in a single call. So you need to prepare two different queries, then execute each of them separately.
You should use a transaction to ensure that the database is consistent across the two queries.
$stmt1 = $connection->prepare('INSERT INTO table2 SELECT * FROM table1 WHERE id=:id');
$stmt2 = $connection->prepare('DELETE FROM table1 WHERE id=:id');
$connection->beginTransaction();
if ($stmt1->execute([':id' => $id]) && $stmt2->execute([':id' => $id])) {
$connection->commit();
header("Location:.");
} else {
$connection->rollBack();
}
first end each query with ; PDO allow multiple query but must each one must be properly declared and ternimated ..
$sql = 'INSERT INTO table2 SELECT * FROM table1 WHERE id=:id;';
$sql. = 'DELETE FROM table1 WHERE id=:id';
then if you have error again could be that the schema for the two table don't match (or don't match for insert select ) so try using an explict columns declaration
$sql = 'INSERT INTO table2 (col1, col2, ..,coln)
SELECT (col1, col2, ..,coln)
FROM table1
WHERE id=:id; ';
$sql. = ' DELETE FROM table1 WHERE id=:id';

Postgresql not binding in prepared statement for SELECT in PHP

<?php
try
{
global $db;
$user = 'postgres';
$password = '*****'; //For security
$db = new PDO('pgsql:host=localhost;dbname=dnd', $user, $password);
$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch (PDOException $ex)
{
echo 'ERROR!!: ' . $ex->getMessage();
die();
}
$table = htmlspecialchars($_REQUEST['table']);
$idNum = htmlspecialchars($_REQUEST['id']);
try {
//$query = "SELECT * FROM $table WHERE id = $idNum"; This works
//$query = "SELECT * FROM $table WHERE id = :number"; This works
$query = "SELECT * FROM :tableName WHERE id = :number";
$statement = $db->prepare($query);
$statement->bindValue(":tableName", $table, PDO::PARAM_STR);
$statement->bindValue(":number", $idNum, PDO::PARAM_INT);
$statement->execute();
$info = $statement->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $excep) {
echo "Opps: " . $excep->getMessage();
die();
}
Okay I'm going crazy here trying to get this to work.
I have a database set up that I need to query from. I receive the query from an AJAX request with the name of the table I want and the id for the item. When I try to query with both variables, the binding does not occur in the prepared statement and instead I get this error code
Opps: SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "$1" LINE 1: SELECT * FROM $1 WHERE id = 1 ^
When I have just the straight PHP variables it works fine so I know it can work, but when I want to bind multiple it seems to fail and give a variable number as above.
I can also get it to work if I simply have one of the variables bound, such as the second commented out query in the code - this only works tho if I have the variable I want at the end and not if I wanted to lookup the table spot. (I.E.
$query = "SELECT * FROM :tableName WHERE id = $idNum"; does not work)
I need to cleanse the variables to prevent SQL injection, but I can't do that without binding the variables in a prepared statement. Any help would be appreciated!
According to the PHP manual you can't bind a tablename. As you mentioned it, you can replace it by a variable, but you can't replace it with a placeholder.
So the only solution that will work for you is the query you have above:
$query = "SELECT * FROM $table WHERE id = :number"
This will be what you're looking for. If you want to make it safe for injection, you have to find another way. (Regex for example).
Ref: http://us3.php.net/manual/en/book.pdo.php#69304

PDO query with dynamic column names [duplicate]

This question already has answers here:
Insert/update helper function using PDO
(11 answers)
Closed 6 years ago.
i am upgrading one application from MySql to PDo, now the application is big so i don't want to write query every time, instead i am creating some insert, update, select etc. functions which accept dyanamic table name, with column and its value in array.
can any one sugest me how i can create this .
so far i have done is
$connection = new PDO("mysql:host=$host;dbname=$database;charset=utf8", "$user", "$password");
for select
$field = array("column1","column2");
$sql = "SELECT ".$fields." FROM ".$table." ".$whereSQL." ";
for inser
$col_val = array("column1"=>value, "column2"=>2);
$query = "insert into ".$table." (".$fields.") values (".$values.")";
$query = $connection->prepare($sql);
$query->execute();
i try to do all this but for an example in insert query i want to pass array as
$col_val = array("column1"=>value, "column2"=>2);
some code and function here which make PDO query easy and insert all column and value correctly.
i am also looking same way to perform Update query.
as you can see here tabel, column and value are totally dynamic which will be pass to function.
for this moment i am using all odd query with
$query = $connection->prepare($sql);
$query->execute();
Thank you in advance.
This is not a complete solution but that's the idea I think you could use to get closer to fix your issue.
$columns = array('column1', 'column2', 'column3')
$comma_separated = implode(",", $columns);
$columns_values = array(
'column1' => 'text1',
'column2' => 'text2',
'column3' => 'text3',
)
$values_query = "";
$index = 0
foreach ($columns as $column_name) {
if ($index == 0){
$values_query .= "'". $columns_values[$column_name]."'"
}else{
$values_query .= ", '". $columns_values[$column_name]
}
}
$query = "INSERT INTO table (". $comma_separated . ") VALUES (".$values_query.");";
Before executing the query you can use PDO to escape the string ($query) to avoid SQL injection

make PDO from string

I'm in the process of procedurally making a PDO statement, so far i've got
$sql = "UPDATE users SET ";
$values_array = array();
foreach($non_empty_fields as $key => $value){
$sql .= $key;
$sql .= " = :".$key.", ";
$values_array[':'.$key] = $value;
}
$sql = substr($sql, 0, -2);
$sql .= " WHERE id = :id";
$values_array[':id'] = $user_id;
$sth = $this->conn->prepare($sql);
print_r($non_empty_fields);
print_r($values_array);
echo($sql);
$sth -> execute($values_array);
$num_affected_rows = $sth -> affected_rows;
$sth -> close();
and when i run it, i get
Array
(
[gender] => female
[device_id] => 1213423489ydasxas98y76
)
Array
(
[:gender] => female
[:device_id] => 1213423489ydasxas98y76
[:id] => 35
)
UPDATE users SET gender = :gender, device_id = :device_id WHERE id = :id<br />
<b>Fatal error</b>: Call to a member function execute() on a non-object in <b>/Sites/api/include/DbHandler.php</b> on line <b>280</b><br />
I suspect its because I'm not setting the prepared statement properly, so my question is: how do you make a prepared statement from a string?
UPDATE:
call_user_func so the code now looks like:
$sql = "UPDATE users SET ";
$values_array = array();
foreach($non_empty_fields as $key => $value){
$sql .= $key;
$sql .= " = ?, ";
$values_array[] = &$value;
}
$sql = substr($sql, 0, -2);
$sql .= " WHERE id = ?";
$values_array[] = &$user_id;
$sth = $this->conn->prepare($sql);
$params = array_merge(array(str_repeat('s', count($values_array))), array_values($values_array));
call_user_func_array(array(&$sth, 'bind_param'), $params);
$sth -> execute();
now the error I'm getting isn't represented, pdo just doesn't update the table.
Oh Mysqli? i thought you said it was PDO. Mysqli doesnt support named markers like :id, you have to use question marks like ?
This parameter can include one or more parameter markers in the SQL statement by embedding question mark (?) characters at the appropriate positions.
Reference
And for PDO you can use either
Prepares an SQL statement to be executed by the
PDOStatement::execute() method. The SQL statement can contain zero or
more named (:name) or question mark (?) parameter markers for which
real values will be substituted when the statement is executed. You
cannot use both named and question mark parameter markers within the
same SQL statement; pick one or the other parameter style. Use these
parameters to bind any user-input, do not include the user-input
directly in the query.
Manual

Best way to avoid preparing the same PDO statement more than once?

Currently I save prepared statements into a private variable, because I ignore how they really work in the deepness, and do it just in case.
So the question is really simple, if I iterate over the same $PDO->prepare(), will it prepare again the same query?
foreach( $arr as $docid ) {
if( $this->dbLink === null ) { // PDO resource, saved in the object.
throw new Exception( 'Must first connect to DB' );
}
if( $this->queryCheckAccess === null ) {
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
}
else {
$result = $this->queryCheckAccess->execute(array(':id'=>$docid));
}
}
Will it matter ? Or the DB Engine / PHP is smart enough to know that it is the same prepared statement?
Thanks a lot.
----------------- EDIT --------------
I think I was misunderstood.
What I ask is what happens if I do:
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
And what happens if I do:
if( $this->queryCheckAccess === null ) {
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
}
Will the engine prepare the query 4 times in the first example? Or will notice it is the same query and just "jump" that?
Your code only prepares the query once, because after the first loop iteration, it's not NULL so it the conditional block won't run. But it's a waste of time to check the condition every time through the loop.
But to answer your question, if you prepare() the same query, it does do redundant work, even if the query is identical to the one you prepared before. So you should avoid that.
But you don't need to prepare inside the loop at all. Prepare once before you start the loop, and bind a variable to the parameter. You don't need to bind every time in the loop, just change the value of that variable.
if( $this->dbLink === null ) { // PDO resource, saved in the object.
throw new Exception( 'Must first connect to DB' );
}
$query = 'SELECT * from something where id = :id';
$this->queryCheckAccess = $this->dbLink->prepare($query);
$this->queryCheckAccess->bindParam(':id' => $docidparam);
foreach( $arr as $docid ) {
$docidparam = $docid;
$result = $this->queryCheckAccess->execute();
}
I'm not sure if you can bind the variable and also use it as the loop variable, there might be a scope conflict.
Another suggestion for this query: why not just run one query to search for a list of values?
$list = implode(",", array_fill(1, count($arr), "?"));
$query = "SELECT * FROM something WHERE id IN ( $list )";
$this->queryCheckAccess = $this->dbLink->prepare($query);
$this->queryCheckAccess->execute($arr);
PS: Also you should check for errors. If you enable PDO error mode EXCEPTION, then errors will automatically throw exceptions. If you don't enable that mode, you need to check the return value of prepare() and execute(), which return false if there's an error.
I just RUN a code similar to your example, and enabled MySQL Query LOG I found that all prepare requests are sent to MySQL Server
Prepare SELECT * FROM test_table WHERE username = ?
Close stmt
Prepare SELECT * FROM test_table WHERE username = ?
Close stmt
Prepare SELECT * FROM test_table WHERE username = ?
Close stmt
Prepare SELECT * FROM test_table WHERE username = ?
Close stmt
Test code:
$sth = $dbh->prepare($sql);
$sth = $dbh->prepare($sql);
$sth = $dbh->prepare($sql);
$sth = $dbh->prepare($sql);
$sth = $dbh->prepare($sql);
$sth->bindParam(1, $user);
$sth->execute();
Then, the best way is to prepare once, and Bind different values and then execute.
$sth = $dbh->prepare($sql);
$user = "test";
$sth->bindParam(1, $user);
$sth->execute();
$user = "test2";
$sth->bindParam(1, $user);
$sth->execute();
$user = "test";
$sth->bindParam(1, $user);
$sth->execute();
No, that's one of the main features of prepared statements. If you're going to run the same query multiple times but with different variables then preparing the query will give you a speed increase. Especially if you make use of transactions (requires InnoDB storage engine).
To answer the question from the title (which is quite different from questions in the body), the best way to avoid preparing the same statement more than once, apparently would be to avoid running multiple similar queries at all.
To answer the question from the question body - no, DB Engine / PHP is not "smart" enough to know that it is the same query were prepared again. With every new prepare() call another statement is created. And I would be first to hate such a "smart" behavior. The "smarter" your tool, the more unpredictable results you get.
To answer the real problem in the code, a smart developer would use a right tool to save himself a trouble.
With safeMysql whole mess will be reduced to one query and one line of code
$data = $this->dbLink->getAll('SELECT * from somth where id IN (?a)', $arr);
S0 - no multiple queries, no multiple preparations, no multiple questions.
By the way, you are losing first id with your code.
Yet you're losing all of them but last one if you don't use the result in place.

Categories