PDO params not passed but sprintf is - php

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.

Related

How to correctly use multiple SqlSelect set/add where functions

I'm piecing together an SQL query using SilverStripes SQLSelect class with some optional (frontend) filters.
When attempting to use addWhere() with parameters the query has no results. E.g:
$sql->useConjunction();
$sql->addWhere(['SiteTree.ClassName' => 'ResourcePage']);
if (self::$Filters['Tags'])
{
$sql->addLeftJoin('ResourcePage_Tags', 'ResourcePage_Tags.ResourcePageID = SiteTree.ID');
$sql->addWhere(['ResourcePage_Tags.ResourceTagID IN (?)' => implode(',', self::$Filters['Tags'])]);
}
if (self::$Filters['Types'])
{
$sql->addLeftJoin('ResourcePage_Types', 'ResourcePage_Types.ResourcePageID = SiteTree.ID');
$sql->addWhere(['ResourcePage_Types.ResourceTypeID IN (?)' => implode(',', self::$Filters['Types'])]);
}
If I treat it as a string, I do get the results (like I expect testing with manual SQL). E.g:
$sql->useConjunction();
$sql->addWhere("SiteTree.ClassName = 'ResourcePage'");
if (self::$Filters['Tags'])
{
$sql->addLeftJoin('ResourcePage_Tags', 'ResourcePage_Tags.ResourcePageID = SiteTree.ID');
$sql->addWhere('ResourcePage_Tags.ResourceTagID IN ('.implode(',', self::$Filters['Tags']).')');
}
if (self::$Filters['Types'])
{
$sql->addLeftJoin('ResourcePage_Types', 'ResourcePage_Types.ResourcePageID = SiteTree.ID');
$sql->addWhere('ResourcePage_Types.ResourceTypeID IN ('.implode(',', self::$Filters['Types']).')');
}
The generated (simplified) query found using $sql->__toString() is:
SELECT
DISTINCT SiteTree.*
FROM
SiteTree
LEFT JOIN
"ResourcePage_Tags" ON ResourcePage_Tags.ResourcePageID = SiteTree.ID
LEFT JOIN
"ResourcePage_Types" ON ResourcePage_Types.ResourcePageID = SiteTree.ID
WHERE
(SiteTree.ClassName = ?)
AND (ResourcePage_Tags.ResourceTagID IN (?))
AND (ResourcePage_Types.ResourceTypeID IN (?))
The parameters portion of the above output is an array:
'ResourcePage',
1 => '38'
2 => '5,4'
The omitted parts of the query is some MATCH AGAINST and LIMIT ORDER BY clauses that are always there.
What have I done wrong to get two different behaviours between string and the parameterised version?
The first example you had which was using in (?) and then passing an array of values will not execute correctly because the assumption is that ? is one value when using the prepare. Which means you are now querying for one ResourceTypeID that is equal to array_value1,array_value2. You therefore need to have a placeholder (?) per item in the self::$Filters['Tags'] array.
The framework does have a helper method to achieve this like so:
$placeholders = DB::placeholders(self::$Filters['Tags'])
$query->addWhere([
"ResourcePage_Types.ResourceTypeID IN ($placeholders)" => self::$Filters['Tags']
]);

pg_query_params and pg function on DB

I'm switching from pg_exec to pg_query_param and when I use a normal query everything it's ok but, I have a problem when I want to use a Postgres function on the database.
Here my actual code:
$sql = "select accsmap.prep_download_files('".$dataType."','{".
$severity."}','{".
$year."}','{".
$region."}')";
$result = pg_exec($connect,$sql);
$connect it's just the DB info to connect to it.
and Here my new code:
$result = pg_query_params($connect,"select accsmap.prep_download_files( ".
"$1" ." ,{ ". "$2" ." },{ ". "$3" ." },{ ". "$4" ." });",
array($dataType, $severity, $year, $region) );
I've tried a lot of ways to translate the $sql var into the query but always it returns me errors near , or { }
and here a sample of $sql
select accsmap.prep_download_files('for Accidents occuring in the following
Authorities','{1,2,3}','{2014,2016}','{E08000001,E06000028,E06000036}')
The function expect $severity, $year and $region as array.
The problem is the way you are handling the parameters. For example:
pg_query_params($dbconn, 'SELECT * FROM shops WHERE name = $1', array("Joe's Widgets"));
If you see the query, there is a column called name (character data type) and it should be single quoted name = 'name'. But since it is inside the pg_query_params, you just use name = $1 'cause the function escapes the parameter and adds the quotes.
Therefore, since you use a postgreSQL array, it fails in constructing the query because your parameters arent well constructed. If your variable $severity is like this $severity="1,2,3" it will try to add a escaped parameter to the reference returning something like this: {'1,2,3'}.
On the other hand, If one of the parameters is an array like in your case (array of ints being passed to a stored procedure), it must be denoted as a set within the array, not php array notation.
I can't test the code, but I believe that the correct way to do it should be:
$severity='{1,2,3}';
$year='{2014,2016}';
$region='{E08000001,E06000028,E06000036}';
$result = pg_query_params($connect,"select accsmap.prep_download_files($1, $2, $3, $4)",
array($dataType, $severity, $year, $region) );

PHP PDO query with parameters in array

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)

mySQL bind_param with IN(?) [duplicate]

This question already has answers here:
How can I bind an array of strings with a mysqli prepared statement?
(7 answers)
Closed 1 year ago.
Using bind_param on all my queries, I now want to use an IN(?) where the number of elements in the list can vary.
The SQLout function I'm using here basically does a $sql_db->prepare, ->bind_param, ->execute(), ->store_result(), ->bind_result
// the code below does not work as the query only matches on element 'a':
$locations = ('a','b','c','d','e');
SQLout ("SELECT Name FROM Users WHERE Locations IN (?)",
array('s', $locations), array(&$usrName));
// the code below does work as a brute-force method,
// but is not a viable solution as I can't anticipate the number of elements in $locations going forward:
SQLout ("SELECT Name FROM Users WHERE Locations IN (?,?,?,?,?)",
array('sssss', $locations[0],$locations[1],$locations[2],$locations[3],$locations[4]), array(&$usrName));
Has anyone come up with a more elegant solution to this?
This is one place placeholders fall on their faces. Minus the auto-escaping, they're almost literally just a string replacement operation internally, meaning that if you have WHERE Locations IN (?), and pass in 1,2,3,4, you'll get the equivalent of
WHERE Locations IN ('1,2,3,4') // note, it's a string, not individual comma-separated integers
logically equivalent to
WHERE Locations = '1,2,3,4' // again, just a string
instead of the intended
WHERE Locations = 1 OR Locations = 2 OR Locations = 3 OR Locations = 4
The only practical solution is to build your own list of comma-separated placeholders (?), e.g:
$placeholders = implode(',', array_fill(0, count($values), '?'));
$sql = "SELECT Name FROM Users WHERE Locations IN ($placeholders)";
and then bind your parameters are usual.
As Hazmat said, you need to build up the parameters and then pass them by calling call_user_func_array on the prepared statement, but slightly closer to working code than his example :)
//In the calling code
$queryString = "SELECT Name FROM Users WHERE Locations IN (";
$queryString .= getWhereIn($locations);
$queryString .= " )";
$parametersArray = array();
foreach($locations as $location){
$parameter = array();
$parameter[0] = 's'; //It's a string
$parameter[1] = $location;
$parametersArray[] = $parameter;
}
//This is a function in a class that wraps around the class mysqli_statement
function bindParameterArray($parameterArray){
$typesString = '';
$parameterValuesArray = array();
foreach($parameterArray as $parameterAndType){
$typesString .= $parameterAndType[0];
$parameterValuesArray[] = $parameterAndType[1];
}
$finalParamArray = array($typesString);
$finalParamArray = array_merge($finalParamArray, $parametersArray);
call_user_func_array(array($this->statement, "bind_param"), $finalParamArray);
}
function getWhereIn($inArray){
$string = "";
$separator = "";
for($x=0 ; $x<count($inArray) ; $x++){
$string .= $separator."?";
$separator = ", ";
}
return $string;
}
You can "build" in IN clause before you prepare/bind it.
$sql = 'SELECT Name FROM Users WHERE Locations IN (' . implode(array_fill(0, count($locations), '?')) . ')';
Then you can use call_user_func_array to bind the parameters, without ever knowing how many there are.
// Parameters for SQLOut
$params = array(
# The SQL Query
$sql,
# The params for bind_param
array(str_repeat('s', count($locations))),
# The params for bind_result
array(&$usrName)
);
// Add the locations into the parameter list
foreach($locations as &$loc){
// not sure if this is needed, but bind_param
// expects its parameters to be references
$params[1][] = &$loc;
}
// Call your function
call_user_func_array('SQLout', $params);
Note: This is untested
IN is usually slow and not prepared statement friendly. The better solution is to build a table of the items that would be in the IN and use a JOIN to get the same effect.
Has anyone come up with a more elegant solution to this?
Sure. I have.
Mysqli is practically unusable with prepared statements, especially with such complex cases.
So, it's better to get rid of prepared statements and implement your own placeholders with support of all real life cases that a developer can meet.
safeMysql (a creation of mine) has a solution you're looking for (and also solutions for a dozen other headaches as well).
In your particular case, it would be as easy as this single line of code
// the code works alright:
$locations = array('a', 'b', 'c', 'd', 'e');
$usrName = $db->getCol("SELECT Name FROM Users WHERE Locations IN (?a)", $locations);
Unlike ugly codes you can get while playing with some PHP and API functions (and still get disturbing warnings depends on the PHP version you're using at the moment), this code is neat and readable. This is an important matter. You can tell what this code does even after a year has passed.

MySQL where clause equals anything (SELECT * WHERE col = ANY_VALUE)

I'd like to create a query in MySQL that has an optional value. When the value is specified the query is filtered by that value, when the value is not all rows are returned. Here's the idea:
public function doQuery($item = 'ANY_VALUE') {
$query = "SELECT * FROM table WHERE item = ?";
db->fetchAll($query,array($item))
...
}
doQuery(); // Returns everything
doQuery($item='item1'); // Returns only rows where item = 'item1'
Is there an easy way to do this without creating two query strings depending on the value of $item?
As far as I know, no such "any" placeholder exists.
If you can use LIKE, you could do
SELECT * FROM table WHERE item LIKE '%'
if you can append a condition, you could nullify the item clause like this:
SELECT * FROM table WHERE item = ? OR 1=1
(won't work in your example though, because you are passing "item" as a parameter)
That's all the options I can see - it's probably easiest to work with two queries, removing the WHERE clause altogether in the second one.
This would probably work, but I*m not sure whether it's a good idea from a database point of view.
public function doQuery($item = 'ANY_VALUE') {
$query = "SELECT * FROM table WHERE item = ? OR 1 = ?";
db->fetchAll($query,array($item, ($item == 'ANY_VALUE' ? 1 : 0))
...
}
Better way to do this is first generate sql query from the parameter you need to bother on, and then execute.
function doQuery($params) {
$query = 'SELECT * FROM mytable ';
if (is_array($params) // or whatever your condition ) {
$query .= 'WHERE item = ' . $params[0];
}
$query .= ' ;';
// execute generated query
execute($query);
}
You cannot get distinct results without giving distinct query strings.
Using $q = "... WHERE item = '$item'" you DO create distinct query strings depending on the value of $item, so it is not that different from using
$q = "..." . ($item=='ANY_VALUE' ? something : s_th_else);.
That said I see two or three options:
use function doQuery($item = "%") { $query = "SELECT ... WHERE item LIKE '$item'"; ...}
But then callers to that function must know that they must escape a '%' or '_' character properly if they want to search for an item having this character literally (e.g. for item = "5% alcoholic solution", giving this as argument would also find "50-50 sunflower and olive oil non alcoholic solution".
use function doQuery($item = NULL) { $query = "SELECT ..."; if ($item !== NULL) $query .= " WHERE item = '$item' "; ...} (where I use NULL to allow any other string or numerical value as a valid "non-empty" argument; in case you also want to allow to search for NULL (without quotes) you must choose another "impossible" default value, e.g., [], and you must anyway use a distinct query without the single quotes which however are very important in the general case), or even:
use function doQuery($item = NULL) { if($item === NULL) $query = "SELECT ..."; else $query = "SELECT ... WHERE item = '$item' "; ...}, which is more to type but probably faster since it will avoid an additional string manipulation (concatenation of the first and second part).
I think the 2nd & 3rd options are better than the first one. You should explain why you want to avoid these better solutions.
PS: always take care of not forgetting the quotes in the SQL, and even to properly escape any special characters (quotes, ...) in arguments which can depend on user input, as to avoid SQL injections. You may be keen on finding shortest possible solutions (as I am), but neglecting such aspects is a no-no: it's not a valid solution, so it's not the shortest solution!

Categories