50

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!

Marlorn
  • 529
  • 1
  • 4
  • 7
  • See also [Can PHP PDO Statements accept the table name as parameter?](http://stackoverflow.com/q/182287/157957) – IMSoP Sep 01 '13 at 22:51

7 Answers7

68

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.

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • 1
    If you have an array of valid strings and retype what you've got like `$input = (string)$input;` to **`(string)`** you can then simply check `if( in_array($input, $valid_orderbys_array) )` it should basically do the same. – jave.web Jul 08 '16 at 20:11
15

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.
Pascal MARTIN
  • 395,085
  • 80
  • 655
  • 663
  • 3
    To be honest - it **IS** possible to bind *values* (not identifiers) in an `ORDER BY` clause – zerkms Sep 01 '13 at 22:15
  • 2
    This is partially right. You can only use bind placeholders to supply **values** . It is *never* possible to use bind placeholders to supply **identifiers** (like table names, column names), SQL **keywords**, or any other part of the SQL syntax other than a **value**. (Basically, a bind placeholder can take the place of string literal, a date literal or a numeric literal. Period. But as zerkms points out, since it is possible to use literals as parts of an expression in an `ORDER BY` clause, it **is** in fact possible to use bind placeholders as part of expressions in an `ORDER BY` clause. – spencer7593 Jan 16 '15 at 23:55
10

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();
rray
  • 2,518
  • 1
  • 28
  • 38
7

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.

goat
  • 31,486
  • 7
  • 73
  • 96
0

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.

cavila
  • 7,834
  • 5
  • 21
  • 19
0

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".

bithom
  • 186
  • 2
  • 14
-3

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.

Turbotoast
  • 141
  • 5
  • If he wasn't going to change the orderby column and direction, then I expect there wouldn't be any point at all in having them be variables. – Kzqai Dec 22 '11 at 18:12