postgresql:加入第n行

postgresql: Join n-th row

我有 table 个用户和 table 个订单。

table 个用户有一个主键 user_id 和一些其他字段。 table 订单有外键 user_id、名称 order_name 和日期 order_date

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

我的想法是查询客户并使用 select 对订单进行三个连接

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

但是,这不起作用,因为它仅 returns 每个订单一行,而不是专门针对每个用户。

如何编写这样的查询?

您可以使用 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 是 table 的主键,您可以从 users table 添加其他列,而无需将它们添加到 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