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.
Related
I'm having problems using params in the ORDER BY section of my SQL. It doesn't issue any warnings, but prints out nothing.
$order = 'columnName';
$direction = 'ASC';
$stmt = $db->prepare("SELECT field from table WHERE column = :my_param ORDER BY :order :direction");
$stmt->bindParam(':my_param', $is_live, PDO::PARAM_STR);
$stmt->bindParam(':order', $order, PDO::PARAM_STR);
$stmt->bindParam(':direction', $direction, PDO::PARAM_STR);
$stmt->execute();
The :my_param works, but not :order or :direction. Is it not being internally escaped correctly? Am I stuck inserting it directly in the SQL? Like so:
$order = 'columnName';
$direction = 'ASC';
$stmt = $db->prepare("SELECT * from table WHERE column = :my_param ORDER BY $order $direction");
Is there a PDO::PARAM_COLUMN_NAME constant or some equivalent?
Thanks!
Yes, you're stuck inserting it directly in the SQL. With some precautions, of course. Every operator/identifier must be hardcoded in your script, like this:
$orders=array("name","price","qty");
$key=array_search($_GET['sort'],$orders);
$order=$orders[$key];
$query="SELECT * from table WHERE is_live = :is_live ORDER BY $order";
Same for the direction.
I wrote a whitelisting helper function to be used in such cases, it greatly reduces the amount of code that needs to be written:
$order = white_list($order, ["name","price","qty"], "Invalid field name");
$direction = white_list($direction, ["ASC","DESC"], "Invalid ORDER BY direction");
$sql = "SELECT field from table WHERE column = ? ORDER BY $order $direction";
$stmt = $db->prepare($sql);
$stmt->execute([$is_live]);
The idea here is to check the value and raise an error in case it is not correct.
I don't think you can :
Use placeholders in an order by clause
Bind column names : you can only bind values -- or variables, and have their value injected in the prepared statement.
It's possible use prepared statements in ORDER BY clause, unfortunately you need pass the order of column insted of the name and is required set PDO_PARAM_INT with type.
In MySQL you can get the order of columns with this query:
SELECT column_name, ordinal_position FROM information_schema.columns
WHERE table_name = 'table' and table_schema = 'database'
PHP code:
$order = 2;
$stmt = $db->prepare("SELECT field from table WHERE column = :param ORDER BY :order DESC");
$stmt->bindParam(':param', $is_live, PDO::PARAM_STR);
$stmt->bindParam(':order', $order, PDO::PARAM_INT);
$stmt->execute();
I don't think you can get ASC/DESC as part of the prepared statement, but the column you can if you list them all in the sql query like so:
// Validate between 2 possible values:
$sortDir = isset($_GET['sortDir']) && $_GET['sortDir'] === 'ASC' ? 'ASC' : 'DESC';
$sql = "
...
order
by
case :orderByCol
when 'email' then email
when 'age' then age
else surname
end
$sortDir
";
$stmt = $db->prepare($sql);
$stmt->bindParam(':orderByCol', $someColumn);
$stmt->execute();
Since ASC/DESC is only two possible values, you can easily validate and select between them as hardcoded values using php code.
You could also make use of the ELT(FIELD(,,,,,),,,,,) functions for this, but then ordering will always be done as a string, even if the column is a numeric data type that should be sorted using numeric semantics / collation.
Unfortunely I guess you could not make it with prepared statements.
It would make it no cacheable since different columns may have values that could be sorted with special sorting strategies.
Create query by using standard escapes and execute it directly.
It is possible . You can use number instead of field name in the 'order by' clause. This is a number starting from 1 and is in the order of field names in the query. And you can concatenate a string in for ASC or DESC. For example
"Select col1,col2,col3 from tab1 order by ? " + strDesc + " limit 10,5".
strDesc=" ASC" / " DESC".
If I'm not entirely mistaken, Pascal is right.
The only binding possible in PDO is the binding of values, as you did with the ':my_param' parameter.
However, there's no harm done in:
$stmt = $db->prepare("SELECT field from table WHERE column = :my_param ORDER BY ".$order ." ".$direction);
$stmt->bindParam(':my_param', $is_live, PDO::PARAM_STR);
$stmt->execute();
The only thing to take notice of would be the correct escaping of $order and $direction, but since you set them manually and didn't set them via user input, I think you're all set.
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.
I have this code here:
if(isset($_POST['possible_new_dd'])){
$dd = $_POST['possible_new_dd'];
$id = $_POST['possible_new_dd_id'];
$sql = mysqli_query($connection, "SELECT SUM(COILS),MACHINE, (SELECT COILS FROM ORDERS WHERE ID='$id') FROM ORDERS WHERE MACHINE=(SELECT MACHINE FROM ORDERS WHERE ID='$id') AND C_DD='$dd';");
$row = mysqli_fetch_array($sql);
$json->coils = $row['SUM(COILS)'];
$json->machine = $row['MACHINE'];
$json->new_coil = $row['?THE SELECT QUERY?'];
$j = json_encode($json);
echo$j;
}
In the json string I can read machine and coils, but I cant find a way to read this bit (SELECT COILS FROM ORDERS WHERE ID='$id').
How do I get this value to be stored into a json string?
Thanks
The simplest way is use an alias eg MY_COL
$sql = mysqli_query($connection, "SELECT SUM(COILS),MACHINE,
(SELECT COILS FROM ORDERS WHERE ID='$id') AS MY_COL
FROM ORDERS
WHERE MACHINE=(SELECT MACHINE FROM ORDERS WHERE ID='$id') AND C_DD='$dd';");
and so you can access easly to the result
$json->my_col= $row['MY_COL'];
but you should not use php var inside in SQL. (you are at risk for sql injection ) To avoid this you should use binding param so take a look at this feature and refactor your code
In PHP, I wrote a php function in controller getItems, to get all the items when I call through ajax using javascript/jquery. I am passing a param limit to limit the items to get from server.
This works fine but how to use the same method to get all the items? Currently, the below query is working if and only if I provide limit param value.
"SELECT DISTINCT name, id from TABLE_NAME
ORDER BY shared_date DESC
LIMIT #~~limit~~#";
How to get all the items by modifying the same query?
Currently, I am solving this by having two different queries, one with limit and other without limit. (which I feel messy)
One way would be to set the limit as an optional argument in getItems.
For example something like this:
function getItems($limit=false){
$sql = "SELECT DISTINCT name, id from TABLE_NAME
ORDER BY shared_date DESC "
if($limit){
$sql .= "LIMIT $limit";
}
...
}
I don't know if you were going to submit the query in a prepared statement or not, so I left it like this (sql-injectable)
$limit = '';
if ($paramLimit != NULL && is_int($paramLimit)) {
$limit = " LIMIT $paramLimit ";
}
"SELECT DISTINCT name, id from TABLE_NAME
ORDER BY shared_date DESC
$limit";
look at this table please
table
|id| |name| |order|
i must get the rows, where name = something and order = somevalue
so i write
select `id` from `table` where `name` = 'something' and `order` = 'somevalue'
but depend on php logic, sometimes i need to get all rows, where name = something, independently of order value. i don't want to change the query structure, because in practise there are many number of fields, and possible count of queries will become very big. so i want to save the structure of query, and when i need to select just by name, i want to write something like this:
select `id` from `table` where `name` = 'something' and `order` = any value
is it possible?
thanks
Well, it's kind of a hack, but if you really need to do this, it'll work like this:
select `id` from `table` where `name` = 'something' and `order` = `order`
Then you're just saying "wherever order is the same as itself", so it's always true.
No, this is not possible. You need to change the structure (optionally to a LIKE so you can use '%', but that's very ugly).
However, you don't need to write a different query to handle every possible combination. You can simply create the query dynamically:
//create base query
$query = "select `id` from `table` where `name` = 'something' ";
//add order if we need it
if ($use_order)
$query .= "and `order` = 'somevalue' ";
//repeat for any other optional part
Note that you should of course still take proper measures to avoid SQL injection and other security issues - I have not included this here in order to keep things simple.
If you are using bound parameters, it would be impossible.
If you just substitute the values, you can do the following:
select `id` from `table` where `name` = 'something' and `order` = `order`
This is a common theme with database queries - you need a variable query depending on how much filtering you wish to apply to the data it queries. You could go the route of having your query repeated as a string throughout your code, but that is bad practice as it increases the complexity of the code needlessly. Chances for errors occur if you need to change the query for some reason, and have to change it in multiple places as a result.
The better solution is to create a function which builds the query for you execute:
function buildMyQuery($name, $order = null) {
$sql = "SELECT `id` FROM `table` WHERE `name`='$name'";
if ($order != null) {
$sql .= " AND `order`='$order'";
}
return $sql;
}
You could then run this for just using the 'name' field:
$query = buildMyQuery("somename");
Or this for using both fields:
$query = buildMyQuery("somename", "someorder");
As someone mentioned above, this code is deliberately simplified and contains no contingency for possibly dangerous data passed in via $name or $order. You would need to use mysql_real_escape_string or something similar to clean the data first, at the beginning of the function before either piece of data is used.
Dynamic query generation is a fact of life as Byron says, so I would become accustomed to it now rather than using hack-ish workarounds.
I don't think you have any choice... Once you do a selection you can't "unfilter" and get more rows.
You should just use two queries-- either two independent queries, or one that selects on the name into a temp table, and then (optionally) one that further selects on the order attribute.
Like Chad said above, just set the column equal to itself. But be careful, on some platforms / install configurations, NULL != NULL:
select `id` from `table` where `name` = 'something' and coalesce(`order`,'') = coalesce(`order`,'')
On reflection, I have a better answer. My colleague showed me a way this can be done.
My example...
Select rentals.* From rentals Where ((? = '') OR (user_id = ?))
The variables must be the same.
If they are both 5 for example, the first boolean will be false, but the second will be true, for the rows where the users id is 5.
If you require "all", setting as an empty string will result in all rows being seen to meet the where clause condition.
Can't you just use a not null query here?
select `id` from `table` where `name` = 'something' and `order` is not null;
You should be able to do it like this:
select `id` from `table` where `name` <>'' and `order` <>''
That will select anywhere that the value is not equal to blank.
$sql = "SELECT * FROM auctions WHERE id = id ";
if ($category !== "ANY") {
$sql .= "AND category = $category "; }
if ($subcategory !== "ANY") {
$sql .= "AND subcategory = $subcategory "; }
if ($country !== "ANY") {
$sql .= "AND country = $country "; }
$sql .= "ORDER BY $order $sort LIMIT $limit OFFSET $offset";