First you need to define "the nth largest salary in the table" clearly.
What about duplicates? If two people earn 1000 and one earns 800, is 800 then the 2nd or 3rd highest salary? Can salary
be NULL
? If so, ignore NULL
values? What if there are not enough rows?
Assuming ...
- Duplicate entries only count once - so 800 is considered 2nd in my example.
salary
can be NULL
. Ignore NULL
values.
- Return
NULL
or nothing (no row) if there are not enough rows.
Proper solution
SELECT salary
FROM (
SELECT salary, dense_rank() OVER (ORDER BY salary DESC NULLS LAST) AS drnk
FROM company
) sub
WHERE drnk = 8;
That's basically the same as Tim's answer, but NULLS LAST
prevents NULL
from coming first in descending order. Only needed if salary can be NULL
, obviously, but never wrong. Best supported with an index using the same sort order DESC NULLS LAST
. See:
FOR
loop as proof of concept
If you insist on using a FOR
loop for training purposes, this would be the minimal correct form:
CREATE OR REPLACE FUNCTION get_nth_max(n integer, OUT nth_max numeric)
LANGUAGE plpgsql AS
$func$
BEGIN
FOR nth_max IN
SELECT DISTINCT salary
FROM company
ORDER BY salary DESC NULLS LAST
LIMIT n
LOOP
-- do nothing
END LOOP;
END
$func$;
Working with numeric
instead of real
, because we don't want to use a floating-point number for monetary values.
Your version does a lot of unnecessary work. You don't need the overall max, you don't need to check anything in the loop, just run through it, the last assignment will be the result. Still inefficient, but not as much.
Notably, SELECT get_nth_max(10)
returns NULL
if there are only 9 rows, while the above SQL query returns no row. A subtle difference that may be relevant. (You could devise a function with RETURNS SETOF numeric
to return no row for no result ...)
Using an OUT
parameter to shorten the syntax. See: