Context

Sorting query by custom order, usually, by a state column. The most intuitive way is using CASE WHEN:

SELECT * FROM orders ORDER BY CASE
    WHEN orders.status = 'new' THEN 1
    WHEN orders.status = 'processing' THEN 2
    WHEN orders.status = 'done' THEN 3
    WHEN orders.status = 'cancelled' THEN 4
    WHEN orders.status = 'awaiting_confirmation' THEN 5
    ELSE 99
  END DESC;

Postgresql support array_position(), so we could write like this.

SELECT * FROM orders ORDER BY array_position(ARRAY['new', 'processing', 'done', 'cancelled', 'awaiting_confirmation']::varchar[], orders.status) DESC;

And finally, some suggests on stackoverflow:

SELECT *
FROM orders
JOIN unnest(ARRAY['new', 'processing', 'done', 'cancelled', 'awaiting_confirmation']) WITH ORDINALITY o(status, ord) USING (status)
ORDER BY o.ord DESC;

Which one is best, or better, when we should use which way? 2 factors to concern: readability and performance. About readability, array_position is prefered by devs, but CASE WHEN is voted by non-dev stackholders. About performance, a benchmark tells more than any word.

Benchmark

I scripted to generate random 1mil records with random status to compare.

DROP TABLE testusers;
CREATE TABLE testusers(
    id BIGINT GENERATED ALWAYS AS IDENTITY,
    PRIMARY KEY(id),
    status VARCHAR(50) NOT NULL CHECK (status IN ('new', 'processing', 'done', 'cancelled', 'awaiting_confirmation'))
);

INSERT INTO testusers(status)
SELECT CASE
WHEN RANDOM() < 0.2 THEN 'new' 
WHEN RANDOM() < 0.4 THEN 'processing' 
WHEN RANDOM() < 0.6 THEN 'done' 
WHEN RANDOM() < 0.8 THEN 'cancelled' 
ELSE 'awaiting_confirmation' END
FROM generate_series(1, 1000000);


SELECT * FROM testusers ORDER BY array_position(ARRAY['new', 'processing', 'done', 'cancelled', 'awaiting_confirmation']::varchar[], testusers.status) ASC, testusers.id LIMIT 1000;

SELECT * FROM testusers ORDER BY CASE
    WHEN testusers.status = 'new' THEN 1
    WHEN testusers.status = 'processing' THEN 2
    WHEN testusers.status = 'done' THEN 3
    WHEN testusers.status = 'cancelled' THEN 4
    WHEN testusers.status = 'awaiting_confirmation' THEN 5
    ELSE 99
  END ASC, testusers.id LIMIT 1000;

SELECT *
FROM testusers
JOIN unnest(ARRAY['new', 'processing', 'done', 'cancelled', 'awaiting_confirmation']) WITH ORDINALITY o(status, ord) USING (status)
ORDER BY o.ord ASC, testusers.id LIMIT 1000;

Conclusion

With test data, all 3 run with similar result, where JOIN unnest is slightly faster.
But if we order result by addition column, CASE approach is fastest 🤔

So, try on your dataset to figure out which approach are fit to case.