考虑表A,B和C. B和C通过外键与A相关,并且有许多B和C具有相同的A外键。
假设以下查询:
SELECT
A.pk AS pk_a,
MAX(B.id) AS new_b,
MAX(C.id) AS new_c
FROM A
INNER JOIN B ON B.fk_a = pk_a
INNER JOIN C ON C.fk_a = pk_a
GROUP BY pk_a
我想从每个GROUP BY pk_a
的B和C中检索整个new_b和new_c行。
当然,我可以将其作为子选项和JOIN B ON b.id = new_b
包装,对于C,, B和C也是如此,我想避免这种情况。
我也可以使用SELECT DISTINCT ON(A.pk) A.pk, B.*, C.*
和ORDER BY A.pk, B.id, C.id
,但这只能保证最新的B. ,而不是最新的C. 。
我还有其他方法吗?
答案 0 :(得分:3)
对于B
中的少数行(如平均2或3或5,依赖)C
和A
每行DISTINCT ON
,{{1通常是最快的。
对于A
中每行许多行,有(很多)更有效的解决方案。并且您的信息:“B和C很大”表示同样多
我建议使用LATERAL
和ORDER BY
的{{1}}个子查询,并附带匹配的索引。
LIMIT 1
假设SELECT A.pk AS pk_a, B.*, C.*
FROM A
LEFT JOIN LATERAL (
SELECT *
FROM B
WHERE B.fk_a = A.pk -- lateral reference
ORDER BY B.id DESC
LIMIT 1
) B ON true
LEFT JOIN LATERAL (
SELECT *
FROM C
WHERE C.fk_a = A.pk -- lateral reference
ORDER BY C.id DESC
LIMIT 1
) C ON true;
和B.id
为C.id
。
您至少需要 FK列上的索引。理想情况下,NOT NULL
和B (fk_a, id DESC)
上的多列索引。
使用 C (fk_a, id DESC)
!不排除LEFT JOIN
或A
中未引用的B
行。在这里使用C
将是一个邪恶的陷阱,因为你加入了两个不相关的表。
详细说明:
相关:
上述查询的结果只有[INNER] JOIN
一次,pk_a
两次。无用镇流器 - 两次相同的列名称可能是实际问题,具体取决于您的客户。
您可以在外部fk_a
(而不是语法快捷键SELECT
)中拼出列列表以避免冗余。如果有更多重复的名称或者您不想要所有列,则可能必须以这种方式执行此操作。
但是通过智能命名约定,A.*, B.*
子句可以为您折叠冗余的PK和FK列:
USING
逻辑,SELECT *
FROM A
LEFT JOIN LATERAL (
SELECT * FROM B
WHERE B.a_id = A.a_id
ORDER BY B.id DESC
LIMIT 1
) B USING (a_id)
LEFT JOIN LATERAL (
SELECT * FROM C
WHERE C.a_id = A.a_id
ORDER BY C.id DESC
LIMIT 1
) C USING (a_id);
在这里是多余的,因为子查询中的USING (a_id)
已经过滤相同的方式。但WHERE B.a_id = A.a_id
的附加效果是将连接列折叠到一个实例。因此,结果中只剩下一个 USING
。 The manual:
此外,
a_id
的输出会抑制冗余列: 没有必要打印两个匹配的列,因为它们必须 有相同的价值观。虽然JOIN USING
生成了来自JOIN ON
的所有列 通过T1
的所有列,T2
为每个列生成一个输出列 列出的列对(按列出的顺序),后跟任何 来自JOIN USING
的剩余列,后跟T1
中的所有剩余列。
对同一数据使用相同的名称通常也很有意义。所以:T2
代表PK 和 FK列。
答案 1 :(得分:2)
这是你要的吗?
SELECT abc.*
FROM (SELECT A.pk AS pk_a, b.*, c.*,
ROW_NUMBER() OVER (PARTITION BY a.pk ORDER BY b.id DESC) as seqnum_b,
ROW_NUMBER() OVER (PARTITION BY a.pk ORDER BY c.id DESC) as seqnum_c
FROM A INNER JOIN
B
ON B.fk_a = pk_a INNER JOIN
C
ON C.fk_a = pk_a
) abc
WHERE seqnum_b = 1 or seqnum_c = 1;
实际上,我认为以上是正确的,但你可能想要:
SELECT a.pk, b.*, c.*
FROM A INNER JOIN
(SELECT DISTINCT ON (b.fk_a) b.*
FROM b
ORDER BY b.fk_a, b.id DESC
) b
ON B.fk_a = pk_a JOIN
(SELECT DISTINCT ON (c.fk_a) c.*
FROM c
ORDER BY c.fk_a, c.id DESC
) c
ON c.fk_a = pk_a;
在Postgres 9.5中,您也可以使用横向连接获得类似的效果。
答案 2 :(得分:1)
这个怎么样:
SELECT DISTINCT
A.pk AS pk_a,
MAX(B.id) OVER(PARTITION BY pk_a) AS new_b,
MAX(C.id) OVER(PARTITION BY pk_a) AS new_c
FROM A
INNER JOIN B ON B.fk_a = pk_a
INNER JOIN C ON C.fk_a = pk_a