PostgreSQL:根据排序顺序选择最近的行

3 postgresql rows sql-order-by selection window-functions

我有一张这样的表:

     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
Run Code Online (Sandbox Code Playgroud)

我想找到一个特定的行 -1.3293 | 7876543213例如 - 并选择最近的 4 行。上面 2 个,如果可能的话,下面 2 个。
排序顺序是 ORDER BY a ASC

在这种情况下,我将得到:

  0.0033  |  2312222332
  0.1133  |  2312882332
  2.3293  |  9999942333
  3.1133  |  2312332332
Run Code Online (Sandbox Code Playgroud)

如何使用 PostgreSQL 实现这一目标?(顺便说一句,我正在使用 PHP。)

PS:对于最后一行或第一行,最近的行将是上面 4 行或下面 4 行。

Erw*_*ter 5

测试用例:

CREATE TEMP TABLE tbl(a float, user_id bigint);
INSERT INTO tbl VALUES
 (0.1133, 2312882332)
,(4.3293, 7876123213)
,(3.1133, 2312332332)
,(1.3293, 7876543213)
,(0.0033, 2312222332)
,(5.3293, 5344343213)
,(3.2133, 4122331112)
,(2.3293, 9999942333);
Run Code Online (Sandbox Code Playgroud)

询问:

WITH x AS (
    SELECT a
          ,user_id
          ,row_number() OVER (ORDER BY a, user_id) AS rn
    FROM   tbl
    ), y AS (
    SELECT rn, LEAST(rn - 3, (SELECT max(rn) - 5 FROM x)) AS min_rn
    FROM   x
    WHERE  (a, user_id) = (1.3293, 7876543213)
    )
SELECT *
FROM   x, y
WHERE  x.rn  > y.min_rn
AND    x.rn <> y.rn
ORDER  BY x.a, x.user_id
LIMIT  4;
Run Code Online (Sandbox Code Playgroud)

返回问题中描述的结果。假设这(a, user_id)是唯一的。

目前尚不清楚是否a应该是唯一的。这就是为什么我user_id另外排序以打破联系。这也是我使用window function 的原因row_number()而不是 rank()为此。row_number()在任何情况下都是正确的工具。我们想要 4 行。rank()如果排序顺序中有对等点,则会给出未定义的行数。

只要表中至少有 5 行,这总是返回 4 行。接近第一行/最后一行,返回第一行/最后4行。在所有其他情况下之前/之后的两行。条件行本身被排除在外。


提高性能

这是@Tim Landscheidt 发布的内容的改进版本。如果您喜欢索引的想法,请投票支持他的答案。不要打扰小桌子。但是会提高大表的性能- 前提是您有合适的索引。最好的选择将是一个多列索引(a, user_id)

WITH params(_a, _user_id) AS (SELECT 5.3293, 5344343213) -- enter params once
    ,x AS  (
    (
    SELECT a
          ,user_id
          ,row_number() OVER (ORDER BY a DESC, user_id DESC) AS rn
    FROM   tbl, params p
    WHERE  a < p._a
       OR  a = p._a AND user_id < p._user_id -- a is not defined unique
    ORDER  BY a DESC, user_id DESC
    LIMIT  5  -- 4 + 1: including central row
    )
    UNION ALL -- UNION right away, trim one query level
    (
    SELECT a
          ,user_id
          ,row_number() OVER (ORDER BY a ASC, user_id ASC) AS rn
    FROM   tbl, params p
    WHERE  a > p._a
       OR  a = p._a AND user_id > p._user_id
    ORDER  BY a ASC, user_id ASC
    LIMIT  5
    )
    )
    , y AS (
    SELECT a, user_id
    FROM   x, params p
    WHERE (a, user_id) <> (p._a, p._user_id) -- exclude central row
    ORDER  BY rn  -- no need to ORDER BY a
    LIMIT  4
    )
SELECT *
FROM   y
ORDER  BY a, user_id   -- ORDER result as requested
Run Code Online (Sandbox Code Playgroud)

与@Tim 版本的主要区别:

  • 根据问题(a, user_id)形式的搜索条件,而不仅仅是a. 这以微妙的不同方式改变了窗口框架ORDER BYWHERE子句。

  • UNION马上,不需要额外的查询级别。您需要在两个 UNION 查询周围加上括号以允许单独的ORDER BY.

  • 按要求对结果进行排序。需要另一个查询级别(几乎没有任何成本)。

  • 由于参数在多个地方使用,我将输入集中在领先的 CTE 中。
    为了重复使用,您可以几乎“按原样”将此查询包装到 SQL 或 plpgsql 函数中。