I have a search form where users can enter a few pieces of information to search for records in the database. Due to the fact that some of the fields can be left blank, I am dynamically creating the WHERE clause of the query as well as dynamically binding the PDO parameters. Everything works great if the user only fills out 1 field in the search form but if more than 1 field is used then an empty array is returned. Here is my code.
if(count($_POST)>0)
{
//Remove any key that has no value
$data = array_filter($_POST);
//Define array to hold the pieces of the where clause
$where = array();
//loop each of the variable to build the query
foreach($data as $key=>$value)
{
$key = mysql_real_escape_string($key);
//Push values to array
array_push($where, "$key=:$key");
}
//Create the select query
$query = "SELECT application_ID,
student_last_name,
student_first_name,
s.school_name,
DATE_FORMAT(submission_datetime, '%m/%d/%Y %h:%i:%s %p') AS submission_datetime,
aps.name
FROM application a
LEFT JOIN application_status aps ON(aps.status_ID = a.application_status_ID)
LEFT JOIN schools s ON(s.school_ID = a.school_choice)";
//As long as criteria was selected in the search form then add the where clause to the query with user's search criteria
if(!empty($where))
{
$query .= "WHERE ".implode(" AND ", $where);
}
//Add ORDER BY clause to the query
$query .= " ORDER BY application_ID";
$stmt = $conn->prepare($query);
//loop each of the variables to bind parameters
foreach($data as $key=>$value)
{
$value = mysql_real_escape_string($value);
$stmt->bindparam(':'.$key, $value);
}
$stmt->execute();
$result = $stmt->fetchall(PDO::FETCH_ASSOC);
}
When I echo the query everything looks fine and even returns results when run from PHPMyAdmin. Here is the query.
SELECT application_ID,
student_last_name,
student_first_name,
s.school_name,
DATE_FORMAT(submission_datetime, '%m/%d/%Y %h:%i:%s %p') AS submission_datetime,
aps.name
FROM application a
LEFT JOIN application_status aps ON(aps.status_ID = a.application_status_ID)
LEFT JOIN schools s ON(s.school_ID = a.school_choice)
WHERE school_choice=:school_choice AND status_ID=:status_ID
ORDER BY application_ID ASC
When I print_r I get an empty array.
Thanks for any help you can provide.
When you iterate through an array to bind values to the PDO statement you should use bindValue instead of bindParam.
When you say $stmt->bindparam(':'.$key, $value), the query will use the value of the variable $value as it is at the time of the query execution. Value of $value will be the last element of the array.
http://php.net/manual/en/pdostatement.bindvalue.php
I hope this helps.
You are not supposed to use mysql_real_escape_string() with prepared statements. And in fact, this function will not work if you don't have a mysql_connect() initialized, which you don't.
That must be why it all is failing, your calls to mysql_real_escape_string() are returning FALSE for everything.
Also, what makes you think array keys coming from $_POST are safe to be used in your SQL query? You are taking a serious risk of SQL injection here, don't ever do that.
Related
In my app, the user can type in an indefinite amount of categories to search by. Once the user hits submit, I am using AJAX to call my PHP script to query my DB and return the results that match what the user defined for the categories.
My category column is separated as so for each row: "blue,red,yellow,green" etc.
I have two questions:
How can I pass an array to MySQL (like so: [blue,yellow,green]) and then search for each term in the categories column? If at-least one category is found, it should return that row.
Can MySQL add weight to a row that has more of the categories that the user typed in, therefor putting it further to the top of the returned results? If MySQL cannot do this, what would be the best way to do this with PHP?
Thanks for taking the time and looking at my issue.
For the part 1 you can use the function below:
<?php
function createquery($dataarray){
$query="select * from table where ";
$loop=1;
foreach($dataarray as $data)
{
$query.="col='$data'";
if(count($dataarray)<$loop-1){
$query.=' or ';
}
$loop++;
}
return $query;
}
?>
This will return the long query.
use this some like this:
mysql_query("select * from table where category in (".implode($yourarray,',').")");
1)
Arrays are not passed to a MySQL database. What's past is a query which is a string that tells the database what action you want to preform. An example would be: SELECT * FROM myTable WHERE id = 1.
Since you are trying to use the values inside your array to search in the database, you could preform a foreach loop to create a valid SQL command with all those columns in PHP, and then send that command / query to the database. For example:
$array = array('blue', 'red', 'yellow', 'green');
$sql = "SELECT ";
foreach ($array as $value)
{
$sql .= $value.", ";
}
$sql .= " FROM myTable WHERE id = 1";
IMPORTANT! It is highly recommended to used prepared statements and binding your parameters in order not to get hacked with sql injection!
2)
You are able to order the results you obtained in whichever way you like. An example of ordering your results would be as follows:
SELECT * FROM myTable WHERE SALARY > 2000 ORDER BY column1, column2 DESC
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);
My database table has many columns.
I want to do a search based on multiple columns.
Sometimes it may not be the value of some columns.
How do these fields in sql query to be ineffective?
Thank you.
for examle:
$C1=$_POST[c1];
$C2=$_POST[c2];
SELECT * FROM mytable WHERE column1='$c1' AND column2='$c2'
i want if C2 be nulled, disable it from sql query.
One way is:
if(!$_POST[C2]){
SELECT * FROM mytable WHERE column1='$c1'
}
...
I want do it through sql query to do because My table has many columns.
First, you should never write queries with variables inside like that. Learn about PDO / mysqli and prepared statements.
Second, key references for an array should either be a string or integer; the expression $_POST[c1] will most likely cause a notice and implicit conversion to a string. It's better to write $_POST['c1'].
Third, and to answer your question, you can use isset() and strlen() to determine whether a value is "empty", i.e. empty string.
$params = array($_POST['c1']); // you should also check whether $_POST['c1'] is defined too
$sql = 'SELECT * FROM `table_name` WHERE column1 = ?';
if (isset($_POST['c2']) && strlen($_POST['c2'])) {
$sql .= ' AND column2 = ?';
$params[] = $_POST['c2'];
}
$stmt = $db->prepare($sql);
$stmt->execute($params);
Build an array of conditions by iterating through the POST values, adding a condition if the respective POST parameter is not empty:
$conditions = array();
foreach ($_POST as $key => $value) {
if (!empty($value)) {
$conditions[] =
$dbcolumn[$key] . " = '" . mysql_real_escape_string($value) . "'";
}
}
You will need an array $dbcolumn that matches POST variables to the database columns (or you have to provide some other means of translating between the two).
Now create a SQL query for the given conditions:
$query = 'SELECT * FROM mytable';
if (!empty($conditions)) {
$query .= ' WHERE ' . join(' AND ', $conditions);
}
Note that the extension that provides mysql_real_escape_string() is deprectaded. You should probably use some other extension to comunicate with the MySQL server and would than have to use the repsective call of the other extension.
This code not recomended, but if you realy want to do it on MySQL, you can use LIKE syntax like this:
SELECT * FROM mytable WHERE column1="$c1" AND column2="$c2%"
Add % character before or after $c2
Please don't do it!!
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
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