Related
I have a problem with setting up a PDO query.
My PDO query looks like:
$query= "
SELECT COUNT(k.id) AS total
FROM members k
INNER JOIN members_subscription p ON p.id = k.id
WHERE k.status=?
AND k.gender IN (?,?)
AND country= ?
AND pic1 !=""
AND galer !=?
AND video !=?
AND birthday < ?
AND birthday > ?
AND purposes in(?,?,?,?) ";
The function that executes this query:
$rows_pr = sql_pdo_funct($query, array($status.$gender_one.$gender_two.$location.$gal_prm.$video_prm.$year_old.$purposes ));
If I set static parameters like:
$rows_pr = sql_pdo_funct($query, array(7,2,5,1,0,0,1999-08-08,1992-08-08,1,2,3,4));
I get correct value as a query result.
But if I'm trying to add dynamic values in PHP like:
$status = '7';
$gender = ',2,5';
$location= ',1';
$gal_prm= ',0';
$video_prm= ',0';
$year_old= ',1999-08-08,1992-08-08';
$purposes_prm= ',1,2,3,4';
And put that in sql_pdo_funct function I get error message:
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY093]: Invalid parameter number' in...
The function call:
$rows_pr = sql_pdo_funct($query,array($status.$gender.$location.$gal_prm.$video_prm.$year_old.$purposes ));
Why this error occurs?
What am I doing wrong and how this can be done?
Thank you for any help and advice.
If you pass the params in function sql_pdo_funct() as like -
$rows_pr = sql_pdo_funct($query, array($status.$gender_one.$gender_two.$location.$gal_prm.$video_prm.$year_old.$purposes ));
Then you can't get from function sql_pdo_funct() like as -
$rows_pr = sql_pdo_funct($query, array(7,2,5,1,0,0,1999-08-08,1992-08-08,1,2,3,4));
You will get like (As a String). Because you concat the String
$rows_pr = sql_pdo_funct($query, array("7,2,5,1,0,0,1999-08-08,1992-08-08,1,2,3,4"));
Note : You should quoted your parameters, If you want to pass the params as String separated.
Update your params like as below. Because you have 12 ? query string in your SQL Query, You should pass 12 params
$status = '7';
$gender = '2',
$gender2 = '5';
$location = '1';
$gal_prm = '0';
$video_prm= '0';
$year_old = '1999-08-08';
$year_old2= '1992-08-08';
$purposes_prm = '1';
$purposes_prm1 = '2';
$purposes_prm2 = '3';
$purposes_prm3 = '4';
$rows_pr = sql_pdo_funct($query,
array(
$status,
$gender_one,$gender_two,
$location,
$gal_prm,
$video_prm,
$year_old,
$year_old2 #Added another birthday params, Because there is 2 birthday conditions
$purposes_prm,$purposes_prm1,$purposes_prm2,$purposes_prm3 #Added more 3 params, Because there is total 4 params in `IN`
)
);
Syntax error,
array($status.$gender_one.$gender_two.$location.$gal_prm.$video_prm.$year_old.$purposes )
Try using the , instead of the .
The . is for concatenation, adding strings together, the comma , is for separating array elements.
Easy mistake to make.
Once you see that the error makes total sense, as your essentially concatining all your data into one array item, and therefor you query is looking for 4 items yet you only sent one.
Invalid parameter number
I prefer the named placeholders, makes it easier to keep track of stuff. It quite easy to do, just change these ? to the names like :country for country and then the same in the input array [':country' => 1 ....] etc. It's easer to read then [1,24,5,2 ... bla bla
UPDATE
This strikes me as wrong $purposes_prm= ',1,2,3,4'; this is one item not 4
Yea this wont work
If I set static parameters like:
$rows_pr = sql_pdo_funct($query, array(7,2,5,1,0,0,1999-08-08,1992-08-08,1,2,3,4));
I get correct value as a query result. But if I'm trying to add dynamic values in PHP like:
$status = '7';
$gender = ',2,5';
Those commas and stuff, yea not gonna work like that. The first part is good the part after But if I'm trying that's because it's not even a valid array when added like that.
Just replace the static values with corresponding variables.
When you use static values in array it have eleven elements , then just simply substitute these static values to variables(don not concatenate these variables)
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.
I haven't worked with this method of prepared statements until recently and i am having a problem. I read some of the similar issues here on stackoverflow but they seem to refer to different things like duplicate markers (i'm using ? not :name so i presume that doesn't apply) and i have used xdebug in php storm to debug what is being passed.
The query is part of a filter i use in my script.
My querystring in the debugger shows this:
SELECT * FROM data_table WHERE (hud_game_type = ? OR hud_game_type = ? OR hud_game_type = ?) AND (hud_table_type = ? OR hud_table_type = ? OR hud_table_type = ?) AND (hud_table_size = ? OR hud_table_size = ? OR hud_table_size = ? OR hud_table_size = ?) AND approve = ? ORDER BY hud_downloads DESC
So there is clearly 11 x ?
$search_values in debugger shows:
0 = "Omaha"
1 = "Holdem"
2 = "All"
3 = "Cash"
4 = "Tourney"
5 = "All"
6 = "6max"
7 = "FR"
8 = "HU"
9 = "All"
10 = "1"
So again clearly 11 values in the array, here is the final part of the code:
Here is the code but as mentioned above the built string is above and it seems to be legit, since this code is relying on values passed in a form its built in another function, i can add that if relevant but i wouldn't think so since i show the outputted query above
$result = $database->resultset("
SELECT * FROM $data_table WHERE $search_game_type AND $search_table_type AND $search_table_size AND approve = ? $search_sort_by",(array($search_values)));
// returns an array of the results, first we execute and then fetch the results
public function resultset($query,$values){
$this->stmt = $this->dbh->prepare($query);
try{
// this handles situation where no params need to be escaped.
if($values == ""){
$this->stmt->execute();
} else{
$this->stmt->execute($values);
}
} catch (PDOException $e){
$this->error_db_query_failed(true,$values,$e->getMessage(),"Error #12");
}
Now the error returned from PDO is:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
This same resultset function seems to work fine in other parts of the code but i'm only passing one or two params in those.
Even if someone can point me in the right direction or suggest what the error might be i am happy to search further but a lot of the other errors refer to :name type binding so the process is different.
Since $search_values is already an array, you should just pass it directly to the function, don't use array($search_values).