如何从 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);
备注:内容仅供参考。
