PDO Prepare Not Replacing Named Placeholders - php

I have tried using named placeholder to fill in the data as shown here:
$STH = $DBH->prepare("SELECT mixes.* FROM mixes JOIN(SELECT id FROM mixes WHERE id NOT IN ( :noredo_ids ) ORDER BY RAND() LIMIT 1) ips on mixes.id = ips.id");
$STH->bindParam(':noredo_ids', $_GET["noredo"]);
$STH->setFetchMode(PDO::FETCH_ASSOC);
$STH->execute();
As well as trying
$arr2["ids"] = $_GET["noredo"];
$STH = $DBH->prepare("SELECT mixes.* FROM mixes JOIN(SELECT id FROM mixes WHERE id NOT IN ( :ids ) ORDER BY RAND() LIMIT 1) ips on mixes.id = ips.id");
$STH->setFetchMode(PDO::FETCH_ASSOC);
$STH->execute($arr2);
But neither of those are working. But when I try to manually put the string in instead of using placeholders, it does work:
$arr2["ids"] = $_GET["noredo"];
$STH = $DBH->prepare("SELECT mixes.* FROM mixes JOIN(SELECT id FROM mixes WHERE id NOT IN (". $arr2['ids'] .") ORDER BY RAND() LIMIT 1) ips on mixes.id = ips.id");
$STH->setFetchMode(PDO::FETCH_ASSOC);
$STH->execute();
Should I manually escape the string? Am I missing something obvious? Thanks!

I am not certain since PHP's documentation on named placeholders for prepared statements is kinda vague about this. http://php.net/manual/en/pdostatement.bindparam.php
But if $_GET['noredo'] is an array of IDs you need to first implode(',',$_GET['noredo']) before passing into the prepared statement, I do not believe that the placeholder replacement is smart enough to flatten the array into a comma separated list acceptable for use in IN().
And in PHP docs, the replacement is a single value and not an array of values, so (and this is where I'm fuzzy) I don't believe that it looks for :named_placeholder in the array you pass it.
But then again, I've only used the ? placeholder for prepared statements...

A query parameter always takes the place of one single scalar value in an SQL expression.
So if you expect $_GET["noredo"] to be an array or a comma-separated list of values, what you're doing won't work. The query will be run as if you did this:
WHERE id NOT IN ( '1,2,3,4' )
A quoted string value that contains a comma-separated list is not the same as a series of comma-separated values. It's one string, and in a numeric context SQL will convert '1,2,3,4' into the scalar number 1.
So you need to use multiple placeholders if you want to bind multiple values in an IN() predicate.
$id_array = (array) $_GET["noredo"];
$placeholders = implode(",", array_fill(0,count($id_array),"?"));
$STH = $DBH->prepare("SELECT mixes.* FROM mixes JOIN(SELECT id FROM mixes
WHERE id NOT IN ( $placeholders ) ORDER BY RAND() LIMIT 1) ips on mixes.id = ips.id");
$STH->execute($id_array);

The second example doesn't work because you used $arr2["ids"] instead of $arr2[":ids"] as far as I can tell. That is, you still have to use the full parameter name.
Also, you should make sure that the values you try to insert are properly sanitized and cannot cause a syntax error.

Related

PHP MySQL Query don't deliver multidimensional Array

SOLVED: Reason Can I parameterize the table name in a prepared statement?
I have a very simple Query to collect data from a two column table in MySQL. Normally it worked but for some reason I know receive the error: Undefined offset: 1
$query_select = ("SELECT ?, ? FROM _HOOFDRUBRIEK");
$stmt = $mysqli->prepare($query_select);
$stmt->bind_param("ss", $column1, $column2);
$stmt->execute();
$stmt->store_result();
//$count = $stmt->num_rows;
//echo $count;
/die();
$stmt->bind_result( $key_hoofdrubriek ,
$descr_hoofdrubriek );
$stmt->fetch();
$hoofdrubriek[] = array('key' =>$key_hoofdrubriek ,
'descr' =>$descr_hoofdrubriek );
//Here I request the variable, what occurs the error
$var = $hoofdrubriek[1]['descr'];
echo 'Show here what's in the var: '.$var ;
Does anyone know why I get this error, because from my point of view, a multidimensional array can be called by $array_name[row][column];
You are mistinpreting how that works. Result bind parameters are just bound in order to the selected field. You still need to select normal fields as usual.
Moreover, you cannot specify field names as input parameters. In your situation, you select two constant values, namely the strings you pass as input parameters. This is why you get the field names in the result instead of the values of those fields. The parameters are just string values, so the query that is executed would look like this:
SELECT 'key_hoofdrubriek', 'descr_hoofdrubriek' FROM FROM RGS_HOOFDRUBRIEK
So, skip the question marks and the input bind parameters altogether and build the query like so:
$query_select = ("SELECT key_hoofdrubriek, descr_hoofdrubriek FROM RGS_HOOFDRUBRIEK");
Or, if you must, by using PHP variables in the statement:
$query_select = ("SELECT $column1, $column2 FROM RGS_HOOFDRUBRIEK");
For reading, you can of course still use bind_result.
You can't use placeholders for column names, they're always treated as expressions. So your prepared query is equivalent to writing:
SELECT 'key_hoofdrubriek', 'descr_hoofdrubriek' FROM RGS_HOOFDRUBIEK
This just returns those literal strings for each row in the table, not the values in the columns with those names.
If you need to determine the column names dynamically, you have to use variable substitution or concatenation, you can't use placeholders:
$query_select = "SELECT $column1, $column2 FROM RGS_HOOFDRUBRIEK";

Using PDO in PHP to count all from database and the 'WHERE' is a variable that has to be cleaned

I'm trying to count all of the rows from an item list where the id matches a user input. I am switching all of my code from mysql to PDO as I have learned it is much better.
The code below is what I found to work in my situation.
$id = '0';
$sql="SELECT count(*) FROM item_list WHERE item_id = $id";
$data=$connMembers->query($sql)->fetchcolumn();
echo $data;
However, It is not safe for a live site due to sql injections.
I want to know how can I change it to work whare it sanatizes the user input.
I would prefer using a prepare and execute functions so the variables are kept seperately.
So is there something I can do?
This is where you start binding parameters. I prefer to do it using ? and one array for inputs.
Assuming $connMembers is your PDO object:
$sql="SELECT COUNT(*) FROM item_list WHERE item_id = ?";
$input=array($id); //Input for execute should always be an array
$statement=$connMembers->prepare($sql);
$statement->execute($input);
$data=$statement->fetchObject();
var_dump($data);
To add more variables to your sql, just add another ? to the query and add the variable to your input.
$sql="SELECT COUNT(*) FROM item_list WHERE item_id = ? AND item_name=?";
$input=array($id, $name); //Input for execute should always be an array
$statement=$connMembers->prepare($sql);
$statement->execute($input);
$data=$statement->fetchObject();
var_dump($data);
OR you can use bindParam:
$sql="SELECT COUNT(*) FROM item_list WHERE item_id = :itemID";
$statement=$connMembers->prepare($sql);
$statement->bindParam(':itemID', $id);
/*Here I am binding parameters instead of passing
an array parameter to the execute() */
$statement->execute();
$data=$statement->fetchObject();
var_dump($data);

How do i select multiple rows which match the value of an item in an array?

I have written a php script that returns an arbitrary number of specific ids (which are in the format of numbers) in an array. I would like to make a new query that selects each row from a table that belongs to each id. I know i can do 1 query to get one row with the matching id. But i would like to do this all in one query. Here is my code:
$id = array(1,4,7,3,11,120); // The array containing the ids
$query = mysql_query("SELECT *
FROM posts
WHERE id = '$id[0]'");
// I would like to Do this for each id in the array and return it as one group of rows.`
I think you want the IN clause:
$idList = implode(",", $id);
SELECT *
FROM posts
WHERE id IN ( $idList );
The implode() function will turn your array of numbers into a comma-separated string of those same values. When you use it as part of an IN clause, it tells the database to use those values as a lookup table to match id against.
Standard Disclaimer/Warning:
As with any SQL query, you really shouldn't be directly concatenating variables into the query string. You're just opening yourself up to SQL injection. Use prepared/parameterized statements instead.
Use PHP's implode function to convert the array into a comma separated value string.
Then, you can use the SQL IN clause to run a single SQL statement containing the values associated with the ids you captured from PHP:
$id = array(1,4,7,3,11,120);
$csv = implode(',', $id);
$query = sprintf("SELECT *
FROM posts
WHERE id IN (%s)",
mysql_real_escape_string($csv));
$result = mysql_query($query)
I omitted the single quotes because they aren't necessary when dealing with numeric values in SQL. If the id values were strings, each would have to be encapsulated inside of single quotes.
What you want is SQL's IN clause.
SELECT * FROM posts WHERE id IN (1, 4, 7, 11, 120)
In PHP, you'll probably want something like:
$query = mysql_query(sprintf("SELECT * FROM posts WHERE id IN (%s)", implode(',', $id)));
Obviously, that's assuming you know you have integer values for $id, and that the values for $id didn't come from the user (that is, they should be sanitized). To be safe, you really ought to do something like:
$ids = implode(',', array_map('mysql_real_escape_string', $id));
$query = mysql_query("SELECT * FROM posts WHERE id IN ($ids)");
And if $id is dynamically generated, don't forget to put something in that IN clause, because SELECT * FROM foo WHERE bar IN () will give you an error. I generally make sure to set my IN-clause variables to 0, since IN (0) is good, and primary keys are pretty much never 0.

Use an array in a mysqli prepared statement: `WHERE .. IN(..)` query [duplicate]

This question already has answers here:
How can I bind an array of strings with a mysqli prepared statement?
(7 answers)
Closed 10 months ago.
Imagine we have a query:
SELECT * FROM somewhere WHERE `id` IN(1,5,18,25) ORDER BY `name`;
and an array of IDs to fetch: $ids = array(1,5,18,25)
With prepared statements it's adviced to prepare one statement and call it multiple times:
$stmt = $mysqli->prepare('SELECT * FROM somewhere WHERE `id`=?;');
foreach ($ids as $id){
$stmt->bind_params('i', $id);
$stmt->exec();
}
But now I'll have to sort the results manually. Do I have any nice alternatives?
you could do it this way:
$ids = array(1,5,18,25);
// creates a string containing ?,?,?
$clause = implode(',', array_fill(0, count($ids), '?'));
$stmt = $mysqli->prepare('SELECT * FROM somewhere WHERE `id` IN (' . $clause . ') ORDER BY `name`;');
call_user_func_array(array($stmt, 'bind_param'), $ids);
$stmt->execute();
// loop through results
Using this you're calling bind_param for each id and you have sorting done by mysql.
Had the same problem and in addition to the answer of #sled 7 years ago, here is a possibility without making the call_user_func_array(array($stmt, 'bind_param'), $ids); step, but only call bind_params once:
$ids = array(1,5,18,25);
// creates a string containing ?,?,?
$bindClause = implode(',', array_fill(0, count($ids), '?'));
//create a string for the bind param just containing the right amount of s
$bindString = str_repeat('s', count($ids));
$stmt = $mysqli->prepare('SELECT * FROM somewhere WHERE `id` IN (' . $bindClause . ') ORDER BY `name`;');
$stmt->bind_param($bindString, ...$ids);
$stmt->execute();
I believe this is the simplest possible answer :
$ids = [1,2,3,4,5];
$pdos = $pdo->prepare("SELECT * FROM somwhere WHERE id IN (:"
. implode(',:', array_keys($ids)) . ") ORDER BY id");
foreach ($ids as $k => $id) {
$pdos->bindValue(":". $k, $id);
}
$pdos->execute();
$results = $pdos->fetchAll();
So long your array of Ids does not contain keys or keys with illegal characters, it wil work.
For the task of executing a secure mysqli query with a dynamic number of incoming values to be fed into the sql string, a prepared statement is the professional technique to implement.
Let's assume that the incoming data payload is user-supplied data -- this means that we cannot guarantee the integrity of the data nor can we guarantee the volume of data. In fact, the expected array of data might be empty. The below snippet will demonstrate how to pass an array of ids to the IN () condition in the WHERE clause of a prepared statement. If there are no values in the array, then a prepared statement provides no benefit and should not be used.
MySQLi result set objects can be immediately iterated by a foreach() loop. Therefore, it is not necessary to make iterated fetch calls; just access the rows' data using array syntax.
The array of ids means that the sql will expect integer values. When calling bind_param(), the first parameter will be a single string of repeated i characters. For general use, if the data will be strings or you might have a mix of data types (e.g. integers, floats/doubles, or strings), then is simpler to just use repeated s characters instead of i characters.
Code: (PHPize.online Demo with SQL)
$ids = [1, 5, 18, 25]; // this could be, for example: $_GET['ids']
$count = count($ids);
$sql = 'SELECT name FROM somewhere';
$orderBy = 'ORDER BY name';
if ($count) {
$placeholders = implode(',', array_fill(0, $count, '?'));
$stmt = $mysqli->prepare("$sql WHERE id IN ($placeholders) $orderBy");
$stmt->bind_param(str_repeat('i', $count), ...$ids);
$stmt->execute();
$result = $stmt->get_result();
} else {
$result = $mysqli->query("$sql $orderBy"); // a prepared statement is unnecessary
}
foreach ($result as $row) {
echo "<div>{$row['name']}</div>\n";
}
Output from my PHPize demo:
<div>Alan</div>
<div>Bill</div>
<div>Chad</div>
<div>Dave</div>
If you don't need to iterate the result set for any reason, then you can fetch_all(). This is commonly used when immediately echoing or returning a json-encoded string (say, as the response to an ajax call). In this case, you replace the foreach() block with: (PHPize.online Demo with SQL)
echo json_encode($result->fetch_all(MYSQLI_ASSOC));
or simply dump the multidimensional array:
var_export($result->fetch_all(MYSQLI_ASSOC));
Output from my PHPize demo:
[{"name":"Alan"},{"name":"Bill"},{"name":"Chad"},{"name":"Dave"}]
From PHP8.1 and higher, it is no longer necessary to call bind_param() because the execute() method can receive the payload of parameters as an array (like PDO).
This means that...
$stmt->bind_param(str_repeat('i', $count), ...$ids);
$stmt->execute();
can be replaced with...
$stmt->execute($ids);
Here's a complete, basic example: (PHPize.online Demo)
$ids = [1, 2, 3, 4, 5];
$stmt = $mysqli->prepare("SELECT * FROM somewhere WHERE id IN (" . rtrim(str_repeat('?,', count($ids)), ',') . ") ORDER BY id");
$stmt->execute($ids);
var_export($stmt->get_result()->fetch_all(MYSQLI_ASSOC));
Topical Resources:
php.net
The RFC was authored by our very own Dharman ♦ and implemented as part of PHP8.1 after a unanimous vote on 2021-03-27.
phpbackend.com article from 24, October 2021
Reddit thread
PDO can do this concisely.
I'll add a slow & ugly solution which nevertheless uses prepared statements for ANY number of array items :) 3 statements are universal for any case and can be reused everywhere.
CREATE TEMPORARY TABLE `ids`( `id` INT );
INSERT INTO `ids` VALUES(?); this will insert your IDs
SELECT `id` FROM `ids` LEFT JOIN .... ; use data from other tables to sort the ids list
SELECT `id` FROM `ids`; select everything back
Otherwise you'll have to use IN (?,?,?,.... or sort the rows manually. The best idea is to use simple MySQL-queries, or, try to get the list of IDs already sorted in the way you like.
Have you considered rewriting you original query using a JOIN and WHERE clause to get the IDS you need to avoid the need for a WHERE IN clause? I came here with the same question and after reviewing the possible solutions I realized an INNER JOIN was my solution.
Copied from my answer here How to use PDO prepared statements with IN clause?
using named place holders
$values = array(":val1"=>"value1", ":val2"=>"value2", ":val2"=>"$value3");
$statement = 'SELECT * FROM <table> WHERE `column` in(:'.implode(', :',array_keys($values)).') ORDER BY `column`';
using ??
$values = array("value1", "value2", "$value3");
$statement = 'SELECT * FROM <table> WHERE `column` in('.trim(str_repeat(', ?', count($values)), ', ').') ORDER BY `column`';
An alternative would be to use PHP usort function on the result object, but this is "manual."
See this:
Sort Object in PHP

PDO binding values for MySQL IN statement [duplicate]

This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 2 years ago.
I have an issue with PDO that I'd really like to get an answer for after being plagued by it for quite some time.
Take this example:
I am binding an array of ID's to a PDO statement for use in a MySQL IN statement.
The array would be say: $values = array(1,2,3,4,5,6,7,8);
The database-safe variable would be $products = implode(',' $values);
So, $products would then be a STRING with a value of: '1,2,3,4,5,6,7,8'
The statement would look like:
SELECT users.id
FROM users
JOIN products
ON products.user_id = users.id
WHERE products IN (:products)
Of course, $products would be bound to the statement as :products.
However, when the statement is compiled and values bound, it would actually look like this:
SELECT users.id
FROM users
JOIN products
ON products.user_id = users.id
WHERE products IN ('1,2,3,4,5,6,7,8')
The problem is it is executing everything inside of the IN statement as a single string, given that I've prepared it as comma-separated values to bind to the statement.
What I actually need is:
SELECT users.id
FROM users
JOIN products
ON products.user_id = users.id
WHERE products IN (1,2,3,4,5,6,7,8)
The only way I can actually do this is by placing the values within the string itself without binding them, however I know for certain there has to be an easier way to do this.
This is the same thing as was asked in this question: Can I bind an array to an IN() condition?
The answer there was that, for a variable sized list in the in clause, you'll need to construct the query yourself.
However, you can use the quoted, comma-separated list using find_in_set, though for large data sets, this would have considerable performance impact, since every value in the table has to be cast to a char type.
For example:
select users.id
from users
join products
on products.user_id = users.id
where find_in_set(cast(products.id as char), :products)
Or, as a third option, you could create a user defined function that splits the comma-separated list for you (cf. http://www.slickdev.com/2008/09/15/mysql-query-real-values-from-delimiter-separated-string-ids/). This is probably the best option of the three, especially if you have a lot of queries that rely on in(...) clauses.
A good way to handle this situation is to use str_pad to place a ? for every value in the SQL query. Then you can pass the array of values (in your case $values) as the argument to execute:
$sql = '
SELECT users.id
FROM users
JOIN products
ON products.user_id = users.id
WHERE products.id IN ('.str_pad('',count($values)*2-1,'?,').')
';
$sth = $dbh->prepare($sql);
$sth->execute($values);
$user_ids = $sth->fetchAll();
This way you get more benefit from using prepared statements rather than inserting the values directly into the SQL.
PS - The results will return duplicate user ids if the products with the given ids share user ids. If you only want unique user ids I suggest changing the first line of the query to SELECT DISTINCT users.id
The best prepared statement you could probably come up with in a situation like this is something resembling the following:
SELECT users.id
FROM users
JOIN products
ON products.user_id = users.id
WHERE products IN (?,?,?,?,?,?,?,?)
You would then loop through your values and bind them to the prepared statement making sure that there are the same number of question marks as values you are binding.
you need to provide same number of ?s in IN as the number of values in your $values array
this can be done easily by creating an array of ?s as
$in = join(',', array_fill(0, count($values), '?'));
and use this $in array in your IN clause
THis will dynamically provide you with tailor made array of ?s as per your changiing $values array
You can do so very easily.
If you have an array of values for your IN() statement
EG:
$test = array(1,2,3);
You can simply do
$test = array(1,2,3);
$values = count($test);
$criteria = sprintf("?%s", str_repeat(",?", ($values ? $values-1 : 0)));
//Returns ?,?,?
$sql = sprintf("DELETE FROM table where column NOT IN(%s)", $criteria);
//Returns DELETE FROM table where column NOT IN(?,?,?)
$pdo->sth = prepare($sql);
$pdo->sth->execute($test);
If the expression is based on user input without binding the values with bindValue(), experimental SQL might not be a great choice. But, you can make it safe by checking the syntax of the input with MySQL's REGEXP.
For example:
SELECT *
FROM table
WHERE id IN (3,156)
AND '3,156' REGEXP '^([[:digit:]]+,?)+$'
Here is an example of binding an unknown number of record columns to values for an insert.
public function insert($table, array $data)
{
$sql = "INSERT INTO $table (" . join(',', array_keys($data)) . ') VALUES ('
. str_repeat('?,', count($data) - 1). '?)';
if($sth = $this->db->prepare($sql))
{
$sth->execute(array_values($data));
return $this->db->lastInsertId();
}
}
$id = $db->insert('user', array('name' => 'Bob', 'email' => 'foo#example.com'));
Please try like this:
WHERE products IN(REPLACE(:products, '\'', ''))
Regards

Categories