我有两个 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;
您的查询对于获取单个用户来说已经相当不错了。绝对比在子查询中对整个
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
理解的“中间聚合”!)参见:或者,在
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
子查询:还使用
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
?参见:或使用
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;
参见:__
旁白:不要使用
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;
在性能方面,您应该关注索引而不是查询结构 - 查询规划器将处理后者。
多列子查询在查询中跳过使用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;