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

PostgreSQL:根据排序顺序选择最近的行,postgresql,sql-order-by,selection,rows,window-functions,Postgresql,Sql Order By,Selection,Rows,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

我有一张这样的桌子:

     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
我想找到一个特定的行,例如1.3293 | 7876543213,然后选择最近的4行。可能的话,上面2个,下面2个。 排序顺序是ASC的顺序

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

  0.0033  |  2312222332
  0.1133  |  2312882332
  2.3293  |  9999942333
  3.1133  |  2312332332
如何使用PostgreSQL实现这一点?顺便说一句,我正在使用PHP

注意:对于最后一行或第一行,最近的行在上面4行或下面4行。

测试用例: 查询: 与@Tim版本的主要差异: 根据问题a,用户id构成搜索条件,而不仅仅是a。这会以微妙不同的方式更改窗口框架、ORDERBY和WHERE子句

立即进行联合,不需要额外的查询级别。您需要在两个UNION查询周围加上括号,以允许单独的ORDERBY

按要求对结果进行排序。几乎不需要任何代价就需要另一个查询级别

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

结果:

DROP TABLE
CREATE TABLE
INSERT 0 8
  val   |    num     | relrnk 
--------+------------+--------
 0.0033 | 2312222332 |     -2
 0.1133 | 2312882332 |     -1
 1.3293 | 7876543213 |      0
 2.3293 | 9999942333 |      1
 3.1133 | 2312332332 |      2
(5 rows)
正如Erwin指出的,输出中不需要中间行。此外,应使用行号而不是秩

WITH ranked_lutsers AS (
        SELECT val, num
        -- ,rank() OVER (ORDER BY val) AS rnk
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
) SELECT that.val, that.num
        , (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-2 AND this.rnk+2 )
WHERE this.val = 1.3293
AND that.rnk <> this.rnk
        ;
更新2:始终选择四个,即使我们在列表的顶部或底部。这使得这个查询有点难看。但没有欧文的丑-

WITH ranked_lutsers AS (
        SELECT val, num
        -- ,rank() OVER (ORDER BY val) AS rnk
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
) SELECT that.val, that.num
        , ABS(that.rnk-this.rnk) AS srtrnk
        , (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-4 AND this.rnk+4 )
-- WHERE this.val = 1.3293
WHERE this.val = 0.1133
AND that.rnk <> this.rnk
ORDER BY srtrnk ASC
LIMIT 4
        ;
更新:带有嵌套CTE的版本,具有外部连接!!!。为了方便起见,我在表中添加了一个主键,这听起来是个好主意

WITH distance AS (
        WITH ranked_lutsers AS (
        SELECT id
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
        ) SELECT l0.id AS one
        ,l1.id AS two
        , ABS(l1.rnk-l0.rnk) AS dist
        -- Warning: Cartesian product below
        FROM ranked_lutsers l0
        , ranked_lutsers l1 WHERE l0.id <> l1.id

        )
SELECT lu.*
FROM lutser lu
JOIN distance di
ON lu.id = di.two
WHERE di.one= 1
ORDER by di.dist
LIMIT 4 
        ;
还有一个:

WITH prec_rows AS
  (SELECT a,
          user_id,
          ROW_NUMBER() OVER (ORDER BY a DESC) AS rn
   FROM tbl
   WHERE a < 1.3293
   ORDER BY a DESC LIMIT 4),
     succ_rows AS
  (SELECT a,
          user_id,
          ROW_NUMBER() OVER (ORDER BY a ASC) AS rn
   FROM tbl
   WHERE a > 1.3293
   ORDER BY a ASC LIMIT 4)
SELECT a, user_id
FROM
  (SELECT a,
          user_id,
          rn
   FROM prec_rows
   UNION ALL SELECT a,
                    user_id,
                    rn
   FROM succ_rows) AS s
ORDER BY rn, a LIMIT 4;

AFAIR WITH将实例化一个内存表,因此此解决方案的重点是尽可能限制其大小(在本例中为八行)。

您是否尝试过在窗口->秩上进行自联接?@wildplasser:情人眼里出西施。:不过,您的美貌有一些瑕疵:1返回4行,而不是5行,排除标准行。2使用行号,而不是排名。我对我的回答作了解释。3您的查询因第一行/最后一行的角案例而失败。如果this.val=5.3293 4您的WHERE子句仅在上筛选,则问题将用户id定义为筛选。现在还不清楚,这是否会产生影响。我没有看到OP不想要中间一排。关于排数和排名的事情你是对的,当然,如果有联系,他们的行为会有所不同。在排序中需要一个额外的列来确定地解决这个问题,我会修复它。WITH位不会查询整个表吗?@TimLandscheidt:它会,而且它需要。最终选择将只返回4行。如果我们有启发性的信息,比如在+/-0.1的范围内总是有4行,那么我们可以在WITH子句中有一个索引和预选-可能更快…@ErwinBrandstetter,但是为什么不在一行上使用索引呢?请参阅我的答案以获取示例。所需的行必须是所选行前面的四行和后面的四行的并集的一部分,不必查询整个表。您仍然缺少角案例。试试this.val=5.3293的位置,看问题的最后一行..Tnx。我从来都不喜欢花车上的选择。。。但是额外的条件会让我的问题和你的一样难看!尽可能的美丽,但尽可能的丑陋。就像生活一样+1非常好。使用大型表和合适的索引,您的解决方案将表现得更好。我对你的提问有几个问题,这些问题不适合发表评论。我给我的答案添加了一个版本。@TimLandscheidt我使用了选择a,用户id从预制行限制4-选择计数1从成功行/2联合所有[相同,但对于成功行]。但是你的可能更快。
WITH ranked_lutsers AS (
        SELECT val, num
        -- ,rank() OVER (ORDER BY val) AS rnk
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
) SELECT that.val, that.num
        , (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-2 AND this.rnk+2 )
WHERE this.val = 1.3293
AND that.rnk <> this.rnk
        ;
  val   |    num     | relrnk 
--------+------------+--------
 0.0033 | 2312222332 |     -2
 0.1133 | 2312882332 |     -1
 2.3293 | 9999942333 |      1
 3.1133 | 2312332332 |      2
(4 rows)
WITH ranked_lutsers AS (
        SELECT val, num
        -- ,rank() OVER (ORDER BY val) AS rnk
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
) SELECT that.val, that.num
        , ABS(that.rnk-this.rnk) AS srtrnk
        , (that.rnk-this.rnk) AS relrnk
FROM ranked_lutsers that
JOIN ranked_lutsers this ON (that.rnk BETWEEN this.rnk-4 AND this.rnk+4 )
-- WHERE this.val = 1.3293
WHERE this.val = 0.1133
AND that.rnk <> this.rnk
ORDER BY srtrnk ASC
LIMIT 4
        ;
  val   |    num     | srtrnk | relrnk 
--------+------------+--------+--------
 0.0033 | 2312222332 |      1 |     -1
 1.3293 | 7876543213 |      1 |      1
 2.3293 | 9999942333 |      2 |      2
 3.1133 | 2312332332 |      3 |      3
(4 rows)
WITH distance AS (
        WITH ranked_lutsers AS (
        SELECT id
        , row_number() OVER (ORDER BY val, num) AS rnk
        FROM lutser
        ) SELECT l0.id AS one
        ,l1.id AS two
        , ABS(l1.rnk-l0.rnk) AS dist
        -- Warning: Cartesian product below
        FROM ranked_lutsers l0
        , ranked_lutsers l1 WHERE l0.id <> l1.id

        )
SELECT lu.*
FROM lutser lu
JOIN distance di
ON lu.id = di.two
WHERE di.one= 1
ORDER by di.dist
LIMIT 4 
        ;
WITH prec_rows AS
  (SELECT a,
          user_id,
          ROW_NUMBER() OVER (ORDER BY a DESC) AS rn
   FROM tbl
   WHERE a < 1.3293
   ORDER BY a DESC LIMIT 4),
     succ_rows AS
  (SELECT a,
          user_id,
          ROW_NUMBER() OVER (ORDER BY a ASC) AS rn
   FROM tbl
   WHERE a > 1.3293
   ORDER BY a ASC LIMIT 4)
SELECT a, user_id
FROM
  (SELECT a,
          user_id,
          rn
   FROM prec_rows
   UNION ALL SELECT a,
                    user_id,
                    rn
   FROM succ_rows) AS s
ORDER BY rn, a LIMIT 4;