如何将一个表与另一个表中具有最高id的行连接?

问题描述 投票:0回答:3

我有两个 Postgres 表:具有唯一行的“用户”表和单个用户可以拥有多个地址的“地址”表。我想将特定的“用户”行与“地址”中的一行连接起来,其中 address.id 是最高的

max(id)

我编写了以下查询,该查询工作正常,但我怀疑当一个用户有多个地址时,该查询将在返回最终结果之前生成一个非常大的中间聚合。有更好的方法来编写这个查询吗?

select "user".id, 
       "user".username,     
       "user".trader_id, 
       address.id,                                             
       address.street, 
       address.trader_id 
    from "user", address 
  where "user".id = 6 
      and  address.trader_id = "user".trader_id                         
 order by address.id desc
 limit 1;
sql postgresql greatest-n-per-group
3个回答
1
投票

您的查询对于获取单个用户来说已经相当不错了。绝对比在子查询中对整个

address
表运行窗口函数快得多。
您的查询仅通过
USING
子句稍微简化:

SELECT trader_id, u.id AS user_id, u.username, a.id AS adr_id, a.street
FROM   "user"  u
JOIN   address a USING (trader_id)
WHERE  u.id = 6
ORDER  BY a.id DESC
LIMIT  1;

address(trader_id, id) 上的多列索引

将为您提供最佳性能。另外,显然还有 
"user"(id)
 上的索引。 (没有像您用
LIMIT 1
理解的“中间聚合”!)参见:

  • 优化 GROUP BY 查询以检索每个用户的最新行
  • 选择每个 GROUP BY 组中的第一行?
或者,在

LATERAL

 子查询中使用相同的技术。适用于检索一个或多个用户:

SELECT u.trader_id, u.id AS user_id, u.username, a.* FROM "user" u LEFT JOIN LATERAL ( SELECT a.id AS adr_id, a.street FROM address a WHERE a.trader_id = u.trader_id ORDER BY a.id DESC LIMIT 1 ) a ON true WHERE u.id = 6; -- or for more than just the one
关于 

LATERAL

 子查询:

  • PostgreSQL 中的 LATERAL JOIN 和子查询有什么区别?
还使用

LEFT JOIN

 来保留没有任何地址的用户。

如果您要使用窗口函数,请使用row_number()

而不是
rank()
。在 
LATERAL
 子查询中执行此操作,同时仅检索单个(或几个)用户,以仅涉及相关行。并且,除非您运行 Postgres 16 或更高版本,否则请添加框架子句 
ROWS UNBOUNDED PRECEDING
 以提高性能:

SELECT u.trader_id, u.id AS user_id, u.username, a.* FROM "user" u LEFT JOIN LATERAL ( SELECT id AS adr_id, street , row_number() OVER (ORDER BY id DESC ROWS UNBOUNDED PRECEDING) AS rn FROM address a WHERE a.trader_id = u.trader_id ) a ON a.rn = 1 WHERE u.id = 6; -- or for more than one user
为什么

ROWS UNBOUNDED PRECEDING

?参见:

  • 查找每个客户的前 3 个订单
  • 标量子查询中的窗口函数无法按预期工作
或使用

DISTINCT ON

:

SELECT DISTINCT ON (traider_id) trader_id, u.id AS user_id, u.username, a.id AS adr_id, a.street FROM "user" u JOIN address a USING (trader_id) -- or LEFT JOIN? WHERE u.id = 6 ORDER BY trader_id, a.id DESC;
参见:

  • 选择每个 GROUP BY 组中的第一行?
__

旁白:不要使用

保留字(如“用户”)作为标识符。


1
投票
您的查询应该可以正常工作并且具有高性能,特别是如果您使用适当的索引,例如两个表的

trader_id

 上的索引。 (可以进行某些改进,例如删除第二个 
trader_id
 列并对一个或两个 
id
 列使用别名。)

但是,如果您想获取多个用户的信息,查询将会失败,因为由于

LIMIT 1

 子句,只会返回一行。那么,一个更通用的解决方案是使用窗口函数,一次获取多个 
"user".id
 所需的信息:

SELECT u.id AS user_id, u.username, trader_id, a.id AS address_id, a.street FROM "user" u JOIN ( SELECT id, street, trader_id, rank() OVER (PARTITION BY trader_id ORDER BY id) AS rank FROM address) a USING (trader_id) WHERE a.rank = 1 ORDER BY u.id;
在性能方面,您应该关注索引而不是查询结构 - 查询规划器将处理后者。


1
投票
您可以使用

多列子查询在查询中跳过使用order by和limit,以便从所有/任何用户的地址表中获取最大id。

第1步:从地址表中写入所有用户的最大地址ID。

select sa.trader_id,max(ss.id) as id from address as sa group by 1
第 2 步:将上述查询放入另一个查询中以获取地址表的必要列。

select ss.* from address as ss where (ss.trader_id ,ss.id) in (select sa.trader_id,max(ss.id) as id from address as sa group by 1)
第3步:将上述查询与“user”表连接起来。您可以使用 where 条件从此获取任何用户最新的地址 ID。

select "user".id, "user".username, "user".trader_id, t1.id, t1.street, t1.trader_id from "user" join ( select ss.* from address as ss where (ss.trader_id ,ss.id) in (select sa.trader_id,max(ss.id) as id from address as sa group by 1) )t1 on "user".trader_id =t1.trader_id where "user".id = 6;
    
© www.soinside.com 2019 - 2024. All rights reserved.