zend framework: how to prepare and execute WHERE IN clause? - php

I want to prepare a statement for use inside a loop. When I try to execute the statement I see an error in the logs saying "Invalid parameter number: no parameters were bound'".
What's wrong with my code?
$itemSelectSql = "SELECT * FROM `tblItems` WHERE `itemID` IN (?)";
$itemSelectStmt = new Zend_Db_Statement_Mysqli($this->db_ro, $itemSelectSql);
while () {
...
$itemIds = array();
// populate $itemIds array
...
$itemSelectStmt->execute(array($itemIds));
}
EDIT:
I think I may have a bug in my set up which explains why whatever I try fails. I'm seeing this:
PHP Warning: call_user_func_array() expects parameter 1 to be a valid callback,
class 'PDOStatement' does not have a method 'bind_param' in
/var/www/lib/Zend/Db/Statement/Mysqli.php on line 204
EDIT:
I was using the wrong adapter. Should have been Zend_Db_Statement_Pdo :-)
Thanks for the replies.

? can't be replaced by an array, it has to be replaced by a scalar (thanks to comment for pointing out that this does not always mean string... brain-fart on my end). Let me know if this works better:
$itemSelectSql = "SELECT * FROM `tblItems` WHERE `itemID` IN ";
while () {
...
$itemIds = array();
// populate $itemIds array
...
// we need to have the same number of "?,"'s as there are items in the array.
// and then remove final comma.
$qs = rtrim(str_repeat("?,", count($itemIds)),',');
// create a statement based on the result
$itemSelectStmt =
new Zend_Db_Statement_Mysqli($this->db_ro, "$itemSelectSql ($qs)");
// bind to each of those commas.
$itemSelectStmt->execute($itemIds);
}

Have you tried doing this:
while () {
...
$itemIds = array();
// populate $itemIds array
$itemIds = implode(',' $itemIds);
$itemSelectStmt->execute(array($itemIds));
}
I'm not an expert of Zend_framework, but when you use statements, to the execute method you must
If you use positional parameters, or
those that are marked with a question
mark symbol ('?'), pass the bind
values in a plain array.
So i think you need to pass an array with one value and that value replace the "?" in the statement. In this case you need a comma separated string (if you ids are int) to replace (?) in the statement.
What you do if you do not implode the values is passing an array containing an array.

You might use FIND_IN_SET(str,strlist) instead of IN ():
mysql> select id, name from customer where find_in_set(id, '10,15,20');
+----+---------+
| id | name |
+----+---------+
| 10 | ten |
| 15 | fifteen |
| 20 | twelve |
+----+---------+
This way, you don't have to bind array with multiple values to IN(), you can pass list of IDs as a single comma-separated string. You can safely use PHP function implode() to generate string from array of IDs.
Though, I'm not sure if it has any impact on performance.
I took a look at output of explain, and it looks like find_in_set() is not able to use indexes, so generating queries with variable number of parameters should perform better.

Related

PHP Red Bean MySQL multi-value binding evaluation in getAll()

I have an array in php containing strings, which I want to use in a query with Red Bean MySQL in the following manner:
$someString = '\'abc\',\'def\',\'ghi\'';
R::getAll("select * from table where name not in (:list)", array(':list'=> $someString));
The problem is that the list is not being evaluated correctly no matter how I set the values in the array string, and the names abc, def, ghi are returned in the result. I've tried the following:
$someString = '\'abc\',\'def\',\'ghi\''
$someString = 'abc\',\'def\',\'ghi'
$someString = 'abc,def,ghi'
running the query in the SQL server manually works and I don't get those values returned, but running it within the php code with redbean is not working, and it seems that the list is not being interpreted correctly syntax-wise.
Can anyone shed some light on the matter?
Thanks to RyanVincent's comment I managed to solve the issue using positional parameters in the query, or more specifically, the R::genSlots function.
replaced the following:
$someString = '\'abc\',\'def\',\'ghi\'';
R::getAll("select * from table where name not in (:list)", array(':list'=> $someString));
with:
$someArray = array('abc', 'def', 'ghi');
R::getAll("select * from table where name not in (". R::genSlots($someArray) .")", $someArray);
This creates a $someArray length positions for parameters in the query, which are then filled with the values in the second parameter passed to the getAll function.
Notice that in this case I used a set content array (3 variables) but it will work dynamically with any length array you will use.
Furthermore, this can also work for multiple positions in the query, for example:
$surnameArray = array('smith');
$arr1 = array('john', 'pete');
$arr2 = array('lucy', 'debra');
$mergedVarsArray = array_merge($surnameArray,$arr1);
$mergedVarsArray = array_merge($mergedVarsArray,$arr2);
R::getAll("select * from table where surname != ? and name in (." R::genSlots($arr1).") and name not in (". R::genSlots($arr2) .")", $mergedVarsArray);
This code will effectively be translated to:
select * from table where surname != 'smith' and name in ('john','pete') and name not in ('lucy', 'debra')
Each '?' placed in the query (or generated dynamically by genSlots() ) will be replaced by the correlating positioned item in the array passed as parameter to the query.
Hope this clarifies the usage to some people as I had no idea how to do this prior to the help I got here.

PHP- Multiple variables in mysql query

I want to get the value from the Epic[2] column.
The mysql table looks like that:
ID | Epic[0] | Epic[1] | Epic[2] | Epic[3]
-----------------------------------------------
440 xy xy xy xy
I have three variables where the informations are stored.
$qualitybudget = "Epic"
$budgetType = "2"
$itemlevel = "440"
I tried the following:
$sql = "SELECT '$qualitybudget'['$budgetType'] FROM `randproppoints` WHERE id = '$itemlevel'";
Output looks like that:
SELECT 'Epic'['2'] FROM `randproppoints` WHERE id = '440'
As you see 'Epic'['2'] has the wrong syntaxing. The output for the working query should be like: Epic[2]
What is the right syntax handling in a mysql query like that?
Use
$sql = "SELECT `${qualitybudget}[$budgetType]` FROM `randproppoints` WHERE id = '$itemlevel'";
instead. This way, the column name is exactly formatted as you wish and encapsulated.
Note that the syntax ${xyz} has to be used to make it clear that only xyz is the name of the variable and that this is all to be printed here. Otherwise, PHP assumes it is an array and evaluates the following squared brackets for the same string insertion. Another example where this syntax has to be used is if for instance you had a variable $a = "thing"; and wanted
$b = "there are three $as";
to get "there are three things". It would not work as PHP would assume you address a variable $as, but the fix would seem like this:
$b = "there are three ${a}s";

PHP, MySQL - Return which value was used when input is an array?

I'm running a PDO query, something like:
$inputArr = array(val1, val2, val3, ...);
$qMarks = str_repeat('?,', count($inputArr) - 1) . '?';
$stmt = $db->prepare("SELECT id, name, type, level
FROM table
WHERE name IN ($qMarks)");
$stmt->execute($inputArr);
... parse the rows that have been returned
And this works exactly as expected, no hang-ups or anything.
My problem is that I need to know which value from $inputArr was used to get each row returned.
I've tried
WHERE name IN ($qMarks) AS inputVal
and
WHERE name IN ($qMarks AS inputVal)
but those crash the query.
How can I determine which input array value was used to return each row in the output?
EDIT 1
Yes, I understand that the input search value would be name, for this particular case, but the query above is only for demonstration purposes of how I am putting the search values into the query.
The actual is much more complex, and returns any name value with is close (but not always identical).
The AS keyword is not going to work as you expect it. It's mainly used for aliasing subqueries. You can't (to my knowledge) use it in a WHERE clause.
The scenario you've outlined should have the 'name' in $row['name']. If it was a different variable that you wanted to see, you'd simply add it in your SELECT clause.
Great question, and simple answer:
The WHERE name IN $qMarks)"); part of your code is only obtaining the values in your database that are matching your array, so what you can do is see which values of name are present in the row you fetched. For example:
$rows_fetched = $stmt->fetchAll(PDO::FETCHASSOC);
$inputArray = array();
foreach($rows_fetched as $value)
{
$inputArray[] = $value['name'];
}
print_r($inputArray);//printing the results
Now you have the array $inputArray with all the values used to return each row in the output. Let me know if that worked for you!

PHP - PDO not taking imploded array as question mark parameter for IN clause in SELECT query

I have a problem with a question mark parameter in a prepared statement using PDO. My Query class looks like this (for now, I'm still adding functionality like data limits, custom parameters filtering and automatic detection of supported statements for the driver being used):
// SQL query
class Query {
public $attributes;
// constructor for this object
public function __construct() {
if ($arguments = func_get_args()) {
$tmp = explode(" ", current($arguments));
if (in_array(mb_strtoupper(current($tmp)), ["ALTER", "DELETE", "DROP", "INSERT", "SELECT", "TRUNCATE", "UPDATE"], true)) {
// classify the query type
$this->attributes["type"] = mb_strtoupper(current($tmp));
// get the query string
$this->attributes["query"] = current($arguments);
// get the query parameters
if (sizeof($arguments) > 1) {
$this->attributes["parameters"] = array_map(function ($input) { return (is_array($input) ? implode(",", $input) : $input); }, array_slice($arguments, 1, sizeof($arguments)));
}
return $this;
}
}
}
}
This is the code fragment which executes the query:
$parameters = (!empty($this->attributes["queries"][$query]->attributes["parameters"]) ? $this->attributes["queries"][$query]->attributes["parameters"] : null);
if ($query = $this->attributes["link"]->prepare($this->attributes["queries"][$query]->attributes["query"], [\PDO::ATTR_CURSOR => \PDO::CURSOR_FWDONLY])) {
if ($query->execute((!empty($parameters) ? $parameters : null))) {
return $query->fetchAll(\PDO::FETCH_ASSOC);
}
}
And this is how I call it in my test code:
$c1->addQuery("lists/product-range", "SELECT * FROM `oc_product` WHERE `product_id` IN (?);", [28, 29, 30, 46, 47]);
if ($products = $c1->execute("test2")) {
foreach ($products as $product) {
print_r($product);
}
}
The problem I have is I just see the first product (it's a test against a vanilla OpenCart installation) with id 28. As you can see in my code, if the passed parameter is an array, it gets automatically detected by the lambda I have in place in the Query class constructor, so it gets rendered as a string like 28,29,30,46,47.
Is there a missing parameter in PDO setup I'm missing? Or maybe there's some bug or platform limit in what I'm doing? I know there's some limitations on what PDO can do in regards to arrays, and that's why I pre-implode all arrays for them to be passed like a simple string.
There's some procedures I've seen here in SO which, basically, composes the query string like WHERE product_id IN ({$marks}), where $marks is being dynamically generated using a procedure like str_repeat("?", sizeof($parameters)) but that's not what I'm looking for (I could resort to that in case there's no known alternative, but it doesn't look like a very elegant solution).
My development environment is composed of: Windows 7 x64, PHP 5.4.13 (x86, thread-safe), Apache 2.4.4 (x86) and MySQL 5.6.10 x64.
Any hint would be greatly appreciated :)
A ? placeholder can only substitute for a single literal. If you want an IN clause to accept an arbitrary number of values, you must prepare a new query for each possible length of your array.
E.g., if you want to select ids in array [1, 2], you need a query that looks like SELECT * FROM tbl WHERE id IN (?,?). If you then pass in a three-item array, you need to prepare a query like SELECT * FROM tbl WHERE id IN (?,?,?), and so on.
In other words, you cannot know with certainty what query you want to build/create until the moment you have the data you want to bind to the prepared statement.
This is not a PDO limitation, it is fundamental to how prepared queries work in SQL databases. Think about it--what datatype would the ? be in SQL-land if you said IN ? but had ? stand in for something non-scalar?
Some databases have array-types (such as PostgreSQL). Maybe they can interpret IN <array-type> the same way as IN (?,?,...) and this would work. But PDO has no way of sending or receiving array-type data (there is no PDO::PARAM_ARRAY), and since this is an uncommon and esoteric feature it's unlikely PDO ever will.
Note there is an extra layer of brokenness here. A normal database, when faced with the condition int_id = '1,2,3,4' would not match anything since '1,2,3,4' cannot be coerced to an integer. MySQL, however, will convert this to the integer 1! This is why your query:
$pstmt = $db->prepare('SELECT * FROM `oc_product` WHERE `product_id` IN (?)');
$pstmt->execute(array('28,29,30,46,47'));
Will match product_id = 28. Behold the insanity:
mysql> SELECT CAST('28,29,30,46,47' AS SIGNED INTEGER);
+------------------------------------------+
| CAST('28,29,30,46,47' AS SIGNED INTEGER) |
+------------------------------------------+
| 28 |
+------------------------------------------+
1 rows in set (0.02 sec)
Lambda detects an array and creates coma delimited string from it, and passed argument is treated as string, so the query looks like:
SELECT * FROM tbl WHERE id IN('1,2,3,4')
'1,2,3,4' is one string value for SQL.
If you are expecting only numerical values, you can omit adding them as parameters and simply put them in the query:
$a = [28, 29, 30, 46, 47];
$s = "SELECT * FROM tbl WHERE id IN(".implode(',', array_map('intval', $a)).")";
For different data types, you have to add as many parameter placeholders as you need, and bind every parameter separately.

how to compare input array with mysql column?

Following is my code showing some error in mysql query:
<?php
$con=mysql_connect('localhost','root','');
$str=$_GET["message"];
$stor=explode(" ",$str);// converting message into array
mysql_select_db('words',$con);
for($j=0;$j<=30; $j++)
{
mysql_query($con,"UPDATE blacklist SET $stor=1 where $stor=0");//if column name=element in array then make it as 1 in database
}
mysql_close($con);
?>
Your code is vulnerable to SQL Injection. Read up on prepared statements and use PDO/MySQLi.
$stor is an array object and cant be used directly in the query. If you want to use it, try using
IN('.implode(",", $stor).')
the code above does the following:
implode() - takes an array and turns it into a comma separated string.
IN() - compares the given comma separated values and returns true if at least one of them exists.
Example (implode):
implode(",", array(1,2,3)) IS EQUAL TO "1,2,3"
Example (IN):
TestID IN (1,2,3) IS SAME AS (TestID = 1 OR TestID = 2 OR TestID = 3)
You're probably getting a mysql error because your query ends up looking like this
UPDATE blacklist SET Array=1 where Array=0;
If you're just echoing out a full array, you get Array instead, you'll need to specify an array element ($stor[1] for example).
What you'll want to do is replace your for loop with a foreach so that you can just throw out the elements one at a time.
Also, your arguments are backwards.
foreach($stor as $word)
{
mysql_query("UPDATE blacklist SET $word=1 where $word=0", $con);
}

Categories