I'm trying to make a prepared query that can be used to select any number of users, and also filter them from a basic search functionality.
It looks like this:
$ALL_MEMBERS = $database->prepare("SELECT * FROM Users WHERE Username LIKE '%?%' LIMIT ?, ?")
But PDO doesn't seem to be picking up that first '?' inbetween the '%'s. Any idea why?
(It gives me an error when I give 3 parameters, saying there's the wrong amount, whereas it doesn't when giving two parameters)
You are likely confusing PDO there with the quotes and percentages, it is looking for a ? mark. Write it as:
$database->prepare("SELECT * FROM Users WHERE Username LIKE ? LIMIT ?, ?");
And then have the first variable as:
$database->execute(array('%'.$A.'%',$B,$C));
I believe you need to do this:
$ALL_MEMBERS = $database->prepare("SELECT * FROM Users WHERE Username LIKE ? LIMIT ?, ?")
PDO should already wrap strings for you.
Related
This question already has answers here:
How to include a PHP variable inside a MySQL statement
(5 answers)
Closed 1 year ago.
I have a problem with my php code when i try to choose a string in my database.
Well, I have a table "news" and a field "category" (TINYTEXT type) there. And I try to display a string from this table by using "where clause":
$Sql = 'SELECT * FROM `news` WHERE `category` = '.$Param['category'].' ORDER BY `id` DESC';
I must say that I received "$Param['category']" by using URL Path: 1
So, if my "category" value of any string in table has any string type value like "games" 2, 3 - string from my table isn't displayed with this error "Fatal error: Uncaught mysqli_sql_exception: Unknown column 'games' in 'where clause'"
But, if I change the "category" value of string in the database into anything numeral value - the string from my table is displayed correctly!4, 5
What's wrong with my code? Thank you in advance! Sorry for the mistakes.
You need to use prepared queries, both for the sake of SQL injection protection, and because it will fix your bug. I believe the issue is that you don't have any quotes at all around your parameters. As a result you're building queries like this:
SELECT * FROM `news` WHERE `category` = games ORDER BY `id` DESC
SELECT * FROM `news` WHERE `category` = 1 ORDER BY `id` DESC
The latter is a valid query: mysql looks for records where the category is 1. The former is not: the query tries to find records where the column category matches the column games. The latter doesn't exist so you get an error. You want to build these queries:
SELECT * FROM `news` WHERE `category` = 'games' ORDER BY `id` DESC
SELECT * FROM `news` WHERE `category` = '1' ORDER BY `id` DESC
Which you would do with this code:
$Sql = "SELECT * FROM `news` WHERE `category` = '".$Param['category']."' ORDER BY `id` DESC";
(note that I switched to double quotes and included single quotes around the input parameter). BUT THIS IS STILL WRONG as it leaves you extremely vulnerable to SQL Injection, which would allow an attacker to potentially download or modify your entire database. Take the time to read up and learn how to use prepared queries, which is both more secure and also would have prevented this bug in the first place.
Others have suggested you use parameterized queries. That's good advice, but they didn't show you what it looks like (not that it's hard to find examples).
Here's a quick example using PDO:
$Sql = 'SELECT * FROM `news` WHERE `category` = ? ORDER BY `id` DESC';
$stmt = $pdo->prepare($Sql);
$stmt->execute([ $Param['category' ]);
This executes the query as if you had put $Param['category'] into the SQL, but it does it completely safely, even if the value contains special characters.
You don't put quotes around the ? placeholder. It's implied that the query will treat it as a single scalar value, not a column name. In fact, you can use parameters only for values — not for column names, or expressions, or SQL keywords or anything else.
Writing code using query parameters is easier than not using parameters, because it makes your code easier to write and easier to read.
You have to put quotes around any string in the query if it is not a field in the table. Try this:
$Sql = "SELECT * FROM `news` WHERE `category` = '".$Param['category']."' ORDER BY `id` DESC";
As was mentioned if you are pulling in that string from a source where you have to word about code injection you should use prepared statements or PDO.
Is there a way to get a UserId in and sql table called Accounts based on a UserEmail also in Accounts? In php i have a page where people will login with a UserEmail from an Accounts table. this is all fine, but they need to write a review that is stored in a Reviews table and im having trouble pulling the UserId and putting it into the Review table.
Yes, many ways; a very simple one is to just use an inline SELECT statement in the INSERT query. A very rough example, assuming you're using something like PDO, might look like:
$pdo = new PDO(...);
$stmt = $pdo->prepare("insert into review(score, user_id) values(?, select user_id from accounts where useremail = ?)");
$stmt->execute($score, $useremail);
In essence, I have a value that I have to call a couple times in my SQL query. Thus, is it possible to reuse the same named placeholder in the statement e.g.
SELECT :Param FROM Table WHERE Column = :Param, then simply bindValue(":Param"), and have the value be there for both :Params?
PDO::prepare states that "you cannot use a named parameter marker of the same name twice in a prepared statement", so I guess that's a no then.
You can if you set PDO::ATTR_EMULATE_PREPARES = true.
E.g. $connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);.
If you're using Laravel you can set this in an options array in config/database.php. e.g. PDO::ATTR_EMULATE_PREPARES => true
Apart from reuse, the main issue here is that you are trying to dynamically change col names.
This answer posted by an anonymous user on http://php.net/manual/en/pdo.prepare.php :
To those wondering why adding quotes to around a placeholder is wrong,
and why you can't use placeholders for table or column names:
There is a common misconception about how the placeholders in prepared
statements work: they are not simply substituted in as (escaped)
strings, and the resulting SQL executed. Instead, a DBMS asked to
"prepare" a statement comes up with a complete query plan for how it
would execute that query, including which tables and indexes it would
use, which will be the same regardless of how you fill in the
placeholders.
The plan for "SELECT name FROM my_table WHERE id = :value" will be the
same whatever you substitute for ":value", but the seemingly similar
"SELECT name FROM :table WHERE id = :value" cannot be planned, because
the DBMS has no idea what table you're actually going to select from.
Even when using "emulated prepares", PDO cannot let you use
placeholders anywhere, because it would have to work out what you
meant: does "Select :foo From some_table" mean ":foo" is going to be a
column reference, or a literal string?
When your query is using a dynamic column reference, you should be explicitly white-listing the columns you know to exist on the table, e.g. using a switch statement with an exception thrown in the default: clause.
Many queries like yours can be rewritten to use only one placeholder.
SELECT :Param FROM Table WHERE Column = :Param
would be the same as
SELECT Column FROM Table WHERE Column = :Param
But sometimes it's not that simple. For example:
SELECT *
FROM my_table
WHERE first_name LIKE :Param
OR last_name LIKE :Param
OR biography LIKE :Param
In such case you could reuse the parameter value storing it in a cross joined derived table (subquery in FROM clause):
SELECT t.*
FROM my_table t
CROSS JOIN (SELECT :Param as Param) AS x
WHERE first_name LIKE x.Param
OR last_name LIKE x.Param
OR biography LIKE x.Param
There's a workaround:
function search($criteria) {
$sql = "SELECT * FROM my_table
WHERE column_1 like CONCAT('%', :criteria1, '%')
OR column_2 like CONCAT('%', :criteria2, '%')
OR column_3 like CONCAT('%', :criteria3, '%')
";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':criteria1', $criteria);
$stmt->bindParam(':criteria2', $criteria);
$stmt->bindParam(':criteria3', $criteria);
$stmt->execute();
return($stmt->fetchAll(PDO::FETCH_ASSOC));
}
In summary, use different placeholders with the same criteria.
I am trying to to get my data from MYSQL using PDO but I am not having any luck.
Here is what I have tried:
$postQuery = $DBH->prepare("SELECT title, views, rating, thumb FROM posts WHERE category=:category and status=1 ORDER BY :sort DESC");
$postQuery ->bindParam(':category', $category);
$postQuery ->bindParam(':sort', $sort);
$postQuery ->execute();
This works without errors but it returns all of the posts in alphabetical order, ignoring the category and the sort.
I tried this:
$postQuery = $DBH->query("SELECT title, views, rating, thumb FROM posts WHERE category={$category} and status=1 ORDER BY {$sort} DESC");
This did work but I don't get the protection of the prepared statement. Any ideas on why one statement works but the other one does not?
Your bound parameter :sort gets expanded to a string literal, not a SQL identifier. That is, you are effectively evaluating something along the lines of:
ORDER BY 'rating' DESC
Since literals like this are constant for every record, it has no effect on the order of the resultset.
You can't parameterise identifiers, so must concatenate that part of the SQL into your prepared statement (the safest way is to set $sort from a predetermined set of safe values, based on whatever logic is appropriate to your needs).
You can't use place holders in ORDER BY clauses. See this question: How do I set ORDER BY params using prepared PDO statement?
In essence, I have a value that I have to call a couple times in my SQL query. Thus, is it possible to reuse the same named placeholder in the statement e.g.
SELECT :Param FROM Table WHERE Column = :Param, then simply bindValue(":Param"), and have the value be there for both :Params?
PDO::prepare states that "you cannot use a named parameter marker of the same name twice in a prepared statement", so I guess that's a no then.
You can if you set PDO::ATTR_EMULATE_PREPARES = true.
E.g. $connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);.
If you're using Laravel you can set this in an options array in config/database.php. e.g. PDO::ATTR_EMULATE_PREPARES => true
Apart from reuse, the main issue here is that you are trying to dynamically change col names.
This answer posted by an anonymous user on http://php.net/manual/en/pdo.prepare.php :
To those wondering why adding quotes to around a placeholder is wrong,
and why you can't use placeholders for table or column names:
There is a common misconception about how the placeholders in prepared
statements work: they are not simply substituted in as (escaped)
strings, and the resulting SQL executed. Instead, a DBMS asked to
"prepare" a statement comes up with a complete query plan for how it
would execute that query, including which tables and indexes it would
use, which will be the same regardless of how you fill in the
placeholders.
The plan for "SELECT name FROM my_table WHERE id = :value" will be the
same whatever you substitute for ":value", but the seemingly similar
"SELECT name FROM :table WHERE id = :value" cannot be planned, because
the DBMS has no idea what table you're actually going to select from.
Even when using "emulated prepares", PDO cannot let you use
placeholders anywhere, because it would have to work out what you
meant: does "Select :foo From some_table" mean ":foo" is going to be a
column reference, or a literal string?
When your query is using a dynamic column reference, you should be explicitly white-listing the columns you know to exist on the table, e.g. using a switch statement with an exception thrown in the default: clause.
Many queries like yours can be rewritten to use only one placeholder.
SELECT :Param FROM Table WHERE Column = :Param
would be the same as
SELECT Column FROM Table WHERE Column = :Param
But sometimes it's not that simple. For example:
SELECT *
FROM my_table
WHERE first_name LIKE :Param
OR last_name LIKE :Param
OR biography LIKE :Param
In such case you could reuse the parameter value storing it in a cross joined derived table (subquery in FROM clause):
SELECT t.*
FROM my_table t
CROSS JOIN (SELECT :Param as Param) AS x
WHERE first_name LIKE x.Param
OR last_name LIKE x.Param
OR biography LIKE x.Param
There's a workaround:
function search($criteria) {
$sql = "SELECT * FROM my_table
WHERE column_1 like CONCAT('%', :criteria1, '%')
OR column_2 like CONCAT('%', :criteria2, '%')
OR column_3 like CONCAT('%', :criteria3, '%')
";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':criteria1', $criteria);
$stmt->bindParam(':criteria2', $criteria);
$stmt->bindParam(':criteria3', $criteria);
$stmt->execute();
return($stmt->fetchAll(PDO::FETCH_ASSOC));
}
In summary, use different placeholders with the same criteria.