I have a table like this:
a | user_id
----------+-------------
0.1133 | 2312882332
4.3293 | 7876123213
3.1133 | 2312332332
1.3293 | 7876543213
0.0033 | 2312222332
5.3293 | 5344343213
3.2133 | 4122331112
2.3293 | 9999942333
And I want to locate a particular row – 1.3293 | 7876543213 for example – and select the nearest 4 rows. 2 above, 2 below if possible.
Sort order is ORDER BY a ASC.
In this case I will get:
0.0033 | 2312222332
0.1133 | 2312882332
2.3293 | 9999942333
3.1133 | 2312332332
How can I achieve this using PostgreSQL? (BTW, I’m using PHP.)
P.S.: For the last or first row the nearest rows would be 4 above or 4 below.
Test case:
Query:
Returns result as depicted in the question. Assuming that
(a, user_id)is unique.It is not clear whether
ais supposed to unique. That’s why I sort byuser_idadditionally to break ties. That’s also why I use the window functionrow_number(), an notrank()for this.row_number()is the correct tool in any case. We want 4 rows.rank()would give an undefined number of rows if there were peers in the sort order.This always returns 4 rows as long as there are at least 5 rows in the table. Close to first / last row, the first / last 4 rows are returned. The two rows before / after in all other cases. The criteria row itself is excluded.
Improved performance
This is an improved version of what @Tim Landscheidt posted. Vote for his answer if you like the idea with the index. Don’t bother with small tables. But will boost performance for big tables – provided you have a fitting index in place. Best choice would be a multicolumn index on
(a, user_id).Major differences to @Tim’s version:
According to the question
(a, user_id)form the search criteria, not justa. That changes window frame,ORDER BYandWHEREclause in subtly different ways.UNIONright away, no need for an extra query level. You need parenthesis around the two UNION-queries to allow for individualORDER BY.Sort result as requested. Requires another query level (at hardly any cost).
As parameters are used in multiple places I centralized the input in a leading CTE.
For repeated use you can wrap this query almost ‘as is’ into an SQL or plpgsql function.