postgresql:加入第n行

时间:2016-02-11 10:13:09

标签: sql postgresql greatest-n-per-group

我有一个表用户和一个表命令。

表用户具有主键user_id和其他一些字段。表格订单包含外键user_id,名称order_name和日期order_date

对于旧版导出,我需要构建一个返回表单中数据的查询 user_id | order_name1 | order_name2 | order_name3订单1-3是客户最近的三个订单。

我的想法是查询客户并使用select to orders进行三次连接

SELECT 
 users.*,
 order1.order_name AS order_name1,
 order2.order_name AS order_name2,
 order3.order_name AS order_name3
 FROM users AS users
JOIN (
 SELECT
  user_id,
  order_name,
  order_date
 FROM orders ORDER BY order_date DESC OFFSET 0 LIMIT 1)
AS order1 ON order1.user_id=users.user_id
JOIN (
 SELECT
  user_id,
  order_name,
  order_date
 FROM orders ORDER BY order_date DESC OFFSET 1 LIMIT 1)
AS order2 ON order2.user_id=users.user_id
JOIN (
 SELECT
  user_id,
  order_name,
  order_date
 FROM orders ORDER BY order_date DESC OFFSET 2 LIMIT 1)
AS order3 ON order3.user_id=users.user_id

但是,这不起作用,因为它只从订单返回一行,而不是专门为每个用户返回。

如何编写这样的查询?

1 个答案:

答案 0 :(得分:2)

您可以使用window function为每个用户编号行:

SELECT users.*,
       o.order_name,
       o.rn as order_number
FROM users AS users
JOIN ( SELECT user_id,
              order_name,
              order_date, 
              row_number() over (Partition by user_id) order by order_date desc as rn
       FROM orders
) AS o ON o.user_id = users.user_id and o.rn <= 3;

但是上面的内容不会给你三列,而是每个用户有三行。要将其转换为列,您需要应用group by来应用穷人的数据透视模式:

SELECT users.user_id,
       max(case when o.rn = 1 then o.order_name end) as order_name1,
       max(case when o.rn = 2 then o.order_name end) as order_name2,
       max(case when o.rn = 3 then o.order_name end) as order_name3
FROM users AS users
JOIN ( SELECT user_id,
              order_name,
              order_date, 
              row_number() over (Partition by user_id) order by order_date desc as rn
       FROM orders
) AS o ON o.user_id = users.user_id and o.rn <= 3;
GROUP BY users.user_id;

如果users.user_id是该表的主键,则可以从users表中添加其他列,而无需将其添加到group by

自Postgres 9.4以来,表达式

max(case when o.rn = 1 then o.order_name end) as order_name1

也可以写成:

max(o.order_name) filter (where o.rn = 1) as order_name1