How to code an SQL query with ORDER BY as a variable?

hELLO,
Here is an image of a menu with an additional dropdown menu where a user can select the order by which the menu items will be displayed

Here is the code which gets the user selected order code

$menu_order = get_menu_order($user_id);
var_dump($menu_order);

Here is the result of var_dump()
C:\wamp64\www\AHopefully_order\pages\instrument.inc.php:6:string ‘3’ (length=1)

there are 4 options for the order, 1, 2, 3, and 4
If the user makes no selection the default option is 1

Here is the function

function get_menu_order($user_id)
{
  global $db;

  try
	{

	 $sql = "SELECT  `menu_order`
		     FROM `order_m`
			 WHERE `user_id` = :user_id";
			 
	 $stmt = $db->prepare($sql);
	 $stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
	 $stmt->execute();

     if($stmt->rowCount() == 0){
	   return 1;
	 }else{
	   $row = $stmt->fetchColumn();
	   return $row;
	 }

	}


  catch(Exception $e) 
	{
	   return false;        
	}
	

}//End function

Here is the function I coded to get the items for main menu

function get_all_couples($user_id, $start_date, $end_date, $hour_start, $hour_end,   
                         $days, $menu_order)
{	
	global $db;
	
	try
	{
		$sql = "SELECT DISTINCT `item`, SUM(`profit`) AS profit 
		        FROM `data` 
				WHERE DATE(`o_time`) BETWEEN :start_date AND :end_date 
			    AND TIME(`o_time`) BETWEEN :hour_start AND :hour_end
				AND WEEKDAY(`o_time`) IN ($days)
				AND `user_id` = :user_id
				GROUP BY item
				ORDER BY 
				       CASE WHEN @menu_order = 1 THEN item ASC
					   CASE WHEN @menu_order = 2 THEN item DESC
					   CASE WHEN @menu_order = 3 THEN profit ASC
					   CASE WHEN @menu_order = 4 THEN profit DESC";
		$stmt = $db->prepare($sql);
		$stmt->bindParam(':start_date', $start_date, PDO::PARAM_STR);
		$stmt->bindParam(':end_date', $end_date, PDO::PARAM_STR);
		$stmt->bindParam(':hour_start', $hour_start, PDO::PARAM_STR);
		$stmt->bindParam(':hour_end', $hour_end, PDO::PARAM_STR);
		$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
		$stmt->bindParam(':menu_order', $menu_order, PDO::PARAM_INT);
        $stmt->execute();
		
		if($stmt->rowCount() == 0)
		return 0;
		else
		return $stmt->fetchAll(PDO::FETCH_ASSOC);
		
	}
	catch(Exception $e) 
	{
	   echo $e;
	   die();
	   return false;        
	}
}

and Here is the error message :

PDOException: SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens in C:\wamp64\www\AHopefully_order\includes\fetch.php:199 Stack trace: #0 C:\wamp64\www\AHopefully_order\includes\fetch.php(199): PDOStatement->execute() #1 C:\wamp64\www\AHopefully_order\pages\instrument.inc.php(10): get_all_couples(‘30’, ‘2017-08-14’, ‘2023-03-07’, ‘00:00:00’, ‘23:59:59’, ‘0,1,2,3,4,5,6’, 1) #2 C:\wamp64\www\AHopefully_order\instrument.php(4): require(‘C:\wamp64\www\A…’) #3 {main}

What is the right syntax for SQL case of the ORDER BY in this query ?

Thanks

Well for starters, you’ve put @menu_order instead of :menu_order.

Thanks for the quick answer,
I tried that too before posting this question
I got this error message:

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘ASC CASE WHEN 1 = 2 THEN item DESC CASE WHEN 1 = 3 THEN profit’ at line 9 in C:\wamp64\www\AHopefully_order\includes\fetch.php:199 Stack trace: #0 C:\wamp64\www\AHopefully_order\includes\fetch.php(199): PDOStatement->execute() #1 C:\wamp64\www\AHopefully_order\pages\instrument.inc.php(10): get_all_couples(‘30’, ‘2017-08-14’, ‘2023-03-07’, ‘00:00:00’, ‘23:59:59’, ‘0,1,2,3,4,5,6’, 1) #2 C:\wamp64\www\AHopefully_order\instrument.php(4): require(‘C:\wamp64\www\A…’) #3 {main}

When do I use @ ? or : before the variable name?
What is the difference ?
I searched the internet
I found different examples but no explanations

: is for PHP parameter replacements. @ is for MariaDB User Defined Variables.

Your PDO exception tells you what the problem is:

So it thinks there’s a problem right before “ASC”, which makes sense. Your CASE statement is expecting the thing immediately after a “THEN [thing]” to be an END, or an ELSE, or another WHEN. Instead, it sees “ASC”. Which doesnt make sense to it, so it throws an error at you.

Here’s the rub; A MariaDB ORDER clause can take certain “dead” operators. This is a valid MariaDB statement (or, the end of one, anyway)
ORDER BY null DESC, null ASC, null DESC, null ASC
(This statement would cause it not to be sorted, or rather to follow the default sort)

Guess what comes out of a CASE statement if it matches none of the cases and hits an END…
SELECT CASE WHEN 1 = 2 THEN "False" END

CASE WHEN 1 = 2 THEN “False” END
null

So we need to tell our query only to sort by the columns that match, and null the rest

Insert an END after each of the table names.

If, for example, :menu_order contains a 2, your query will end up being:
ORDER BY null ASC, item DESC, null ASC, null DESC, which will cause the table to sort itself correctly. (And is also why null is a reserved word. No confusion about it possibly being a column name…)

To be honest I see no advantage of putting the logic into the query. Why not create a string depending on the menu_order content with a simple php switch and append that string to the query string?
In this case you do not need to use prepared statements as your script is creating the content depending on a variables content but not with the content itself

ORDER
    BY CASE WHEN :menu_order = 1 THEN item 
            WHEN :menu_order = 2 THEN -1 * item 
            WHEN :menu_order = 3 THEN profit 
                                 ELSE -1 * profit 
        END  ASC	     

Thanks for your reply

Here is the case now

ORDER BY 
                        CASE WHEN :menu_order = 1 THEN item END, 
                        CASE WHEN :menu_order = 2 THEN item END DESC,
                        Case WHEN :menu_order = 3 THEN profit END,
                        CASE WHEN :menu_order = 4 THEN profit END DESC

the first 2 options - sorting by abc works well
The options where sorting is by profit, and should go from max to min or the opposit I get a kind of random order of posotive and negative profits.
The order changes direction when I change between profit up and profit down

When I add ASC to the case the problem is solved

CASE WHEN :menu_order = 1 THEN item END ASC, 
                        CASE WHEN :menu_order = 2 THEN item END DESC,
                        CASE WHEN :menu_order = 3 THEN profit END ASC,
                        CASE WHEN :menu_order = 4 THEN profit END DESC

ASC is a default option so why do I have to add it to make the query work ?
Thanks

What type of field is profit? (For that matter, what’s the structure of the table as a whole)
Can you give us an example of what you get as output? (It may not be as random as you think…)

Here is the table structure

This sql query works well

SELECT DISTINCT `item`, SUM(`profit`) AS profit 
		        FROM `data` 
				WHERE DATE(`o_time`) BETWEEN :start_date AND :end_date 
			    AND TIME(`o_time`) BETWEEN :hour_start AND :hour_end
				AND WEEKDAY(`o_time`) IN ($days)
				AND `user_id` = :user_id
				GROUP BY item
				ORDER BY profit ASC

And I checked the CASE again, the problem is not solved

you actually have 4 CASE exprtessions

needlessly awkward, with all those NULLs

please see the solution i posted with 1 CASE

It’s also probably not a great idea to reuse “profit” as an alias if its also a column in the table…(are you trying to sort by the column, or by the SUM?)

Just a small hint. Never use float for a price value

agreed – not because of syntax (it does work) but semantics

also, DISTINCT is redundant when using GROUP BY

O.K. but why not ? What should I use for a number like 513.23 or -24.88 ?

Use cents and store those as integers, i.e., 51323 and -2488

The reason for doing this is that floating point operations are known to produce incorrect results due the way they are implemented. For stuff like graphics (where floating points are used a lot) this is fine, but for money you really don’t want to deal with weird rounding errors.

I tried it with and without cpmma

SELECT `item`, SUM(`profit`) AS profit 
		        FROM `data` 
				WHERE DATE(`o_time`) BETWEEN :start_date AND :end_date 
			    AND TIME(`o_time`) BETWEEN :hour_start AND :hour_end
				AND WEEKDAY(`o_time`) IN ($days)
				AND `user_id` = :user_id
				GROUP BY item
				ORDER BY 
                        CASE WHEN :menu_order = 1 THEN item END,  
                             WHEN :menu_order = 2 THEN item END DESC, 
                             WHEN :menu_order = 3 THEN profit END, 
                             ELSE profit END DESC

I got this error messdage:
PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘WHEN 1 = 2 THEN item END DESC, WHEN 1 = 3 THEN pr’ at line 10

On the other hand this

ORDER BY 
                        CASE WHEN :menu_order = 1 THEN item END,  
                        CASE WHEN :menu_order = 2 THEN item END DESC, 
                        CASE  WHEN :menu_order = 3 THEN profit END, 
                        CASE  WHEN :menu_order = 3 THEN profit END DESC

works. But there;s the problem that frofit is a colomn and an alias …

What do I miss here ?

I believe Rudy referred you to the post he made, #6 above. I’ll copy/paste it here, but this is the work of @r937

His approach gets rid of the whole asc/desc thing by negating the values, which will then sort in the opposite order automagically being numeric values.

ORDER
    BY CASE WHEN :menu_order = 1 THEN item 
            WHEN :menu_order = 2 THEN -1 * item 
            WHEN :menu_order = 3 THEN profit 
                                 ELSE -1 * profit 
        END  ASC

I replied to post #6 with changes I made following Rudy’s suggestion and I also added the error message I received.
P.S
Are ASC / DESC the source of the error?
Can you negate a string of letters?

Sorry, I’ve only been about 1/2 following along, but in this particular case, I think the KISS (Keep It Simple Stupid) approach might just be better. Instead of having this gobbledygook in the SQL, do it in the PHP instead.

try
	{
		$sql = "SELECT DISTINCT `item`, SUM(`profit`) AS profit 
		        FROM `data` 
				WHERE DATE(`o_time`) BETWEEN :start_date AND :end_date 
			    AND TIME(`o_time`) BETWEEN :hour_start AND :hour_end
				AND WEEKDAY(`o_time`) IN ($days)
				AND `user_id` = :user_id
				GROUP BY item
				ORDER BY";
        switch ($menu_order) {
             case "1":
                 $sql .= " item ASC";
                 break;
             case "2":
                 $sql .= " item DESC";
                 break;
             case "3":
                 $sql .= " profit ASC";
                 break;
             case "4":
                 $sql .= " profit DESC";
                 break;
             default:
                 $sql .= " item ASC";
                 break;
        }
		$stmt = $db->prepare($sql);
		$stmt->bindParam(':start_date', $start_date, PDO::PARAM_STR);
		$stmt->bindParam(':end_date', $end_date, PDO::PARAM_STR);
		$stmt->bindParam(':hour_start', $hour_start, PDO::PARAM_STR);
		$stmt->bindParam(':hour_end', $hour_end, PDO::PARAM_STR);
		$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
        $stmt->execute();

Take the menu order out of the bind entirely.

alias profit to allprofit and the order by case to

 ORDER BY 
                        CASE WHEN :menu_order = 1 THEN item END,  
                        CASE WHEN :menu_order = 2 THEN item END DESC, 
                        CASE  WHEN :menu_order = 3 THEN allprofit END, 
                        CASE  WHEN :menu_order = 3 THEN allprofit END DESCchanged the

I saw examples on the web where an alias if used for ORDER BY

I got an error saying that it is not acceptable
PDOException: SQLSTATE[42S22]: Column not found: 1247 Reference ‘allprofit’ not supported (reference to group function)
what the group function has to do with it ?

I thought maybe there is a problem usig a column and an alias at the same case so I gave item an alias and changed the case accordingly. It didn;t help
I also tried sun(profit) in order by
How do I uss a sum of trades SUM(profit) to sort items ?