I have an array of ids $friends = array(0001, 0002, 0003, 0004) and a database where table_name = friends, column_header = fid. fid in friends may or may not contain one of the friend IDs. I want to input $friends into the query, and return all of the present values that were both in $friends and in a row of fid.
I'm sure the fid={array_values($friends)} is wrong, but I don't know how to pass the WHERE portion an array of values...
//All DB_X's are defined in another file that is included in this actual file
$db = new PDO("mysql:host=".DB_SERVER.";dbname=".DB_NAME, DB_USER, DB_PASS);
$stmt = $db->prepare("SELECT fid FROM friends WHERE fid={array_values($friends)} ORDER BY fid ASC");
$stmt->execute();
$friendResults = $stmt->fetchAll();
You will need to make use of SQL's IN operator:
SELECT ... FROM ... WHERE foo IN (val1, val2, ...)
You can use PHP's implode() function to get the desired SQL bit:
$values = implode(', ', array_values($friends));
$query = "SELECT ... FROM ... WHERE fid IN ({$values})";
The above will work if your values are numeric. If they are strings, you'll have to modify the values before implode()ing:
$values = array_map(array_values($friends), function($value) {
return "'{$value}'"; // Here is where you could do sanitization
});
$values = implode(', ', $values);
PLEASE NOTE: You must properly sanitize the data in $friends to prevent SQL injection.
Related
My SQL looks something like this:
$sql = "select * from user where id in (:userId) and status = :status";
$em = $this->getEntityManager();
$stmt = $em->getConnection()->prepare($sql);
$stmt->bindValue(':userId', $accounts, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY);
$stmt->bindValue(':status', 'declined');
$stmt->execute();
$result = $stmt->fetchAll();
But it returns:
An exception occurred while executing (...)
with params
[[1,2,3,4,5,6,7,8,11,12,13,14], "declined"]
Notice: Array to string conversion
I cannot user queryBuilder because my real SQL is more complicated (ex. contains joined select, unions and so on)
You can't use prepared statements with arrays simply because sql itself does not support arrays. Which is a real shame. Somewhere along the line you actually need to determine if your data contains say three items and emit a IN (?,?,?). The Doctrine ORM entity manager does this for you automatically.
Fortunately, the DBAL has you covered. You just don't use bind or prepare. The manual has an example: https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion
In your case it would look something like:
$sql = "select * from user where id in (?) and status = ?";
$values = [$accounts,'declined'];
$types = [Connection::PARAM_INT_ARRAY, \PDO::PARAM_STR];
$stmt = $conn->executeQuery($sql,$values,$types);
$result = $stmt->fetchAll();
The above code is untested but you should get the idea. (Make sure you use Doctrine\DBAL\Connection; for Connection::PARAM_INT_ARRAY)
Note for people using named parameters:
If you are using named parameters (:param instead of ?), you should respect the parameter names when providing types. For example:
$sql = "select * from user where id in (:accounts) and status = :status";
$values = ['accounts' => $accounts, 'status' => 'declined'];
$types = ['accounts' => Connection::PARAM_INT_ARRAY, 'status' => \PDO::PARAM_STR];
If you want to stick to the :param syntax where order does not matter, you have to do a bit of extra work, but I'll show you an easier way to bind the parameters:
// store all your parameters in one array
$params = array(
':status' => 'declined'
);
// then, using your arbitrary array of id's ...
$array_of_ids = array(5, 6, 12, 14);
// ... we're going to build an array of corresponding parameter names
$id_params = array();
foreach ($array_of_ids as $i => $id) {
// generate a unique name for this parameter
$name = ":id_$i"; // ":id_0", ":id_1", etc.
// set the value
$params[$name] = $id;
// and keep track of the name
$id_params[] = $name;
}
// next prepare the parameter names for placement in the query string
$id_params = implode(',', $id_params); // ":id_0,:id_1,..."
$sql = "select * from user where id in ($id_params) and status = :status";
In this case we end up with:
"select * from user where id in (:id_0,:id_1,:id_2,:id_3) and status = :status"
// now prepare your statement like before...
$stmt = $em->getConnection()->prepare($sql);
// ...bind all the params in one go...
$stmt->execute($params);
// ...and get your results!
$result = $stmt->fetchAll();
This approach will also work with an array of strings.
You need to wrap them in an array
$stmt->bindValue(':userId', array($accounts), array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY));
http://doctrine-dbal.readthedocs.io/en/latest/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion
edit
I should have elaborated more. You cannot bind an array like that, dont prepare the sql execute directly as the example in the docs.
$stmt = $conn->executeQuery('SELECT * FROM articles WHERE id IN (?)',
array(array(1, 2, 3, 4, 5, 6)),
array(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY));
You cannot bind an array of values into a single prepared statement parameter
following query returns all wanted results if entered in phpmyadmin:
SELECT postid, voting
FROM postvotes
WHERE userid = 1
AND postid IN
(1007,1011,1012,1013,1014,
1015,1016,1017,1018,1019,1020,1021,1023,1025,1026,
1027,1028,1029,1030,1031)
But PDO fails to fetchAll(). It just returns the first match like fetch().
What's wrong?
PHP Code:
private function userPostVotings( $postIDs ) {
// $postIDs contains a string like 1,2,3,4,5,6,7...
// generated through implode(',', idArray)
try {
$userPostVote = $this->_db->prepare('SELECT postid, voting
FROM postvotes
WHERE userid = ?
AND postid IN ( ? )');
$userPostVote->setFetchMode(\PDO::FETCH_ASSOC);
$userPostVote->execute( array( $this->_requester['id'], $postIDs ) );
while ( $res = $userPostVote->fetch() ) {
var_dump( $res );
}
} catch (\PDOException $p) {}
}
If I echo out the query used in this method and fire it through phpmyadmin I get the correct number of results. However PDO gives just the first. No matter if a loop with fetch() or fetchAll().
You cannot bind array in prepared statements in PDO.
Reference:
Can I bind an array to an IN() condition?
it is not PDO's fetchAll() of course, but your query.
Which is not
IN (1007,1011,1012,1013,1014)
but
IN ('1007,1011,1012,1013,1014')
and of course it will find only first value as this string will be cast to the first number
One have to create a query with placeholders representing every array member, and then bind this array values for execution:
$ids = array(1,2,3);
$stm = $pdo->prepare("SELECT * FROM t WHERE id IN (?,?,?)");
$stm->execute($ids);
To make this query more flexible, it's better to create a string with ?s dynamically:
$ids = array(1,2,3);
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE column IN ($in)";
$stm = $db->prepare($sql);
$stm->execute($ids);
$data = $stm->fetchAll();
I'm trying to select elements from a table in a mysql database where the id of a row is in the given array.
This returns values:
<?php
$ids = '1,2,3,4';
$DBH = ....
$getID = $DBH->prepare("SELECT * FROM t1 WHERE id IN ($ids)");
$getID->execute();
?>
This returns nothing:
<?php
$ids = '1,2,3,4';
$DBH = ....
$getID = $DBH->prepare("SELECT * FROM t1 WHERE id IN (:ids)");
$getID->execute(array(':ids'=>$ids));
?>
I can't understand what is wrong with that code.
In the first one, you're using PHP to do string interpolation before talking to the database; in effect, using PHP variables to generate SQL code. This is where SQL injection comes from - the database doesn't know the difference between data and code, so it can't protect you from "data" leaking into the "code" space. In the second, you are using bound parameters, telling the database "Please deal with :ids as a SINGLE VALUE, whose contents I will tell you later." An easy way to solve the disconnect is something like:
$sql = 'SELECT * from t1 where id in (' . str_repeat('?', count($ids)) . ')';
$stmt = $pdo->prepare($sql);
$stmt->execute($ids);
Check out this tutorial for more on these points.
Assuming you are using PDO, try the following.
<?php
$ids = '1,2,3,4';
$DBH = ....
$getID = $DBH->prepare("SELECT * FROM t1 WHERE id IN (:ids)");
$getID->bindParam(":ids", $ids, PDO::PARAM_INT);
$getID->execute();
?>
When your original query is executed, PDO will escape your input so your query will look like
SELECT * FROM t1 WHERE id IN ("1,2,3,4")
When you tell PDO to bind as a integer, it will execute
SELECT * FROM t1 WHERE id IN (1,2,3,4)
I have the following function. I expect it to print the number of rows in the table provided in its argument.
private function getTotalCount($tbl){
$sql = "SELECT count(*) FROM :tbl ;";
$sth = $this->db->prepare($sql);
$sth->execute(array(
':tbl' => $tbl
));
$data = $sth->fetch(PDO::FETCH_ASSOC);
print_r($data);
}
But the function is not printing anything...
When I replace the function to something like this:
private function getTotalCount($tbl){
$sql = "SELECT count(*) FROM $tbl ;";
$sth = $this->db->prepare($sql);
$sth->execute();
$data = $sth->fetch(PDO::FETCH_ASSOC);
print_r($data);
}
Then it works fine and print the number of rows.
QUESTION: Why the execute() function not binding the :tbl parameter to $tbl ??
Sadly MySQL PDO doesn't accept parameters for SQL keywords, table names, view names and field names. This doesn't really come up in the main manual, but is mentioned a couple of times in comments.
The solution you have in the second piece of code is the workaround, although you may wish to sanitise the table name first (checking against a white list of table names would be ideal). More info: Can PHP PDO Statements accept the table or column name as parameter?
I have a form field that is returning a comma-delimited string that I want to pass in to a PHP PDO MySQL query IN operation, but the IN operation requires that the values be comma-delimited (as opposed to my string of delimited values).
How do I do this?
$values = $_POST['values']; # '10,5,4,3' (string)
$query = "SELECT * FROM table WHERE id IN (:values)";
$data = array( ':values' => $values );
You can't pass in multiple values in a single placeholder. You will have to enter a different placeholder for each value to be passed into IN (). Since you don't know how many there will be, use ? instead of named parameters.
$values = explode(',', $values) ;
$placeholders = rtrim(str_repeat('?, ', count($values)), ', ') ;
$query = "SELECT * FROM table WHERE id IN ($placeholders)";
$stm = $db->prepare($query) ;
$stm->execute($values) ;
PDO alone cannot bind arrays to a :parameter. You need a helper function for that.
Also in your example, the literal string '10,5,4,3' would be received as one value. Parameter binding will turn it into .. id IN ('10,5,4,3') and not into a list comparison.
The workaround in your case would be to fall back on using dynamic SQL and escaping.
$values = preg_replace('/[^\d,]/', "", $_POST['values']);
$query = "SELECT * FROM table WHERE id IN ($values)";
I'm personally using a wrapper/helper function which has a special syntax for arrays (but you don't actually have one to begin with, so it would be a double workaround):
db("SELECT * FROM table WHERE id IN (??)", explode(",",$values));
The trick is to recognize that $values is a bunch of individual values, and set up your query with this in mind. This is easier to do if you use ? placeholders instead of named placeholders. For example, you could do something like the following:
$values = explode(',', $_POST['values']); //array(10,5,4,3)
$placeholder_string = implode(',', array_fill(0, count($values), '?')); // string '?,?,?,?'
$query = "SELECT * FROM table WHERE id IN ($placeholder_string)";
$statement = $db->prepare($query);
$statement->execute($values);