0

I have the following postgres stored function. Last CASE WHEN in ORDER BY returns data with incorrect ordering. However, simiral second CASE WHEN works as expected. I have double checked that sort_column and sort_direction are correct. Any help is appreciated

DB Fiddle: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=32ad76d21a9cb97518b92acf58ddf803

    CREATE OR REPLACE FUNCTION find_experts(BIGINT, _sort_column VARCHAR(256), _sort_direction VARCHAR(256)) 
    RETURNS TABLE (
    user_id BIGINT,
    name VARCHAR(256),
    email VARCHAR(256),
    email_validated BOOLEAN,
    is_verified BOOLEAN,
    rating FLOAT,
    is_promoted BOOLEAN,
    registry_date TIMESTAMP,
    picture VARCHAR(256),
    price FLOAT,
    biography VARCHAR(2028),
    short_bio VARCHAR(1000),
    timeslots VARCHAR(256)[],
    timezone VARCHAR(256)
) AS $$
    SELECT users.user_id, users.name, users.email, users.email_validated, users.is_verified, users.rating, users.is_promoted, users.registry_date,
    user_settings.picture, user_settings.price, user_settings.biography, user_settings.short_bio, user_settings.timeslots, user_settings.timezone
    FROM users 
    LEFT JOIN user_settings ON (users.user_id=user_settings.user_id)
    WHERE users.user_id IN 
    (SELECT user_id FROM users_tech) AND
    active_role='expert' AND price IS NOT NULL
    ORDER BY 
    CASE WHEN _sort_column = 'rating' AND _sort_direction = 'DESC' THEN rating END DESC,
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'ASC' THEN price END ASC, rating ASC, registry_date ASC,
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'DESC' THEN price END DESC, rating DESC, registry_date ASC
    OFFSET ($1 - 1) * 10
    LIMIT 10
$$ LANGUAGE SQL;

Function gets executed like that:

pool.query(`SELECT * FROM find_experts($1, $2, $3)`, [1, 'price', 'DESC'])

Actual result:

250
12
11
102
43
856
21
34
63
85

Expected result:

856
250
102
85
63
43
34
21
12
11
  • 1
    Why do you expect these results? I don't see any data nor SQL statement that would create any outcome. Please create something we can work with: https://dbfiddle.uk/?rdbms=postgres_14 – Frank Heikens Dec 15 '21 at 10:41
  • @FrankHeikens Hi, added the fiddle: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=32ad76d21a9cb97518b92acf58ddf803 – Карина Баринова Dec 15 '21 at 10:59
  • 1
    I think you wanted those to be multiple options in a single `case` statement. Currently, each of your `case` blocks constitutes a separate `order by` clause, with its own options. It should be `order by case when ... then, when ... then else...end offset...` not `order by case when...end, case when ...end`. Do you think this might be the case? – Zegarek Dec 15 '21 at 11:12
  • You could also switch to plpgsql function and just concatenate your `_sort_column` and `_sort_direction` in the right spot. [Here](https://stackoverflow.com/questions/8139618/postgresql-parameterized-order-by-limit-in-table-function) and [here](https://stackoverflow.com/questions/49775242/dynamic-order-by-and-asc-desc-in-a-plpgsql-function) are related threads. – Zegarek Dec 15 '21 at 11:21
  • @Zegarek Hi, changing the order by into *one* case works only if I have one column to that should be used in the ordering. Also I would be able to specify the order direction. Just tried it here: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=4d287077665d928e208b56ec43ec405f However, I have several columns in the order by clause, with different order directions here: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=32ad76d21a9cb97518b92acf58ddf803 – Карина Баринова Dec 15 '21 at 11:28
  • @Zegarek the problem with the original order by clause is that the very last CASE WHEN gets executed incorrectly, we could switch places of second and third and again only the last one returns incorrect order. That is weird – Карина Баринова Dec 15 '21 at 11:30
  • They all get executed correctly. I'm saying not all your `order by` clauses are within case statements. Here's a [dbfiddle](https://dbfiddle.uk/?rdbms=postgres_14&fiddle=74c48fd4d3d04eb4c71514d242d455c0) with a solution and I'll make it into an answer in a minute. – Zegarek Dec 15 '21 at 11:36
  • @Zegarek oh, I got it now. I thought it is necessary to specify all ordering columns under "their condition". Totally new information, thank you! Please create an answer and I will accept it – Карина Баринова Dec 15 '21 at 11:44

1 Answers1

1

Fix:

You need to wrap all your options in case statements in the order by clause for it to work as you expect:

ORDER BY 
    CASE WHEN _sort_column = 'rating' AND _sort_direction = 'DESC' THEN rating END DESC,
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'ASC' THEN price END ASC, 
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'ASC' THEN rating END ASC, 
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'ASC' THEN registry_date END ASC,
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'DESC' THEN price END DESC, 
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'DESC' THEN rating END DESC, 
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'DESC' THEN registry_date END ASC

Or switch to plpgsql and dynamic sql, to just concatenate your desired order by column and direction. Here's your db<>fiddle, fixed.

Explanation:

Your case statements constitute standalone order by clauses. I think you might assume that each line in that section is a different case, but they actually end up as a semi-constant set of columns to order by, so they would make more sense formatted like so:

ORDER BY 
    CASE WHEN _sort_column = 'rating' AND _sort_direction = 'DESC' THEN rating END DESC,--order column 1, sometimes skipped
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'ASC' THEN price END ASC, --order column 2, sometimes skipped
    rating ASC,        --order column 3, never skipped
    registry_date ASC, --order column 4, never skipped
    CASE WHEN _sort_column = 'price' AND _sort_direction = 'DESC' THEN price END DESC, --order column 5, sometimes skipped
    rating DESC,       --order column 6, never skipped
    registry_date ASC  --order column 7, never skipped

And each line beginning with CASE will either end up being what you want, or a null, because you did not specify an else block. Order by will skip those case statements evaluated to null, so in your example call, it ends up like this

ORDER BY 
    null::integer DESC, --skipped
    null::integer ASC, --skipped
    rating ASC, 
    registry_date ASC,
    price DESC, 
    rating DESC, 
    registry_date ASC

Note that PostgreSQL allows for a case or select in an order by to be evaluated to null, but doesn't allow a literal unless casted to something sortable.

Zegarek
  • 6,424
  • 1
  • 13
  • 24
  • It would be nice to be able to make that a single conditional block, but in this instance the direction couldn't be handled within a `when` case, or within a separate, consecutive `case` block. `order by case when true then col1 asc end` causes a syntax error, and so does `order by case when true then col1 end case when true then asc end`. – Zegarek Dec 15 '21 at 12:02