Subqueries in the ORDER BY
Generally speaking, you can use complex expression, and notably subqueries in the ORDER BY.
Here it would allow you to condition your first ORDER BY term to the id being one of your 3 most populated employee types:
SELECT
et.id,
et.employeetype_description,
count(e.employeetypeid) as thesortorder
FROM
employeetype et
LEFT JOIN
employee e ON e.employeetypeid = et.id
GROUP BY
et.id,
et.employeetype_description
ORDER BY
CASE WHEN et.id IN
(
SELECT employeetypeid
FROM employee
GROUP BY 1
ORDER BY COUNT(*) DESC
LIMIT 3
)
THEN count(e.employeetypeid) END
DESC NULLS LAST,
employeetype_description
;
Or use a correlated subquery:
WITH top3 AS
(
SELECT employeetypeid AS id, COUNT(*) count
FROM employee
GROUP BY 1
ORDER BY 2 DESC LIMIT 3
)
SELECT
et.id,
et.employeetype_description,
count(e.employeetypeid) as thesortorder
FROM
employeetype et
LEFT JOIN
employee e ON e.employeetypeid = et.id
GROUP BY
et.id,
et.employeetype_description
ORDER BY
(SELECT count FROM top3 WHERE top3.id = et.id) DESC NULLS LAST,
employeetype_description
;
But as you notice, we obtain something quite complex and, moreover, repetitive (COUNT() is computed twice).
And as you now, in any programming language as well as in data models, duplication is risky…
row_number() window function
… So in your specific case where thesortorder is used not only for the ORDER BY but also to be a result column,
I would first compute the COUNT(*) for every row of the desired resultset in a Common Table Expression
(with "Common" meaning we want to reuse it: once for its data, once as a sort criteria, we're good to go),
with an additional column telling where each row is placed compared to the others, thanks to the row_number() window function.
Then condition the use of the "count" sort criteria, to this position being computed as one of the first 3 rows of this CTE.
WITH e AS
(
SELECT
employeetypeid,
COUNT(*) count,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) pos
FROM employee
GROUP BY 1
)
SELECT
et.id,
et.employeetype_description,
e.count as thesortorder
FROM
employeetype et
LEFT JOIN
e ON e.employeetypeid = et.id
ORDER BY
CASE WHEN e.pos <= 3 THEN e.count END DESC NULLS LAST,
employeetype_description
;
The 3 methods are shown, in that order, in a db<>fiddle (where I gave different counts to C, D and E, to avoid a false positive due to incidentally already sorted data):
| id |
employeetype_description |
thesortorder |
| F |
F |
20 |
| A |
A |
10 |
| B |
B |
5 |
| C |
C |
1 |
| D |
D |
3 |
| E |
E |
2 |
Final note: the richness of window functions offers a lot of flexibility, take time to read their list to get a glimpse;
for example, you could use rank() instead of row_number() to address the problem of ties that Thorsten Kettner
pointed.
Or use percent_rank().
Even look for the "best placed cut" by finding the biggest gap between 2 successive counts, so that if you had 20, 10, 9, 8, 2, 1, 1 you took the 4th (8) with the first three.
ORDER BY least(4,row_number()over(order by count(*) desc)), id- in this method, the additional tie-breaking @ThorstenKettner's comment asks to specify, simply goes into the window spec: either as another expression after thecount(*), or as arank()ordense_rank()replacingrow_number().