使用外键进行分组时,使用MAX(id)获取整行的有效方法

时间:2017-01-27 15:14:23

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

考虑表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.

我还有其他方法吗?

3 个答案:

答案 0 :(得分:3)

对于B中的少数行(如平均2或3或5,依赖)CA每行DISTINCT ON,{{1通常是最快的。

对于A中每行许多行,有(很多)更有效的解决方案。并且您的信息:“B和C很大”表示同样多 我建议使用LATERALORDER 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.idC.id

至少需要 FK列上的索引。理想情况下,NOT NULLB (fk_a, id DESC)上的多列索引。

使用 C (fk_a, id DESC) !不排除LEFT JOINA中未引用的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的附加效果是将连接列折叠到一个实例。因此,结果中只剩下一个 USINGThe 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