有没有办法 SELECT n ON (如 DISTINCT ON,但每个都不止一个)

Sha*_*vil 4 postgresql greatest-n-per-group distinct

我有一个us_customers看起来像这样的表(有数十万行):

+----------+----------+
|    id    | us_state |
+----------+----------+
| 12345678 | MA       |
| 23456781 | AL       |
| 34567812 | GA       |
| 45678123 | FL       |
| 56781234 | AZ       |
| 67812345 | MA       |
| 78123456 | CO       |
| 81234567 | FL       |
+----------+----------+
Run Code Online (Sandbox Code Playgroud)

...我想n从每个us_state.

有没有办法在 PostgreSQL 9.3 中干净利落地做到这一点?

我可以通过以下方式us_state轻松地从每个客户那里获得一位客户:

SELECT DISTINCT ON (us_state) id
FROM us_customers
ORDER BY us_state;
Run Code Online (Sandbox Code Playgroud)

但是,如果我想要来自每个州的三个客户,有没有一种方法可以在不多次运行相同查询的情况下做到这一点?

Jul*_*eur 6

您可以使用窗口函数进行编号和排序id,并且只保留第一个值:us_stateROW_NUMBER()n

SELECT * 
FROM (
  SELECT *
    , ROW_NUMBER() OVER(PARTITION BY us_state ORDER BY id) as n
  FROM data
) as ord
WHERE n <= 2
ORDER BY us_state
;
Run Code Online (Sandbox Code Playgroud)

或者您可以使用子查询 CROSS JOIN:

SELECT l.*
FROM (
  SELECT DISTINCT us_state FROM data
) as s
CROSS JOIN LATERAL (
  SELECT * 
  FROM data d
  WHERE d.us_state = s.us_state
  ORDER BY id
  LIMIT 2
) as l
ORDER BY l.us_state
;
Run Code Online (Sandbox Code Playgroud)
  • 示例 SQL 小提琴在这里
  • 我使用了每个州 1 到 3 行的小样本。因此我只限制为 2 个值
  • 我订购了它,ids但您可以更改它并按最适合您的方式订购

输出我的小样本:

       id | us_state | n
      123 |       AL | 1 
      456 |       AL | 2 
 56781234 |       AZ | 1 
 78123456 |       CO | 1 
 45678123 |       FL | 1 
 81234567 |       FL | 2 
 34567812 |       GA | 1 
      123 |       MA | 1 
      456 |       MA | 2 
Run Code Online (Sandbox Code Playgroud)

请注意, n 是 ROW_NUMBER 的结果,在第二个查询中不存在。在大表上,分区(us-state)和 order(此处为 id)列上的索引会有所帮助。

使用的样品:

CREATE TABLE data
    ("id" int, "us_state" varchar(2))
;

INSERT INTO data
    ("id", "us_state")
VALUES
    (12345678, 'MA'),
    (123, 'MA'),
    (456, 'MA'),
    (23456781, 'AL'),
    (123, 'AL'),
    (456, 'AL'),
    (34567812, 'GA'),
    (45678123, 'FL'),
    (56781234, 'AZ'),
    (67812345, 'MA'),
    (78123456, 'CO'),
    (81234567, 'FL')
;
Run Code Online (Sandbox Code Playgroud)