PAYATHON 2026

如何从 joined table 中获取最后一条记录

支付小周

结论

按文档关联字段 cnt_man_doc_signature_doc_id 分组,取每组最大的签名 ID。使用 LEFT JOIN 可以保留没有签名记录的文档:

SELECT
    docs.cnt_man_doc_id,
    docs.cnt_man_doc_name,
    sign.cnt_man_doc_signature_name
FROM cnt_man_docs AS docs
LEFT JOIN cnt_man_doc_signatures AS sign
    ON sign.cnt_man_doc_signature_doc_id = docs.cnt_man_doc_id
   AND sign.cnt_man_doc_signature_id = (
       SELECT MAX(sign2.cnt_man_doc_signature_id)
       FROM cnt_man_doc_signatures AS sign2
       WHERE sign2.cnt_man_doc_signature_doc_id = docs.cnt_man_doc_id
   );

如果实际列名采用示例中的简化名称,可以这样写:

SELECT
    d.doc_id,
    d.doc_name,
    s.signature_name
FROM documents AS d
LEFT JOIN signatures AS s
    ON s.sig_doc_id = d.doc_id
   AND s.sig_id = (
       SELECT MAX(s2.sig_id)
       FROM signatures AS s2
       WHERE s2.sig_doc_id = d.doc_id
   );

原查询的问题

子查询中的关联条件写错了:

WHERE sign.cnt_man_doc_signature_id = docs.cnt_man_doc_id

这个条件比较的是签名主键和文档主键,也就是 signature_id = document_id。正确的判断应当是签名所属的文档:

WHERE sign2.cnt_man_doc_signature_doc_id = docs.cnt_man_doc_id

原查询还使用了 INNER JOIN。没有签名的文档匹配不到任何记录,所以 Invoice 2 会被过滤。改用 LEFT JOIN 后,这类文档仍会保留,对应的 signature_name 为 NULL。

查询结果

按照题目给出的全部数据,查询结果应为:

doc_id  doc_name       signature_name
1       Contract       Chew
2       Lead           Paul
3       Invoice 1      Silvan
4       Invoice 2      NULL
5       Payment 123    Roden

题目的预期结果没有包含 doc_id = 5,但数据中同时存在该文档及其签名。在没有其他筛选条件时,它应当出现在查询结果中。

如果业务上只需要前四个文档,应添加明确的业务条件,例如:

WHERE docs.cnt_man_doc_id <= 4

这个条件不应只是为了匹配示例结果,实际使用时需要替换成真正的业务规则。

使用派生表的写法

还可以先计算每个文档最大的签名 ID,再连接签名表:

SELECT
    d.doc_id,
    d.doc_name,
    s.signature_name
FROM documents AS d
LEFT JOIN (
    SELECT sig_doc_id, MAX(sig_id) AS last_sig_id
    FROM signatures
    GROUP BY sig_doc_id
) AS latest
    ON latest.sig_doc_id = d.doc_id
LEFT JOIN signatures AS s
    ON s.sig_id = latest.last_sig_id;

这种写法分成两步:先找出每组最大的 ID,再读取对应的签名内容,逻辑更容易理解。

MySQL 8.0 及以上版本

MySQL 8.0 或更高版本也可以使用窗口函数:

SELECT
    d.doc_id,
    d.doc_name,
    s.signature_name
FROM documents AS d
LEFT JOIN (
    SELECT
        sig_doc_id,
        signature_name,
        ROW_NUMBER() OVER (
            PARTITION BY sig_doc_id
            ORDER BY sig_id DESC
        ) AS rn
    FROM signatures
) AS s
    ON s.sig_doc_id = d.doc_id
   AND s.rn = 1;

需要把 s.rn = 1 放在 ON 条件中。如果将它放入 WHERE,没有签名的文档仍会被过滤。

注意事项

这些查询将“最后一条”定义为 sig_id 最大的记录。只有在 sig_id 始终按插入顺序递增时,这个定义才成立。

如果表中有签名时间字段,例如 created_at,按时间倒序会更可靠。时间相同时,可以再用 sig_id 确定顺序:

ROW_NUMBER() OVER (
    PARTITION BY sig_doc_id
    ORDER BY created_at DESC, sig_id DESC
)

数据量较大时,可以为关联字段和排序字段建立联合索引:

CREATE INDEX idx_signatures_doc_id_sig_id
ON signatures (sig_doc_id, sig_id);

备注:内容仅供参考。