I would like to create a filter, and pass both category and the value into the sql query in the WHERE clause.
If i set manually the category, it gets the values and filters the results. But when I want to pass the category, it gives me this error.
Here is the code I use:
$('#filterEvents').click(function () {
document.location.href = 'events.php?filter=' + $('#eventFilterOption').val() + '&filterValue=' + $('#eventFilterInput').val();
});
And the PHP processing:
$category = $_GET['filter'];
$searchValue = $_GET['filterValue'];
$sql = "SELECT EV_Date, EV_KKZ, EV_CardNr, TE_Name, EV_Name, EV_SurName, EV_EventTyp FROM events1
INNER JOIN terminal1 ON terminal1.TE_IDX = events1.EV_FK_TermIDX
WHERE ".$category." = '" . $searchValue . "'
ORDER BY EV_Date DESC
LIMIT 2000";
print_r($sql);
$query = $DB->prepare($sql);
$query->execute();
$data = $query->fetchAll(PDO::FETCH_ASSOC);
You have a problem with getting the variables - the PHP script is not getting the values - so I believe you have a bug in your Javascript.
More importantly, this is not a safe way to run queries. Your PHP is taking variables passed by the user and inserting them directly into SQL - with no validation. It would be very easy to abuse this functionality to extract information or modify your database.
You should use the value of $category to select a known column and then use bind parameters to set $searchValue.
If you change the functionality a little you will be able to use the prepared query with bound params.
so say you have filters for colour and size at the moment looks like you would call urls like
events.php?filter=Colour&filterValue=Red
events.php?filter=Size&filterValue=Large
you could change to pass filter[name]=value
events.php?filterColour=Red
events.php?filterSize=Large
Javascript would look something like this
$('#filterEvents').click(function () {
document.location.href = 'events.php?filter' + $('#eventFilterOption').val() + '=' + $('#eventFilterInput').val();
});
The query could be rewritten as follows (assuming columns of Size and Colour)
$sql = "SELECT EV_Date, ....
WHERE
( '' = :colour OR Colour = :colour ) AND
( '' = :size OR Size = :size )
ORDER BY ....
and a call to bind the actual params added
$query = $DB->prepare($sql);
$query->bindParam(':colour', $_GET['filterColour'], PDO::PARAM_STR, 12);
$query->bindParam(':size', $_GET['filterSize'], PDO::PARAM_STR, 12);
$query->execute();
This allows you to use prepared queries and avoid risk of SQL injection
As a bonus (or bug) it could support multiple filter options at the same time
events.php?filterSize=Large&filterColour=Red
You have empty values here:
$category = $_GET['filter'];
$searchValue = $_GET['filterValue'];
You should debug the method how you are getting them in js.
BTW you will always see that error when open this page /events.php. Because these variables will be empty by default.
Related
I'm trying to implement a pretty basic search engine for my database where the user may include different kinds of information. The search itself consists of a couple of a union selects where the results are always merged into 3 columns.
The returning data however is being fetched from different tables.
Each query uses $term for matchmaking, and I've bound it to ":term" as a prepared parameter.
Now, the manual says:
You must include a unique parameter marker for each value you wish to pass in to the statement when you call PDOStatement::execute(). You cannot use a named parameter marker of the same name twice in a prepared statement.
I figured that instead of replacing each :term parameter with :termX (x for term = n++) there must be a be a better solution?
Or do I just have to bind X number of :termX?
Edit Posting my solution to this:
$query = "SELECT ... FROM table WHERE name LIKE :term OR number LIKE :term";
$term = "hello world";
$termX = 0;
$query = preg_replace_callback("/\:term/", function ($matches) use (&$termX) { $termX++; return $matches[0] . ($termX - 1); }, $query);
$pdo->prepare($query);
for ($i = 0; $i < $termX; $i++)
$pdo->bindValue(":term$i", "%$term%", PDO::PARAM_STR);
Alright, here is a sample. I don't have time for sqlfiddle but I will add one later if it is necessary.
(
SELECT
t1.`name` AS resultText
FROM table1 AS t1
WHERE
t1.parent = :userID
AND
(
t1.`name` LIKE :term
OR
t1.`number` LIKE :term
AND
t1.`status` = :flagStatus
)
)
UNION
(
SELECT
t2.`name` AS resultText
FROM table2 AS t2
WHERE
t2.parent = :userParentID
AND
(
t2.`name` LIKE :term
OR
t2.`ticket` LIKE :term
AND
t1.`state` = :flagTicket
)
)
I have ran over the same problem a couple of times now and I think i have found a pretty simple and good solution. In case i want to use parameters multiple times, I just store them to a MySQL User-Defined Variable.
This makes the code much more readable and you don't need any additional functions in PHP:
$sql = "SET #term = :term";
try
{
$stmt = $dbh->prepare($sql);
$stmt->bindValue(":term", "%$term%", PDO::PARAM_STR);
$stmt->execute();
}
catch(PDOException $e)
{
// error handling
}
$sql = "SELECT ... FROM table WHERE name LIKE #term OR number LIKE #term";
try
{
$stmt = $dbh->prepare($sql);
$stmt->execute();
$stmt->fetchAll();
}
catch(PDOException $e)
{
//error handling
}
The only downside might be that you need to do an additional MySQL query - but imho it's totally worth it.
Since User-Defined Variables are session-bound in MySQL there is also no need to worry about the variable #term causing side-effects in multi-user environments.
I created two functions to solve the problem by renaming double used terms. One for renaming the SQL and one for renaming the bindings.
/**
* Changes double bindings to seperate ones appended with numbers in bindings array
* example: :term will become :term_1, :term_2, .. when used multiple times.
*
* #param string $pstrSql
* #param array $paBindings
* #return array
*/
private function prepareParamtersForMultipleBindings($pstrSql, array $paBindings = array())
{
foreach($paBindings as $lstrBinding => $lmValue)
{
// $lnTermCount= substr_count($pstrSql, ':'.$lstrBinding);
preg_match_all("/:".$lstrBinding."\b/", $pstrSql, $laMatches);
$lnTermCount= (isset($laMatches[0])) ? count($laMatches[0]) : 0;
if($lnTermCount > 1)
{
for($lnIndex = 1; $lnIndex <= $lnTermCount; $lnIndex++)
{
$paBindings[$lstrBinding.'_'.$lnIndex] = $lmValue;
}
unset($paBindings[$lstrBinding]);
}
}
return $paBindings;
}
/**
* Changes double bindings to seperate ones appended with numbers in SQL string
* example: :term will become :term_1, :term_2, .. when used multiple times.
*
* #param string $pstrSql
* #param array $paBindings
* #return string
*/
private function prepareSqlForMultipleBindings($pstrSql, array $paBindings = array())
{
foreach($paBindings as $lstrBinding => $lmValue)
{
// $lnTermCount= substr_count($pstrSql, ':'.$lstrBinding);
preg_match_all("/:".$lstrBinding."\b/", $pstrSql, $laMatches);
$lnTermCount= (isset($laMatches[0])) ? count($laMatches[0]) : 0;
if($lnTermCount > 1)
{
$lnCount= 0;
$pstrSql= preg_replace_callback('(:'.$lstrBinding.'\b)', function($paMatches) use (&$lnCount) {
$lnCount++;
return sprintf("%s_%d", $paMatches[0], $lnCount);
} , $pstrSql, $lnLimit = -1, $lnCount);
}
}
return $pstrSql;
}
Example of usage:
$lstrSqlQuery= $this->prepareSqlForMultipleBindings($pstrSqlQuery, $paParameters);
$laParameters= $this->prepareParamtersForMultipleBindings($pstrSqlQuery, $paParameters);
$this->prepare($lstrSqlQuery)->execute($laParameters);
Explanation about the variable naming:
p: parameter, l: local in function
str: string, n: numeric, a: array, m: mixed
I don't know if it's changed since the question was posted, but checking the manual now, it says:
You cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on.
http://php.net/manual/en/pdo.prepare.php -- (Emphasis mine.)
So, technically, allowing emulated prepares by using $PDO_obj->setAttribute( PDO::ATTR_EMULATE_PREPARES, true ); will work too; though it may not be a good idea (as discussed in this answer, turning off emulated prepared statements is one way to protect from certain injection attacks; though some have written to the contrary that it makes no difference to security whether prepares are emulated or not. (I don't know, but I don't think that the latter had the former-mentioned attack in mind.)
I'm adding this answer for the sake of completeness; as I turned emulate_prepares off on the site I'm working on, and it caused search to break, as it was using a similar query (SELECT ... FROM tbl WHERE (Field1 LIKE :term OR Field2 LIKE :term) ...), and it was working fine, until I explicitly set PDO::ATTR_EMULATE_PREPARES to false, then it started failing.
(PHP 5.4.38, MySQL 5.1.73 FWIW)
This question is what tipped me off that you can't use a named parameter twice in the same query (which seems counterintuitive to me, but oh well). (Somehow I missed that in the manual even though I looked at that page many times.)
It's possible only if you enable prepared statement emulation. You can do it by setting PDO::ATTR_EMULATE_PREPARES to true.
A working solution:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
$query = "SELECT * FROM table WHERE name LIKE :term OR number LIKE :term";
$term = "hello world";
$stmt = $pdo->prepare($query);
$stmt->execute(array('term' => "%$term%"));
$data = $stmt->fetchAll();
User defined variables its one way to go and use a the same variable multiple times on binding values to the queries and yeah that works well.
//Setting this doesn't work at all, I tested it myself
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
I didn't wanted to use user defined variables at all like one of the solutions posted here. I didn't wanted also to do param renaming like the other solution posted here. So here it's my solution that works without using user defined variables and without renaming anything in your query with less code and it doesn't care about how many times the param is used in the query. I use this on all my project and it's works well.
//Example values
var $query = "select * from test_table where param_name_1 = :parameter and param_name_2 = :parameter";
var param_name = ":parameter";
var param_value = "value";
//Wrap these lines of codes in a function as needed sending 3 params $query, $param_name and $param_value.
//You can also use an array as I do!
//Lets check if the param is defined in the query
if (strpos($query, $param_name) !== false)
{
//Get the number of times the param appears in the query
$ocurrences = substr_count($query, $param_name);
//Loop the number of times the param is defined and bind the param value as many times needed
for ($i = 0; $i < $ocurrences; $i++)
{
//Let's bind the value to the param
$statement->bindValue($param_name, $param_value);
}
}
And here is a simple working solution!
Hope this helps someone in the near future.
I'm trying to implement a pretty basic search engine for my database where the user may include different kinds of information. The search itself consists of a couple of a union selects where the results are always merged into 3 columns.
The returning data however is being fetched from different tables.
Each query uses $term for matchmaking, and I've bound it to ":term" as a prepared parameter.
Now, the manual says:
You must include a unique parameter marker for each value you wish to pass in to the statement when you call PDOStatement::execute(). You cannot use a named parameter marker of the same name twice in a prepared statement.
I figured that instead of replacing each :term parameter with :termX (x for term = n++) there must be a be a better solution?
Or do I just have to bind X number of :termX?
Edit Posting my solution to this:
$query = "SELECT ... FROM table WHERE name LIKE :term OR number LIKE :term";
$term = "hello world";
$termX = 0;
$query = preg_replace_callback("/\:term/", function ($matches) use (&$termX) { $termX++; return $matches[0] . ($termX - 1); }, $query);
$pdo->prepare($query);
for ($i = 0; $i < $termX; $i++)
$pdo->bindValue(":term$i", "%$term%", PDO::PARAM_STR);
Alright, here is a sample. I don't have time for sqlfiddle but I will add one later if it is necessary.
(
SELECT
t1.`name` AS resultText
FROM table1 AS t1
WHERE
t1.parent = :userID
AND
(
t1.`name` LIKE :term
OR
t1.`number` LIKE :term
AND
t1.`status` = :flagStatus
)
)
UNION
(
SELECT
t2.`name` AS resultText
FROM table2 AS t2
WHERE
t2.parent = :userParentID
AND
(
t2.`name` LIKE :term
OR
t2.`ticket` LIKE :term
AND
t1.`state` = :flagTicket
)
)
I have ran over the same problem a couple of times now and I think i have found a pretty simple and good solution. In case i want to use parameters multiple times, I just store them to a MySQL User-Defined Variable.
This makes the code much more readable and you don't need any additional functions in PHP:
$sql = "SET #term = :term";
try
{
$stmt = $dbh->prepare($sql);
$stmt->bindValue(":term", "%$term%", PDO::PARAM_STR);
$stmt->execute();
}
catch(PDOException $e)
{
// error handling
}
$sql = "SELECT ... FROM table WHERE name LIKE #term OR number LIKE #term";
try
{
$stmt = $dbh->prepare($sql);
$stmt->execute();
$stmt->fetchAll();
}
catch(PDOException $e)
{
//error handling
}
The only downside might be that you need to do an additional MySQL query - but imho it's totally worth it.
Since User-Defined Variables are session-bound in MySQL there is also no need to worry about the variable #term causing side-effects in multi-user environments.
I created two functions to solve the problem by renaming double used terms. One for renaming the SQL and one for renaming the bindings.
/**
* Changes double bindings to seperate ones appended with numbers in bindings array
* example: :term will become :term_1, :term_2, .. when used multiple times.
*
* #param string $pstrSql
* #param array $paBindings
* #return array
*/
private function prepareParamtersForMultipleBindings($pstrSql, array $paBindings = array())
{
foreach($paBindings as $lstrBinding => $lmValue)
{
// $lnTermCount= substr_count($pstrSql, ':'.$lstrBinding);
preg_match_all("/:".$lstrBinding."\b/", $pstrSql, $laMatches);
$lnTermCount= (isset($laMatches[0])) ? count($laMatches[0]) : 0;
if($lnTermCount > 1)
{
for($lnIndex = 1; $lnIndex <= $lnTermCount; $lnIndex++)
{
$paBindings[$lstrBinding.'_'.$lnIndex] = $lmValue;
}
unset($paBindings[$lstrBinding]);
}
}
return $paBindings;
}
/**
* Changes double bindings to seperate ones appended with numbers in SQL string
* example: :term will become :term_1, :term_2, .. when used multiple times.
*
* #param string $pstrSql
* #param array $paBindings
* #return string
*/
private function prepareSqlForMultipleBindings($pstrSql, array $paBindings = array())
{
foreach($paBindings as $lstrBinding => $lmValue)
{
// $lnTermCount= substr_count($pstrSql, ':'.$lstrBinding);
preg_match_all("/:".$lstrBinding."\b/", $pstrSql, $laMatches);
$lnTermCount= (isset($laMatches[0])) ? count($laMatches[0]) : 0;
if($lnTermCount > 1)
{
$lnCount= 0;
$pstrSql= preg_replace_callback('(:'.$lstrBinding.'\b)', function($paMatches) use (&$lnCount) {
$lnCount++;
return sprintf("%s_%d", $paMatches[0], $lnCount);
} , $pstrSql, $lnLimit = -1, $lnCount);
}
}
return $pstrSql;
}
Example of usage:
$lstrSqlQuery= $this->prepareSqlForMultipleBindings($pstrSqlQuery, $paParameters);
$laParameters= $this->prepareParamtersForMultipleBindings($pstrSqlQuery, $paParameters);
$this->prepare($lstrSqlQuery)->execute($laParameters);
Explanation about the variable naming:
p: parameter, l: local in function
str: string, n: numeric, a: array, m: mixed
I don't know if it's changed since the question was posted, but checking the manual now, it says:
You cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on.
http://php.net/manual/en/pdo.prepare.php -- (Emphasis mine.)
So, technically, allowing emulated prepares by using $PDO_obj->setAttribute( PDO::ATTR_EMULATE_PREPARES, true ); will work too; though it may not be a good idea (as discussed in this answer, turning off emulated prepared statements is one way to protect from certain injection attacks; though some have written to the contrary that it makes no difference to security whether prepares are emulated or not. (I don't know, but I don't think that the latter had the former-mentioned attack in mind.)
I'm adding this answer for the sake of completeness; as I turned emulate_prepares off on the site I'm working on, and it caused search to break, as it was using a similar query (SELECT ... FROM tbl WHERE (Field1 LIKE :term OR Field2 LIKE :term) ...), and it was working fine, until I explicitly set PDO::ATTR_EMULATE_PREPARES to false, then it started failing.
(PHP 5.4.38, MySQL 5.1.73 FWIW)
This question is what tipped me off that you can't use a named parameter twice in the same query (which seems counterintuitive to me, but oh well). (Somehow I missed that in the manual even though I looked at that page many times.)
It's possible only if you enable prepared statement emulation. You can do it by setting PDO::ATTR_EMULATE_PREPARES to true.
A working solution:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
$query = "SELECT * FROM table WHERE name LIKE :term OR number LIKE :term";
$term = "hello world";
$stmt = $pdo->prepare($query);
$stmt->execute(array('term' => "%$term%"));
$data = $stmt->fetchAll();
User defined variables its one way to go and use a the same variable multiple times on binding values to the queries and yeah that works well.
//Setting this doesn't work at all, I tested it myself
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, TRUE);
I didn't wanted to use user defined variables at all like one of the solutions posted here. I didn't wanted also to do param renaming like the other solution posted here. So here it's my solution that works without using user defined variables and without renaming anything in your query with less code and it doesn't care about how many times the param is used in the query. I use this on all my project and it's works well.
//Example values
var $query = "select * from test_table where param_name_1 = :parameter and param_name_2 = :parameter";
var param_name = ":parameter";
var param_value = "value";
//Wrap these lines of codes in a function as needed sending 3 params $query, $param_name and $param_value.
//You can also use an array as I do!
//Lets check if the param is defined in the query
if (strpos($query, $param_name) !== false)
{
//Get the number of times the param appears in the query
$ocurrences = substr_count($query, $param_name);
//Loop the number of times the param is defined and bind the param value as many times needed
for ($i = 0; $i < $ocurrences; $i++)
{
//Let's bind the value to the param
$statement->bindValue($param_name, $param_value);
}
}
And here is a simple working solution!
Hope this helps someone in the near future.
Unless I am missing something very obvious, I would expect the values of $data1 and $data2 to be the same?? But for some reason when I run this scenario twice (its run once each function call so I'm calling the function twice) it produces different results.
Call 1: PDO = Blank, Sprintf = 3 rows returned
Call 2: PDO = 1 row, Sprintf = 4 rows (which includes the PDO row)
Can someone tell me what I'm missing or why on earth these might return different results?
$sql = "SELECT smacc.account as Smid,sappr.*,CONCAT('$domain/',filepath,new_filename) as Image
FROM `{$dp}table`.`territories` pt
JOIN `{$dp}table`.`approvals` sappr ON pt.approvalID = sappr.ID
JOIN `{$dp}table`.`sm_accounts` smacc ON pt.ID = smacc.posted_territory_id
LEFT JOIN `{$dp}table`.`uploaded_images` upimg ON pt.imageID = upimg.ID
WHERE postID = %s AND countryID = %s AND smacc.account IN (%s) AND languageID = %s";
echo sprintf($sql,$postID,$countryID,implode(',',$accs),$langID);
$qry1 = $db->prepare(str_replace('%s','?',$sql));
$qry1->execute(array($postID,$countryID,implode(',',$accs),$langID));
$data1 = $qry1->fetchAll();
print'<pre><h1>PDO</h1>';print_r($data1);print'</pre>';
$qry2 = $db->query(sprintf($sql,$postID,$countryID,implode(',',$accs),$langID));
$data2 = $qry2->fetchAll();
print'<pre><h1>Sprintf</h1>';print_r($data2);print'</pre><hr />';
The root of the problem is the implode(',',$accs) function.
While you are using sprintf() it will generate a coma separated list and that list will be injected into the query string.
The result will be something like this:
smacc.account IN (1,2,3,4,5)
When you are binding the same list with PDO, it handles it as one value (a string: '1,2,3,4,5'). The "result" will be something like this:
smacc.account IN ('1,2,3,4,5')
Note the apostrophes! -> The queries are not identical.
In short, when you are using PDO and binding parameters, you have to bind each value individually (you can not pass lists as a string).
You can generate the query based on the input array like this:
$query = ... 'IN (?' . str_repeat(', ?', count($accs)-1) . ')' ...
// or
$query = ... 'IN (' . substr(str_repeat('?,', count($accs)), 0, -1) . ')'
This will add a bindable parameter position for each input value in the array. Now you can bind the parameters individually.
$params = array_merge(array($postID, $countryID), $accs, array($langID));
$qry1->execute($params);
Yes as Kris has mentioned the issue with this is the IN part of the query. Example 5 on the following link helps fix this: http://php.net/manual/en/pdostatement.execute.php. I tried using bindParam() but that didn't seem to work so will use Example 5 instead.
What would be a safe way (eg bindParam, prepare()) to insert a dynamic where clause. This is sent to PHP via ajax. So something comes from ajax form with
.php?where=name&what=bob
or maybe
.php?where=type$what=clothes
Then in PHP after everything is set to variables eg
if(isset($_POST['where'])){
$where = $_POST['where'];
}
if(isset($_POST['what'])){
$what= $_POST['what'];
}
Then a function is run to retrieve data
function retrieveData($db, $where, $what){
$getData = $db->prepare("SELECT name, type, stuff FROM tbl WHERE :where = :what");
$getData->bindParam(':what',$what);
$getData->bindParam(':where',$where);
$getData->execute();
..............
}
When I run a query like this i always get the SQL error about
'WHERE name = bob"
So the values are passed but I guess the SQL is not valid?
Any help appreciated. Thanks.
I think the string should be quoted
...WHERE name = 'bob'....
so try like this
$getData->bindParam(':what',$what,PDO::PARAM_STR, 15);
$getData->bindParam(':where',$where,PDO::PARAM_STR, 15);
I need help with building a query, I have a form ( multiple dropdowns) and need to display a data table according to the user selection.
filters.php (on server side - ajax )
$criteria[0] = $_POST['Size'];
$criteria[1] = $_POST['Name'];
$criteria[2] = $_POST['Color'];
$criteria[3] = $_POST['Sku'];
$criteria[4] = $_POST['Features'];
$statement = $dbh->prepare("select * from table_name where Sku = :sku AND Size = :size AND Color = :color AND Features LIKE :features ");
$statement->execute(array(':name' => $criteria[1], ':Size'=> $criteria[0], ':color'=>$criteria[2], ':sku'=>$criteria[3], ':features'=> '%'.$features[4].'%'));
$statement->setFetchMode(PDO::FETCH_ASSOC);
$subrows = $statement->fetchAll();
// ect..
When all the criteria are selected ( all dropdowns have a value ) the call will return data , but what if the user only selects one, two or three dropdowns? It will return an empty table which is logic if using the query above.
I was thinking maybe I should try first to check if the variables are set, then perform for each scenario a query ? Im kind of lost how to implement it.
The idea is to have a small div with dropdowns next to the data table which allows filtering.
thanks
If statements dependent on the $_POST key may be the best way - and add the corresponding part to the query string:
i.e. :
$queryString = "select * from table_name where ";
if($_POST['SKU']){
$queryString .= "Sku = :sku";
}
Furthermore, to handle the "AND" parts :
$and = (count($_POST) > 1 ? 1 : 0);
and inside each if statement, the first line should be:
$queryString .= ($and ? "AND " : "");
Furthermore, I would suggest using an ORM, (Idiorm being my personal favorite, which I use in this example: )
$queryBuilder = ORM::for_table("table");
if($_POST['Sku'])
$queryBuilder = $queryBuilder->where("Sku", $_POST['Sku']);
and so on and so forth.
Check the desired variables to see if they contain data, if so you have to build the query. You can add to the array at the same time! For example:
$myarray = array();
if(!empty($criteria[0])){
$myarray["Size"] = $criteria[0];
}