PostgreSQL 获取每个范围的 top-k 最小值

Ale*_*ros 4 postgresql-9.3 greatest-n-per-group top

假设我有以下数据:

| f1 | f2 | f3 |
|----|----|----|
|  1 |  1 |  1 |
|  1 |  1 |  5 |
|  1 |  2 |  3 |
|  1 |  2 |  6 |
|  1 |  3 |  4 |
|  1 |  3 |  7 |
|  2 |  1 |  2 |
|  2 |  1 | 22 |
|  2 |  2 |  3 |
|  2 |  2 |  4 |
Run Code Online (Sandbox Code Playgroud)

每个 f1,f2 组合最多有两个 f3 值。

对于这个特定示例,我想获得每个 f1 和范围 (f2 - max(f2) per f1) 的前 2 个最小值。

示例输出:

| f1 | RNG| f3 |
|----|----|----|
|  1 |1 -3|  1 |
|  1 |1 -3|  3 |
|  1 |2 -3|  3 |
|  1 |2 -3|  4 |
|  1 |3 -3|  4 |
|  1 |3 -3|  7 |
|  2 |1- 2|  2 |
|  2 |1- 2|  3 |
|  2 |2- 2|  3 |
|  2 |2- 2|  4 |
Run Code Online (Sandbox Code Playgroud)

不需要创建字段 RNG。我添加它只是为了表明 forf1=1有 3 个范围:1-3, 2-3, 3-3f2for的不同值创建f1=1。对于每个这样的范围,我想计算每个 f1 和范围的前 k 个最小值。

SQL小提琴在这里:

http://sqlfiddle.com/#!15/9ddbb/1

可以通过以下方式构建范围:

SELECT DISTINCT s1.f1,s1.f2 AS range_from ,s2.f2 AS range_to
FROM dbTable s1,
(SELECT f1,MAX(f2) AS f2 FROM dbTable
GROUP BY f1) s2
WHERE s1.f1=s2.f1
ORDER BY s1.f1,s1.f2;
Run Code Online (Sandbox Code Playgroud)

有没有办法在不构建中间数据库表的情况下实现这一目标?

ype*_*eᵀᴹ 6

另一种方法,使用LATERAL9.3+ 版本提供的语法:

WITH t AS
  ( SELECT f1, f2,  
           MAX(f2) OVER (PARTITION BY f1) AS range_to
    FROM dbTable 
    GROUP BY f1, f2
  ) 
SELECT t.f1, 
       -- t.f2 AS range_from, t.range_to,
       t.f2 || ' - ' || t.range_to AS RNG,
       x.f3
FROM t
  CROSS JOIN LATERAL
    ( SELECT f3 
      FROM dbTable 
      WHERE f1 = t.f1 AND f2 >= t.f2 
      ORDER BY f3 LIMIT 2
    ) AS x (f3)
ORDER BY t.f1, t.f2, x.f3 ;
Run Code Online (Sandbox Code Playgroud)

SQLfiddle测试。

或者没有 CTE:

SELECT t.f1, 
       -- t.f2 AS range_from, 
       -- MAX(t.f2) OVER (PARTITION BY t.f1) AS range_to,
       t.f2 || ' - ' || MAX(t.f2) OVER (PARTITION BY t.f1) AS RNG,
       x.f3
FROM dbTable AS t
  CROSS JOIN LATERAL
    ( SELECT f3 
      FROM dbTable 
      WHERE f1 = t.f1 AND f2 >= t.f2 
      ORDER BY f3 LIMIT 2
    ) AS x (f3)
GROUP BY t.f1, t.f2, x.f3
ORDER BY t.f1, t.f2, x.f3 ;
Run Code Online (Sandbox Code Playgroud)