Search code examples
sqlplsqloracle11gsql-order-byoracle12c

How to specify dynamic order by clause in Oracle


I have an Oracle Stored Procedure which is responsible for query data paginated. It receives the text to search, the offset and the page number. Now I want to add two new parameters to sort the query result before paginating it. My question is, how can I specify the dynamic order and direction.

For now, I now that I should use CASE clause to specify the column but I need to add if is ASC or DESC.

CREATE OR REPLACE PROCEDURE search_city_paged (
        in_search_key     IN                VARCHAR2,
        in_offset         IN                NUMBER,
        in_num_elements   IN                NUMBER,
        in_polling_place  IN                VARCHAR2,
        in_order_by       IN                NUMBER,
        in_order_dir      IN                VARCHAR2,
        out_city          OUT               SYS_REFCURSOR
    ) AS

BEGIN

    OPEN out_citizens FOR 
        SELECT COUNT(*) OVER (),
               cit.city_code,
               cit.city_name,
               cit.city_zip,
               cit.city_cities
        FROM city cit
        WHERE CATSEARCH(cit.FULL_TEXT_SEARCH, l_in_search_key, NULL)>0
        ORDER BY ???
        OFFSET in_offset ROWS FETCH FIRST in_num_elements ROWS ONLY;
END search_city_paged;

If I receive in the new parameters 1 and ASC, the result is sorted by city_code ASC. If I receive in the new parameters 1 and DESC, the result is sorted by city_code DESC. If I receive in the new parameters 2 and ASC, the result is sorted by city_name ASC. If I receive in the new parameters 2 and DESC, the result is sorted by city_name DESC.

And... in the same way on each column that I must allow sorting results.


Solution

  • Order by two outer CASE expressions, one ASC one DESC. In the first one check if you want to sort ASC and if not let the expression return a constant or NULL, i.e. it doesn't change the order. For the case you want to sort ASC add an inner CASE expression, that returns the column to sort by. Analogously handle the DESC case.

    ...
    ORDER BY CASE
               WHEN in_order_dir = 'ASC' THEN
                 CASE
                   WHEN in_order_by = 1 THEN
                     city_code
                   WHEN in_order_by = 2 THEN
                     city_name
                   ...
                 END
             END ASC,
             CASE
               WHEN in_order_dir = 'DESC' THEN
                 CASE
                   WHEN in_order_by = 1 THEN
                     city_code
                   WHEN in_order_by = 2 THEN
                     city_name
                   ...
                 END
             END DESC;
    

    Maybe you have to adapt it because of type incompatibilities between the columns, I can't tell that from what you posted. But it should convey the general concept.


    Edit:

    On the type problem:

    One possibility is to convert the columns to compatible data types in a way the order is kept. For instance, if you have a char_column (of the type char), a date_column and an integer_column (of date and integer) you could do

    to_char(date_column, 'YYYYMMDDHH24MISS')
    

    to convert the date and

    lpad(11, 38, '0')
    

    to convert the integer to char.

    The cleaner thing to do, but the one with more work to do (for the programmer, in terms of performance there should be no significant difference whatsoever) is to split the expressions once more. I.e. have an outer CASE for each type your columns are for each direction.

    Like

    ORDER BY CASE
               WHEN in_order_dir = 'ASC' THEN
                 CASE
                   WHEN in_order_by = 1 THEN
                     char_column1
                   WHEN in_order_by = 2 THEN
                     char_column2
                   ...
                 END
             END ASC,
             CASE
               WHEN in_order_dir = 'ASC' THEN
                 CASE
                   WHEN in_order_by = 3 THEN
                     date_column1
                   WHEN in_order_by = 4 THEN
                     date_column2
                   ...
                 END
             END ASC,
    

    and so on for all types and analogously for DESC. Remember, if the column isn't meant to be sorted after such CASE will yield NULL and sorting by that won't influence the order.