MySQL PDO statement with IN clause? - php

I recently heard that soon PHP will be deprecating all of the traditional mysql functions (ex: mysql_query(), mysql_num_rows(), etc...). That being said, I'm trying to convert all of my queries into PDO format. This one particular case is giving me some difficulty,
$team = $_GET['team'];
$ncaa = array("Duke", "Harvard", "Yale", "Stanford");
$limit = idate('z')+14;
$list = join("','", $ncaa);
$query = "SELECT game_id
FROM ncaa_current_season_games
WHERE game_date_int >= :game_date_int
AND (home_team = :team OR away_team = :team)
AND home_team IN(:list)
AND away_team IN(:list)
ORDER BY game_date_int ASC
LIMIT 1";
$stmt = $db->prepare($query);
$stmt->execute(array(':game_date_int' => $limit, ':team' => $team, ':list' => $list));
$num = $stmt->rowCount();
$num is returned as 0. When I had the query in my old format (not PDO), it worked just fine.
Any suggestions?
Thanks,
Lance

You can't use the :list as one placeholder for the in clause, you need one placeholder for one value.

PDO, as any other raw API, is insufficient for the any real-life task.
And prepared statements are insufficient too, as they accept only scalar data, not whatever complex data types or arbitrary query parts.
So, a developer have to help himself with adopting a database abstraction library.
And use this library methods in their application code instead of raw API calls.
With raw PDO you need to create ? mark for the every IN statement value dynamically, using str_repeat() function or something of the kind, add every value to the data array, and then run all that train.
You can find plenty of examples on this site. Some of them are quite smart though.
But with good database access library, you can do it the way you tried (in vain) with raw PDO
$team = $_GET['team'];
$ncaa = array("Duke", "Harvard", "Yale", "Stanford");
$limit = idate('z')+14;
$query = "SELECT game_id
FROM ncaa_current_season_games
WHERE game_date_int >= ?s
AND (home_team = ?s OR away_team = ?s)
AND home_team IN(?a)
AND away_team IN(?a)
ORDER BY game_date_int ASC
LIMIT 1";
$data = $db->getRow($query, $limit, $team, $team, $list, $list);

Related

PDO query appears to be returning DESC order. Ignoring ASC hardcode

Im new to PDO so maybe some subtle nuance that I am not aware of. But I have a query that is supposed to list the drivers by date ASC. The code works the right way, ASC when I test in workbench, so I know the test table data is solid; but when I run the below code in PDO, it does not show any error and returns the record but im seeing the results for DESC order by not ASC like expected. Here's the code:
$selectsql = "SELECT driverid, busid, firstname
FROM drivers
WHERE active= 1
AND day= 1
ORDER BY ? ASC
LIMIT ? ";
$results = $pdo->prepare($selectsql);
$results->execute([$list_date,$count]);
$row = $results->fetchAll();
I get no error and data is returned. Just not in order expected.
I did check and the 2 variables $list_date and $count are set properly.
Does anyone see what it possibly could be?
UPDATE
Im finding that it is sorting by the primary index driversid. I have no idea why.
I think you cannot use a field name as parameter for a prepared statement. You can change your code to something like this:
$selectsql = "SELECT driverid, busid, firstname
FROM drivers
WHERE active= 1
AND day= 1
ORDER BY " . $list_date . " ASC
LIMIT ? ";
$results = $pdo->prepare($selectsql);
$results->execute([$count]);
$row = $results->fetchAll();
You are sorting by a constant expression, not a column, so the sort order will basically be arbitrary (not even random).
If you want to inject code into a SQL statement (such as a column name) you need to use good old string manipulation functions. Prepared statements are specifically designed to avoid that:
$selectsql = "SELECT driverid, busid, firstname
FROM drivers
WHERE active= 1
AND day= 1
ORDER BY $list_date ASC
LIMIT ? ";
$results = $pdo->prepare($selectsql);
$results->execute([$count]);
$row = $results->fetchAll();
Make sure that $list_date is valid SQL you have full control on and not external input.

Can I use name of the column as parameter in PDO?

I'm trying to execute this:
$colparam = 'abcd';
$stmt = $db->prepare("SELECT DISTINCT ? AS kol FROM katalog ORDER BY kol ASC");
$stmt->execute(array($colparam));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
and it's not working (no errors, just empty array as result).
Instead this works fine:
$stmt = $db->prepare("SELECT DISTINCT abcd AS kol FROM katalog ORDER BY kol ASC");
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
So is there any catch with the use of parameter as a name of the column in PDO?
No, you can't use parameter replacements for any database objects (tables, columns, etc.) in MySQL.
When you think about what a prepared statement actually is, this makes complete sense. As how can MySQL prepare a query execution plan when it does not even know the database objects involved.
I certainly wish that more documentation would actually cover what a prepared statement actually does (beyond it's obvious use for parametrization).
Here is link to MySQL prepared statement documentation for more reading:
https://dev.mysql.com/doc/refman/5.6/en/sql-syntax-prepared-statements.html

PHP SQLSRV Sorting with Parameter of Prepared Statement

I can't figure out why sorting will work as long as I'm not using $sort as a passed in parameter. Example below will work for sorting:
$sort = "quantity desc";
$sql = " with items as (
SELECT i.[item_id]
,i.[name]
,i.[value]
,i.[quantity]
,i.[available]
,isnull(r.awarded, 0) as awarded
, ROW_NUMBER() OVER(
ORDER BY $sort
) rowNumber
FROM [Intranet].[dbo].[Goodwell_Item] i
LEFT JOIN (
SELECT r.item_id
, COUNT(1) awarded
from [Intranet].[dbo].[Goodwell_Reward] r
group by r.item_id
) as r
ON i.item_id = r.item_id
)
SELECT *
FROM items
WHERE rowNumber BETWEEN (?) and (?)
and ( (?) = '' OR (available = (?)))
";
$params = array( $pagify['startFrom'], $end, $available, $available );
$stmt = sqlsrv_query( $conn, $sql, $params );
However if I change the line with ORDER BY to:
ORDER BY (?)
and add it to my $params like so:
$params = array($sort, $pagify['startFrom'], $end, $available, $available );
then the sort for some reason is being ignored.
Please tell me how to get the sort working in a way that doesn't allow SQL injection.
I am dealing with this exact issue right now, and cannot find anything online to help.
I have tried:
$query = "SELECT * FROM {$this->view} WHERE SeriesID = ? ORDER BY ? ";
$result = $conn->getData($query, array($seriesID,$sortBy));
and
$query = "SELECT * FROM {$this->view} WHERE SeriesID = ? ORDER BY ? ?";
$result = $conn->getData($query, array($seriesID,$sortBy,$sortOrder));
In both cases, I get no error, and no results.
I think the only way to solve this safely is to use a switch statement before the query to manually validate the acceptable values. However, unless you're only ever dealing with one table, you can't know what the possible values are for the SortBy column.
However, if you just go with the assumption that the values at this point have already been cleaned, you can go with the non-parameterized version like this:
$query = "SELECT * FROM {$this->view} WHERE SeriesID = ? ORDER BY " . $sortBy . " " . $sortOrder;
$result = $conn->getData($query, array($seriesID));
What I plan to do is make sure to validate sortBy and sortOrder before I pass them to the method that contains this code. By doing it this way, each place I call the code becomes responsible for validating the data before sending it. The calling code would know the valid possible values for the table (or view in this case) that it is calling. (I'm the author of both pieces of code in this case, so I know it's safe.)
So, in short, just make sure that the values at this point in the code are already cleaned and safe, and push that responsibility up one level the code that calls this code.

Using Doctrine DBAL to count number of rows from SELECT query

OK so I am looking for a neat and short way to count the number of rows from a SELECT query using Doctrine DBAL.
I know that I could SELECT COUNT(*) but then I need to sort through the array when I fetch results. Alternatively, it's been suggested to look in to getScalarResult(). But I can't seem to find any documentation about this, other than in DQL (which is a different project).
So what is the neatest way to do this? I guess it's because I'm used to the great MySQLI attribute num_rows!
Another way to do this with Doctrine DBAL is to get the count as a field and return the column
$sql = "SELECT count(*) AS Total FROM myTable WHERE myId = :myId";
$stmt = $conn->prepare($sql);
$stmt->bindValue('myId', $myId, PDO::PARAM_INT);
$stmt->execute();
$count = $stmt->fetchColumn(0);
Actually I thought I had looked really hard, but I just came across this Count Records Returned MySQL Doctrine
So the way to do it is via the rowCount() method.
Example:
$num_rows = $conn->executeQuery("SELECT * FROM users")->rowCount();
I enjoy to use the query builder. An example:
$queryBuilder = $connection->createQueryBuilder();
$queryBuilder->select('COUNT(*)');
$queryBuilder->from("the_table");
$queryBuilder->where('some_column = :theValue');
$queryBuilder->setParameter('theValue', $someValue);
return (int) $queryBuilder->execute()->fetchColumn();
Here is an updated version of #DonkeyKong answer for Doctrine DBAL >= 2.13:
$sql = "SELECT count(*) AS Total FROM myTable WHERE myId = :myId";
$stmt = $conn->prepare($sql);
$stmt->bindValue('myId', $myId, PDO::PARAM_INT);
$result = $stmt->executeQuery();
$count = $result->fetchOne();

MySQL PDO with IN clause

I'm in the process of switching all of my queries to PDO format and I'm having problems with one in particular that involves the IN() clause.
$nba[0] = "Boston Celtics";
$nba[1] = "New York Knicks";
$nba[2] = "Houston Rockets";
$query = "SELECT game_id
FROM table
WHERE date_int >= :date_int
AND (home_team = :team OR away_team = :team)
AND home_team IN(:list)
AND away_team IN(:list)
ORDER BY game_date_int ASC
LIMIT 1";
$stmt = $db->prepare($query);
$stmt->execute(array(':date_int' => $limit, ':team' => $team, ':list' => implode(',', $nba)));
IN cannot be parameterized like other values. So just have to use implode the placeholders and the values. I have been thinking about trying to implement it in PHP for some while now though. However I never got further than thinking about it.
I also see you have the same named parameter (:list) twice in your query. This is also not possible if you use real prepared statements. Note that when you are using the mysql driver and PDO you have to disable emulated prepared statements.
You could solve this like this:
$nba = array();
$nba[0] = "Boston Celtics";
$nba[1] = "New York Knicks";
$nba[2] = "Houston Rockets";
$params = array(':date_int' => $limit, ':team' => $team);
$nba_teams = array();
for($i=0;$i<count($nba);$i++){
$nba_teams[] = ':list' . $i;
$params[':list' . $i] = $nba[$i];
}
$query = "SELECT game_id
FROM table
WHERE date_int >= :date_int
AND (home_team = :team OR away_team = :team)
AND home_team IN(".implode(',', $nba_teams).")
AND away_team IN(".implode(',', $nba_teams).")
ORDER BY game_date_int ASC
LIMIT 1";
$stmt = $db->prepare($query, $params);
$stmt->execute();
Haven't tested it yet, but I think you know what I'm trying
PDO is very weak with cases like this, so the task is going to be quite toilsome.
Like any other API, PDO is good for basic tasks from beginner's manual only, and offers no real help to developer for whatever real life issues.
So a developer have to adopt some sort of abstraction library to let it do all the dirty job.
So, I'll give you a safeMysql example which is better than PDO in any way:
$nba[0] = "Boston Celtics";
$nba[1] = "New York Knicks";
$nba[2] = "Houston Rockets";
$query = "SELECT game_id
FROM table
WHERE date_int >= ?i
AND (home_team = ?s OR away_team = ?s)
AND home_team IN(?a)
AND away_team IN(?a)
ORDER BY game_date_int ASC
LIMIT 1";
$data = $db->getAll($query, $limit, $team, $team, $nba, $nba);
Look - this code is neat, concise and certain. It does only meaningful things, hiding all the dirty job of binding complex data inside.
Unlike ugly codes you can get playing with some PHP and API functions this code is readable. This is important matter. You can tell what does this code do even after a year or so.
NLZ's answer is a perfect example - the code gets polluted with unnecessary and quite puzzling code.
When you're looking up your code, you're looking for the business logic in first place. And such useless code blocks, intended only to create a small part of SQL query, would make you dizzy, hiding the real matters from you.
In ought to be moved somewhere behind internals.

Categories