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.
Related
So what I'd really like to do is combine the two queries.
I broke them up into two to help me figure out where the issue is.
sql2 is where the issue is. When I run it in phpMyAdmin (without WHERE)it works so what's going on here?
$holidayID = formval('holidayID');
$sort= formval('sort', 'rating');
$dir = formval('dir', 'ASC');
$sql = "SELECT recipeID, holidayID, recipeName, rating
FROM recipes
WHERE holidayID = $holidayID
ORDER BY $sort $dir ";
//execute
$result = mysqli_query($dbc, $sql);
$sql2 = "SELECT recipes.holidayID, holidays.holidayName
FROM recipes
JOIN holidays ON recipes.holidayID=holidays.holidayID
WHERE holidayID = $holidayID
LIMIT 1";
$result2 = mysqli_query($dbc, $sql2);
var_dump($result2);
The first query works fine.
So what am I doing wrong?
Thank you for your time.
The problem with your query is that the holidayID in your WHERE condition is ambiguous - MySQL doesn't know if you mean from the recipes or holidays table. Specify it, like you do when selecting and joining,
SELECT recipes.holidayID, holidays.holidayName
FROM recipes
JOIN holidays ON recipes.holidayID=holidays.holidayID
WHERE holidays.holidayID = $holidayID -- Specify the column, here its holidays
LIMIT 1
You should also note that this query might be vulnerable to SQL injection, and that you should utilize prepared statements in order to protect your database against that.
How can I prevent SQL injection in PHP?
Using proper error-reporting, with mysqli_error($dbc);, MySQL would've told you this as well.
PHP.net on mysqli_error();
Let's say the query looks like this:
$query = 'select * from some_table LIMIT :limit'
My db->selects are the following:
a) $orders = $db->select($db->raw($query), array("limit" => '0,10'));
b) $orders = $db->select($db->raw($query), array("limit" => '10'));
a) doesn't work, but b) does. Why?
Also this doesn't work:
$query2 = 'select :col from some_table LIMIT :limit';
$orders = $db->select($db->raw($query2), array("col" => "some_col","limit" => '10'));
Am I using it the wrong way?
You need to realize that prepared statements are not just formatted strings. The idea of prepared statements is that syntax and arguments are sent separately, so you can safely send user data without risking mysql injection. In query a) you are putting syntax in the parameter. The same can be said about the columns. Column names are part of the syntax.
Is it possible with PHP PDO to use named placeholders that effectively match everything?
Take as example: $sql = "SELECT * FROM users WHERE targetGroup = :targetGroup AND userId = :userId"
And take $statement = $this->dbh->prepare($sql);
Can I then somehow bind values such that I get the following behaviours:
Original query (this can be done obviously).
Parameters: :targetGroup = 1, :userId = 5.
Resulting query: $sql = "SELECT * FROM users WHERE targetGroup = 1 AND userId = 5
A query to show all users
Parameters: Somehow set :targetGroup and :userId to some special value ALL.
Resulting query: $sql = "SELECT * FROM users
I would like to use this such that I can define queries once somewhere in my program, as static strings, and then reuse them to my likings.
You can't do that. You'll have to prepare different statements for different queries.
$findOneStmt = $pdo->prepare("SELECT * FROM user WHERE target_group = :tg AND user_id = :uid");
$findAllStmt = $pdo->prepare("SELECT * FROM users");
Later, when you'll need one of this queries just call:
$findOneStmt->execute(array(':tg' => 123, ':uid' => 321));
$user = $findOneStmt->fetch();
// ...
$findAllStmt->execute();
$users = $findAllStmt->fetchAll();
You could put a conditional in your SQL that lets you show everything. For example:
SELECT *
FROM users
WHERE
1 = :showAll OR (
targetGroup = :targetGroup
AND userId = :userId
)
Then you can set the :showAll to 0 when you want to filter by :targetGroup and :userId or to 1 when you want to ignore :targetGroup and :userId.
I believe best practice however would be to have two separate queries.
The problem is that you're essentially performing two entirely different queries. They may look very similar, but a SELECT * with no where clause is quite different to one with a where clause.
Rather than define SQL queries in one place and then reuse, abstract the functionality out and use that method call as and when needed, it'll provide you with the ability to have the query defined once, but you will have two queries there now. This way is much safer and much better terms of readability and usage.
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.
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);