I'm trying to accomplish dynamic PDO prepared UPDATE statements using a customized array filled with $_POST data.The array structure looks like this:
$data = array();
$data[00001] = array("description"=>"item1", "stock"=>"100");
$data[00002] = array("description"=>"item2", "thr_alert"=>"20");
The mysql columns have the same name as the array keys. The 'id' is the main array key (00001, 00002, etc).
My first approach was the method described here: php.net pdo execute()
foreach($data as $itemId=>$value) {
$keys = array_keys($value);
$fields = '`'.implode('`, `',$keys).'`';
$placeholder = substr(str_repeat('?,',count($keys)),0,-1);
echo "INSERT INTO `baz`($fields) VALUES($placeholder)";
print_r(array_values($value)); //values to fill up execute() later on
echo "<br>";
}
unset($keys, $fields, $placeholder, $itemId, $value);
Nice approach but doesn't work, as this is designed for INSERT prepared statements and can't be used for UPDATE as the syntax should be UPDATE foo SET bar = :bar/? WHERE id = :id/?.Second approach:
$out = "UPDATE `baz` SET ";
foreach($data as $itemId=>$value) {
foreach($value as $column=>$foo) {
}
$out1 .= $column." = :$column, ";
$out2 = "WHERE id = :id";
}
$output = $out.rtrim($out1, ", ")." ".$out2."<br>";
echo $output;
Output:
UPDATE baz SET stock = :stock, description = :description WHERE id = :id
Notice that only the first array is displayed.
Any ideas on how to achive my goal ?
Move this line inside the second foreach loop
$out1 .= $column." = :$column, ";
But the result is awkward for the current data,since it will generate something like
...SET description = :description,description = :description....
Solved!I had to unset($out1); after $output is written and execute the $out1 line in the second foreach loop (see code below).
$out = "UPDATE `foo` SET ";
foreach($data as $itemId=>$value) {
foreach($value as $column=>$foo) {
$out1 .= $column." = :$column, ";
}
$out2 = "WHERE id = :id";
$output .= $out.rtrim($out1, ", ")." ".$out2."<br>";
unset($out1);
}
echo $output;
The next step will be to prepare the statement using $db->prepare($output); and pass the data to $db->execute($data);.
So to answer my own question - yes, dynamic prepared statements using one "global" array IS possible ! :-)
Related
I am fetching some data from MySQL database using PHP PDO prepared statement. I have to use if statement inside the execution of the prepared statement. See my codes below for better understanding
$query = "SELECT * FROM my_table WHERE 1=1";
if(isset($_GET['one'])){
$query .= " AND one = :one";
}
if(isset($_GET['two'])){
$query .= " AND two = :two";
}
if(isset($_GET['three'])){
$query .= " AND three = :three";
}
$result = $db->prepare($query);
$result->execute([
/* ------------------------------------
How to declare the above parameters here
as it will show error if any of the if statement is not true?
----------------------------------------*/
]);
I want to know how to declare the prepared array parameter using if statement inside the $result->execute(......]) block?
You need to create an empty $params array, and inside each if block you can push the appropriate value to it. For example:
if(isset($_GET['one'])){
$query .= " AND one = :one";
$params[':one'] = $_GET['one'];
}
Then you can simply do
$result->execute($params);
Note that you can based on what you've written, you could simplify your code with an outer foreach on a list of parameter names e.g.
$names= array('one', 'two', 'three');
$params = array();
foreach ($names as $name) {
if (isset($_GET[$name])) {
$query .= " AND $name = :$name";
$params[":$name"] = $_GET[$name];
}
}
$result->execute($params);
I have to update a mysql table with values that comes from array variable in size.
For that i was doing something like this but this code execute many query as the number of items on the array, and is hard to manage errors.
how i can execute only one query but still using a foreach loop?
the php version of server is so old does not support anything else than mysql_query.
foreach($decoded_array as $key=>$value)
{
if(!is_numeric($value))
{
$value = "'".$value."'";
}
echo $sql ="UPDATE table SET ".$key." = ".$value.";
$res = mysql_query($sql);
}
$set = "";
foreach($decoded_array as $key=>$value)
$set .= ($set ? "," : "") . $key . "=" . (is_numeric($value) ? $value : "'".$value."'");
$sql = "UPDATE table SET " . $set;
$res = mysql_query($sql);
But im not sure using "'".$value."'" is safe. I prefer mysqli function escape_string.
echo $sql ="UPDATE table SET ".$key." = ".$value.";
change this to this
$sql ="UPDATE table SET '".$key."' = '".$value."';
and this
$value = "'".$value."'";
to this
$value = '".$value."';
one more think mysql_ function are depricited.
use mysqli_ Function or PDO
For mysqli_ function check this link http://php.net/manual/en/book.mysqli.php
FOR PDO check this link http://php.net/manual/en/book.pdo.php
I'm updating my mysql functions to use PDO. I've got the hang of most of it but struggling with an update function to update multiple fields in a records.
The function is in a class and I'm trying to keep it flexible to reuse with other tables etc.
Here's my function so far:
public function dbUpdateRecord($table, $values, $where)
{
$this->conn();
$fieldNames = array_keys($values);
var_dump($fieldNames);
$set="";
foreach ($fieldNames as $field) {
$set .= " $field = :$field,";
}
//strip last comma
$set = substr($set, 0, strlen($set) - 1);
$wherefields = array_keys($where);
$whereCondition="";
foreach ($wherefields as $field) {
$whereCondition .= " $field = :$field AND";
}
//strip last AND
$whereCondition = substr($whereCondition, 0, strlen($whereCondition) - 3);
$sql = "UPDATE $table SET $set WHERE $whereCondition";
var_dump($sql);
$stmt = $this->db->prepare($sql);
foreach ($values as $field => $value) {
$stmt->bindParam(':$field', $value);
}
foreach ($where as $field => $value) {
$stmt->bindParam(':$field', $value);
}
return $stmt->execute();
}
The problem is all the fields in the record are being updated by the id of the record which is contained in the $where variable.
$values contains an array of (fieldname=>value).
I think the problem lies around the bindparam and trying to make the fieldnames/placeholders dynamic
I thought I needed to use bindparam as best practice - is this correct or can I just go to execute()?
ANy help appreciated
You are lifting this log from the wrong end.
Your approach is potentially insecure yet inflexible at the same time.
What if you need a JOIN based update? What if you need OR in the WHERE (or IN)?
What you really need is a conventional query where only SET statement values have to be generated.
So, you need a helper function to produce such a statement out of data array, returning both correctly formatted SET statement and array with variables to be bound:
$fields = array("name","email");
$sql = "UPDATE users SET ".pdoSet($fields,$values,$data)." WHERE id = :id"
// now we have $values array to be passed into query
$stmt = $dbh->prepare();
$values["id"] = $_POST['id'];
$stmt->execute($values);
With this code you'll be able to make updates for the arbitrary query. And make it safe.
As a further step you will need to start using type-hinted placeholders, to make whole code like this:
$db->query("UPDATE ?n SET ?u WHERE id IN(?a)",$table,$data,$ids);
Getting back to your problem, ONe is right - you need to use bindValue instead of bindParam (as it mentioned in the tag wiki)
I believe the problem is that you are using a foreach to bind the params to the query. Why is this a problem? Because when you bind a variable, you bind a reference to that variable, so if that variable changes, the value in the query will change too. Since you are using a foreach loop, the value for all the parameters will be the latest value that the variable $value referenced to.
You can read more about this foreach behavior here and here. So basically, you have 2 options:
Use a reference to the actual value, instead of using a reference to $value (which can change its value in the next iteration)
Use an auxiliar variable that references another memory position that won't change during the loop
I came here because I was having the same problems, and YCS's solution was what I needed. For anyone else in this situation, here's the helper function I ended up using:
function commit_table($record_id, $changed_values)
{
$db = open_database();
$query = 'UPDATE table SET ';
$query_arguments = array();
$is_first = TRUE;
foreach(array_keys($changed_values) as $key)
{
if($is_first)
{
$is_first = FALSE;
}
else
{
$query .= ', ';
}
$value_var = ':' . $key;
$query .= $key;
$query .= ' = ';
$query .= $value_var;
$query_arguments[$value_var] = $changed_values[$key];
}
$query .= ' WHERE record_id = :record_id';
$query_arguments[':record_id'] = $record_id;
$stmt = $db->prepare($query);
$stmt->execute($query_arguments);
close_database($db);
}
This question already has answers here:
Passing an array to a query using a WHERE clause
(17 answers)
Closed 1 year ago.
Hi I'm trying to compare values from 2 arrays with this query, which is the only way I know:
$session = "1,2,3,"
$table_name = "table1";
$column_name = "data1"; // value for test is 1,4,5,
$sql = "";
$sql .= "SELECT * FROM $table_name WHERE ";
$franquia = array();
$franquia = explode(",", $session);
if (!empty($franquia)) {
$final_id = array();
foreach ($franquia as $val) {
if (trim($val) != '') {
$final_id[] = $val;
}
}
$count_data = count($final_id);
foreach ($final_id as $key => $id) {
if ($id > 0) {
$sql .= " $id IN ($column_name) ";
if ($key < $count_data - 1) {
$sql .= "OR ";
}
}
}
}
echo $sql;
I have values 1,2,3 on $session and 1,4,5on $data1 so the comparison between $session and $data1 was suposed to return true due to both of them have value 1, but I don't get any results.
Actually it only works if both arrays are the same, like $session = 1,2,3 and $data1 = 1,2,3
What am I doing wrong?
You are using the IN clause wrong; In a nutshell, instead of
WHERE value IN (column)
The correct usage is:
WHERE column IN (value, value, value)
Which in turn will not help with what you are trying to do (more about the IN clause). Rather, try the following:
foreach ($final_id as $key => $id) {
if ($id > 0) {
$sql .= "$column_name LIKE %$id,%";
if ($key < $count_data - 1) {
$sql .= "OR ";
}
}
}
}
This should work, but there are a couple things you should be aware of. First you probably are vulnerable to SQL Injections, and second you should consider either using a different scheme with your data in your DB, since using LIKE %% to search for numeric values is a waste, or fetch in advance the entry from the DB and search in the resulting string. Consider the code above nothing but a quick and dirty hack that you should rather avoid :)
I like the flexibility of Dynamic SQL and I like the security + improved performance of Prepared Statements. So what I really want is Dynamic Prepared Statements, which is troublesome to make because bind_param and bind_result accept "fixed" number of arguments. So I made use of an eval() statement to get around this problem. But I get the feeling this is a bad idea. Here's example code of what I mean
// array of WHERE conditions
$param = array('customer_id'=>1, 'qty'=>'2');
$stmt = $mysqli->stmt_init();
$types = ''; $bindParam = array(); $where = ''; $count = 0;
// build the dynamic sql and param bind conditions
foreach($param as $key=>$val)
{
$types .= 'i';
$bindParam[] = '$p'.$count.'=$param["'.$key.'"]';
$where .= "$key = ? AND ";
$count++;
}
// prepare the query -- SELECT * FROM t1 WHERE customer_id = ? AND qty = ?
$sql = "SELECT * FROM t1 WHERE ".substr($where, 0, strlen($where)-4);
$stmt->prepare($sql);
// assemble the bind_param command
$command = '$stmt->bind_param($types, '.implode(', ', $bindParam).');';
// evaluate the command -- $stmt->bind_param($types,$p0=$param["customer_id"],$p1=$param["qty"]);
eval($command);
Is that last eval() statement a bad idea? I tried to avoid code injection by encapsulating values behind the variable name $param.
Does anyone have an opinion or other suggestions? Are there issues I need to be aware of?
I think it is dangerous to use eval() here.
Try this:
iterate the params array to build the SQL string with question marks "SELECT * FROM t1 WHERE p1 = ? AND p2 = ?"
call prepare() on that
use call_user_func_array() to make the call to bind_param(), passing in the dynamic params array.
The code:
call_user_func_array(array($stmt, 'bind_param'), array($types)+$param);
I made a filter function which recives an array an asociative array like $_GET:
In model class I've defined a couple of properties including the schema:
private $table_name = "products";
protected $schema = [
'id' => 'INT',
'name' => 'STR',
'description' => 'STR',
'size' => 'STR',
'cost' => 'INT',
'active' => 'BOOL'
];
Then a filter method which recive an asociative arrays of conditions:
function filter($conditions)
{
$vars = array_keys($conditions);
$values = array_values($conditions);
$where = '';
foreach($vars as $ix => $var){
$where .= "$var = :$var AND ";
}
$where =trim(substr($where, 0, strrpos( $where, 'AND ')));
$q = "SELECT * FROM {$this->table_name} WHERE $where";
$st = $this->conn->prepare($q);
foreach($values as $ix => $val){
$st->bindValue(":{$vars[$ix]}", $val, constant("PDO::PARAM_{$this->schema[$vars[$ix]]}"));
}
$st->execute();
return $st->fetchAll(PDO::FETCH_ASSOC);
}
And works great to filter results
You don't really need prepared statements and bound arguments, because you can always use mysql_real_escape_string(). And you're right; dynamically generated SQL is far more flexible and valuable.
Here's a simple example using the regular mysql_* interface:
// Array of WHERE conditions
$conds = array("customer_id" => 1, "qty" => 2);
$wherec = array("1");
foreach ($conds as $col=>$val) $wherec[] = sprintf("`%s` = '%s'", $col, mysql_real_escape_string($val));
$result_set = mysql_query("SELECT * FROM t1 WHERE " . implode(" AND ", $wherec);
Of course, this is a simplistic example, and to make it useful you have to build and refine it a lot, but it shows the ideas and it's really very very useful. For example, here is a completely generic function to insert a new row into an arbitrary table, with the columns filled with the values from an associative array and completely SQL-injection safe:
function insert($table, $record) {
$cols = array();
$vals = array();
foreach (array_keys($record) as $col) $cols[] = sprintf("`%s`", $col);
foreach (array_values($record) as $val) $vals[] = sprintf("'%s'", mysql_real_escape_string($val));
mysql_query(sprintf("INSERT INTO `%s`(%s) VALUES(%s)", $table, implode(", ", $cols), implode(", ", $vals)));
}
// Use as follows:
insert("customer", array("customer_id" => 15, "qty" => 86));