PDO Query Variable Issue? - php

In the following queries, I'm using entry_id values to from exp_relationships to find out the smallest value (a year) in exp_channel_data. I'll give you some dummy data.
entry_id 1 has a year of 2014
entry_id 2 has a year of 2012
The first query generates "1, 2" which I save for the next one to provide the WHERE parameters. If I were to type in "1, 2" instead of :showid, it works correctly and displays the 2012 since that is the first one in the ORDER BY I have set. But when the query runs as it is written below, it displays the 2014.
$sql = 'SELECT DISTINCT parent_id FROM exp_relationships WHERE child_id = :entry_id AND grid_field_id IN (90, 91)';
$stmt = $conn->prepare($sql);
$showid = "";
try {
$stmt->execute(array('entry_id' => {entry_id}));
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($result as $row) {
$showid .= $row['parent_id'].", ";
}
$showid = substr($showid, 0, -2);
} catch(PDOException $e) { error_log($e->getMessage(),0); }
$sql = 'SELECT field_id_16 FROM exp_channel_data WHERE entry_id IN (:showid) ORDER BY field_id_16 ASC LIMIT 1';
$stmt = $conn->prepare($sql);
$year = "";
try {
$stmt->execute(array('showid' => $showid));
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($result as $row) {
$year = $row['field_id_16'];
echo $year;
}
} catch(PDOException $e) { error_log($e->getMessage(),0); }

This is because defaultly it will cast $showid to its primitive type, string or int. In you case I guess if will be the string 1, 2. This way you cannot just inject variable value in IN to simulate more than one value. Each placeholder is one value, and IN (:placeholder) will refer to only one thing. You need either to split as much placeholders as needed, i.e. in this particular case 2 placeholders IN (?, ?) and then implode your $showid string into array. Or implode $showid into array and then split it into the IN clause, which I consider the worse way of both.

Related

PHP SQL select uses only the first result of the array in IN statement [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.
I am trying to sum a colomn based on the IDs selected from a table that i put in a array. For some reasom only the first ID is used in the Where clausule. When I echo the variable all the ids are there. What am i doing wrong?
$counttheid = array();
$stmt3 = $mysqli->prepare("SELECT
id
FROM account
WHERE level <= '5' AND door = ? AND `group_name` = ? AND betaald = 'Yes'");
$stmt3->bind_param("ss",$usernamesession,$groupname);
$stmt3->execute();
$result3 = $stmt3->get_result(); //only works when nd_mysli is set on the server!
while ($rowid = $result3->fetch_assoc())
{
$counttheid[] = $rowid['id'];
$countid = implode(',', $counttheid); // contains all the ids !!
}
$sql = "SELECT SUM(mobcash) AS totalcash FROM account WHERE id IN (?)
";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("i",$countid);
$stmt->execute();
$stmt->bind_result($row['totalcash']);
while($stmt->fetch()) $sumcash = $row['totalcash'];
echo $sumcash; // Somhow only the sum of the first ID of the array !!
echo $countid;// all the ids from the array !!
Not only for the in, but the number of bind parameters will need to match as well.
Try with this example for the code from the while to the execute:
while ($rowid = $result3->fetch_assoc())
{
$counttheid[] = $rowid['id'];
// $countid = implode(',', $counttheid); // contains all the ids !!
}
$in = str_repeat('?,', count($counttheid) - 1) . '?';
$types = str_repeat('i', count($counttheid));
$sql = "SELECT SUM(mobcash) AS totalcash FROM account WHERE id IN ($in)";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param($types, ...$counttheid);
$stmt->execute();
At the bind_param, the part with ...$counttheid, the ... portion is the argument unpacking operator.

Collect values from JSON file for use in MySQL `IN` clause

include "includes/dbh.inc.php";
$data = file_get_contents("dumps/datadump-hung1.json");
$post = json_decode($data, true);
foreach($post['sessionResult']['leaderBoardLines'] as $userArr){
$carname .=$userArr['car']['carModel'];
} echo $carname;
this echoes out the carmodel numbers like this= 19115 instead of 19 1 15
when i try to match these with my database to echo out the name of cars the numbers respond to with:
$carGETdb = "SELECT carName FROM cars WHERE carID IN ($carname)";
$result = mysqli_query($conn, $carGETdb);
$row = mysqli_fetch_array($result)["carName"];
echo $row;
it posts nothing, because no cars is associated with 19115 but 3 different cars is associated with 19, 1 and 15
is there a way to foreach each array in singles, so i can match them with my database and echo out as carnames instead of numbers?
Map the values you're after to a new array
Build a prepared statement with the appropriate number of ? parameters in your IN clause
Bind the array of values to your statement
Execute and fetch
$carIds = array_map(function($userArr) {
return $userArr['car']['carModel'];
}, $post['sessionResult']['leaderBoardLines']);
// [ 19, 1, 15 ]
$placeholders = implode(', ', array_fill(0, count($carIds), '?'));
// "?, ?, ?"
$bindTypes = str_repeat('i', count($carIds));
// "iii"
$stmt = $conn->prepare(
"SELECT `carID`, `carName` FROM `cars` WHERE `carID` IN ($placeholders)");
$stmt->bind_param($bindTypes, ...$carIds);
$stmt->execute();
$stmt->bind_result($carId, $carName);
$carNames = [];
while ($stmt->fetch()) {
$carNames[$carId] = $carName;
}
var_dump($carNames);
Using your code...
include "includes/dbh.inc.php";
$data = file_get_contents("dumps/datadump-hung1.json");
$post = json_decode($data, true);
$carname = [];
foreach($post['sessionResult']['leaderBoardLines'] as $userArr){
$carname[] = $userArr['car']['carModel'];
}
$carnames = implode(',', $carname);
echo $carnames;
Then with your following statement when querying the database, you can use the implded value ( but this is bound to sql injection attacks ). You should use parameterised query instead. However, I am not going to change too much of your code so you can see what is going on.
$carGETdb = "SELECT carName FROM cars WHERE carID IN ($carnames)";
$result = mysqli_query($conn, $carGETdb);
$row = mysqli_fetch_array($result)["carName"];
echo $row;

How to use PDO prepared statements with IN clause? [duplicate]

This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 1 year ago.
I stored some data in a field inside MySQL in this format: 1,5,9,4
I named this field related. Now I want to use this field inside an IN clause with PDO. I stored that field contents in $related variabe. This is my next codes:
$sql = "SELECT id,title,pic1 FROM tbl_products WHERE id IN (?) LIMIT 4";
$q = $db->prepare($sql);
$q->execute(array($related));
echo $q->rowCount();
But after executing this code, I can fetch only one record whereas I have to fetch 4 records (1,5,9,4). What did I do wrong?
using named place holders
$values = array(":val1"=>"value1", ":val2"=>"value2", ":val2"=>"value3");
$statement = 'SELECT * FROM <table> WHERE `column` in(:'.implode(', :',array_keys($values)).')';
using ??
$values = array("value1", "value2", "value3");
$statement = 'SELECT * FROM <table> WHERE `column` in('.trim(str_repeat(', ?', count($values)), ', ').')';
You need as many ? placeholders as your "IN" values.
So:
$related = array(1,2,3); // your "IN" values
$sql = "SELECT id,title,pic1 FROM tbl_products WHERE id IN (";
$questionmarks = "";
for($i=0;$i<count($related);$i++)
{
$questionmarks .= "?,";
}
$sql .= trim($questionmarks,",");
$sql .= ") LIMIT 3;";
// echo $sql; // outputs: SELECT id,title,pic1 FROM tbl_products WHERE id IN (?,?,?) LIMIT 3;
$q = $db->prepare($sql);
$q->execute($related); // edited this line no need to array($related), since $related is already an array
echo $q->rowCount();
https://3v4l.org/No4h1
(also if you want 4 records returned get rid of the LIMIT 3)
More elegantly you can use str_repeat to append your placeholders like this:
$related = array(1,2,3); // your "IN" values
$sql = "SELECT id,title,pic1 FROM tbl_products WHERE id IN (";
$sql .= trim(str_repeat("?,",count($related)),",");
$sql .= ") LIMIT 3;";
// echo $sql; // outputs: SELECT id,title,pic1 FROM tbl_products WHERE id IN (?,?,?) LIMIT 3;
$q = $db->prepare($sql);
$q->execute($related); // edited this line no need to array($related), since $related is already an array
echo $q->rowCount();
https://3v4l.org/qot2k
Also, by reading again your question i can guess that your $related variable is just a string with value comma-separated numbers like 1,40,6,99. If that's the case you need to make it an array. do: $related = explode($related,","); to make it an array of numbers. Then in your execute method pass $related as-is.

bind_param(): Number of variables doesn't match number of parameters in prepared statement

Basically the problem is I have a column of dates in my database table and I want to count the number of each particular date and have it stored in an array.I've busted my head around this problem for a week and so far I've come up with this.
<?php
function vref($arr) {
if (strnatcmp(phpversion(),'5.3') >= 0) {
$refs = array();
foreach($arr as $key => $value) $refs[$key] = &$arr[$key];
return $refs;
}
return $arr;
}
$mysqli = new mysqli("localhost", "root","" , "ti_project");
$bind = 'sssss';
$feedbackdate = array($bind);
$query = "SELECT dateTime FROM feedback";
$result = $mysqli->prepare($query);
$result->execute();
$result->bind_result($Date);
while ($result->fetch()){
$feedbackdate[] = array($Date);
}
$rawQuery = 'SELECT COUNT(*) FROM feedback WHERE dateTime IN (';
$rawQuery .= implode(',',array_fill(0,count($feedbackdate),'?'));
$rawQuery .= ')';
$stmt = $mysqli->prepare($rawQuery);
call_user_func_array(array($stmt,'bind_param'),vref($feedbackdate));
$stmt->execute();
$stmt->bind_result($count);
while ($stmt->fetch()) {
printf ("%s\n", $count);
}
?>
But here I get the error
mysqli_stmt::bind_param(): Number of variables doesn't match number of parameters in prepared statement.
So how to do this?
I am not sure why you need to do two queries to get the result set you are looking for. This query will group the results by date and count them:
SELECT dateTime, COUNT(*) FROM feedback GROUP BY dateTime;
This will output something like:
+-----------------------+-------+
| dateTime | count |
+-----------------------+-------+
|2016-01-25 00:00:00 | 1 |
|2016-01-24 00:00:00 | 2 |
+-----------------------+-------+
Is that the type of data you are after?
Your parameter array consists of arrays, which I don't think is what you want:
Replace:
while ($result->fetch()){
$feedbackdate[] = array($Date);
}
By:
while ($result->fetch()){
$feedbackdate[] = $Date;
}
Also, you initialise your array already with one element, which makes no sense. So replace:
$feedbackdate = array($bind);
with:
$feedbackdate = array();
Then the injection of these arguments can be done a lot easier by using the optional parameter of execute():
$stmt = $mysqli->prepare($rawQuery);
$stmt->execute($feedbackdate);

PDO not substituting parameters properly

I'm making a blog based on PHP and MySQL (using PDO to connect). I have written a function (below) that will retrieve blog posts and return them, but order by isn't working when I pass it by reference:
<?php
/**
* Get blog posts, filtered and sorted to taste.
* #return array Array of rows - each row is an array indexed by column name.
* #param string $inFunction What to select posts by - id, date or tag(s).
* #param string $inFilter Filter data to select posts by - id no., date(s) or tag(s). If you are filtering by date and only specify one, it will be taken to be the 'newer than' date.
* #param string $inOrder How to sort posts. This parameter is fed directly into the query and therefore should be raw SQL.
* #param object $inDatabase Database handle to pass to the SQL query.
*/
function getBlogPosts($inFunction, $inDatabase, $inFilter, $inOrder) {
switch ($inFunction) {
case "permalink": {
$query = $inDatabase->prepare("select * from blog_posts where permalink = :permalink");
$query->bindValue(":permalink", $inFilter);
$query->execute();
$result = $query->fetch();
return new BlogPost($result["id"], $result["title"], $result["permalink"], $result["post_full"], $result["post_sample"], $result["tags"], $result["timestamp"]);
break;
}
case "number": {
$query = $inDatabase->prepare("select * from blog_posts
order by :order
limit :limit_start , :limit_end");
$query->bindParam(":order", $inOrder);
$splitLimits = explode(", ", $inFilter);
if (sizeOf($splitLimits) === 1)
$splitLimits[] = 1; // First post
$limitEnd = $splitLimits[0] + $limitStart;
$limitStart = $splitLimits[1] - 1;
$query->bindValue(":limit_start", (int) $limitStart, PDO::PARAM_INT);
$query->bindValue(":limit_end", (int) $limitEnd, PDO::PARAM_INT);
$query->debugDumpParams();
$query->execute();
$results = $query->fetchAll(PDO::FETCH_ASSOC);
$return = array();
foreach ($results as $result) {
$return[] = new BlogPost($result["id"], $result["title"], $result["permalink"], $result["post_full"], $result["post_sample"], $result["tags"], $result["timestamp"]);
}
return $return;
break;
}
case "id": {
$query = $inDatabase->prepare("select * from blog_posts where id = :id order by :order");
$query->bindParam(":id", $inFilter);
$query->bindParam(":order", $inOrder);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC); // Prevents duplicate results when using loops (foreach, while etc.)
break;
}
case "date": {
$splitdate = explode(", ", $inFilter);
$query = $inDatabase->prepare("select * from blog_posts
where (date_posted > :newerthan_date)
and (date_posted <= :olderthan_date)
order by :order");
if (sizeof($splitdate) === 1) {
$splitdate[] = date("Y-m-d");
}
$query->bindParam(":newerthan_date", $splitdate[0]);
$query->bindParam(":olderthan_date", $splitdate[1]);
$query->bindParam(":order", $inOrder);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
break;
}
case "tag": {
$tags = explode(", ", $inFilter);
$insert = "";
foreach ($tags as $key => $tag) {
if ($key === 0) {
$insert .= "where tags like :tag_{$key}";
}
else {
$insert .= " or tags like :tag_{$key}";
}
}
$query = $inDatabase->prepare("select * from blog_posts
{$insert}
order by :order");
foreach ($tags as $key => $tag) {
$query->bindValue(":tag_{$key}", '%^' . $tag . '^%');
}
$query->bindParam(":order", $inOrder);
$query->execute();
return $query->fetchAll(PDO::FETCH_ASSOC);
break;
}
}
}
On the main page, $results = getBlogPosts("number", $sql_conn, "10", "timestamp desc"); is called and a foreach loop iterates through the posts. The problem is that PDO does not seem to be applying the :order parameter; whatever I put for $inOrder it always has the same order. If I go and directly change the statement within the getBlogPosts function to have order by timestamp desc instead of order by :order, it works fine.
I'm stumped - any ideas?
"This parameter is fed directly into the query and therefore should be raw SQL." => the comment is correct, the code is not, fix this. The reason is you can specify strings/numbers/etc. with parameters, but not identifiers (column names etc.).
What your query does it this:
SELECT ... FROM ... ORDER BY 'columnname';
Rather then:
SELECT ... FROM ... ORDER BY columnname;
So. it sorts by the string 'columnname', not the value in the field with the same name, with is the same for every row, so no sorting takes place (you might as well ORDER BY 1. The solution here is to add that order by clause as raw MySQL as the docblock comment states. If you want more control over it / prevent nastiness there, you could provide a whitelist of allowable order by clauses and refuse others.

Categories