mysql左外联另一个表时关联字段是一对多,但只想要最新一条与之关联的优雅的解决方案
需求
需求是这样:我们有表a为人员入场表,表b为人员登记表,但是我们有不能保证人员多次登记,所以这个时候我们只取最新的一条登记数据与入场表以人员id关联
历史解决方案(low的)
按照以往的情况,我可能会把b表进行分组然后排序然后取第一条生成个表与第一个表关联,但是写法特别复杂类似这样:
SELECTa.*,v.* FROMaLEFT JOIN ( SELECT b.* FROM b b1 JOIN (SELECT user_id, MAX(create_time) AS max_timeFROM bGROUP BY user_id ) b2 ON b1.user_id = b2.user_id AND b1.create_time = b2.max_time; ) v on a.user_id = v.user_id
而且极限情况如果create_time完全相同还会出现问题,
优雅解决方案(mysql 8.0以上)
但是我最近发现个更优雅的写法
SELECTa.*,v.* FROMaLEFT JOIN (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC) AS rn FROM b) v ON a.user_id = v.user_id AND v.rn = 1
解释:
PARTITION BY user_id:将结果集按 user_id字段进行分区。每个不同的 user_id 值会形成一个独立的分区。
ORDER BY create_time DESC:在每个分区内部,按 create_time 字段降序排列。这意味着每个分区内的记录会根据 create_time 从最新到最旧进行排序。
ROW_NUMBER():为每个分区内的记录分配一个行号,行号从1开始,按 create_time 的降序递增。
mysql5.7替代方案
那有的同学说了我用的mysql5.7不支持窗口函数怎么办呢?没关系我们可以使用子查询和变量来模拟 ROW_NUMBER()
的行为
SELECTa.*,v.* FROMaLEFT JOIN (SELECTb.*,@rn := IF(@current_user = user_id, @rn + 1, 1) AS rn,@current_user := user_idFROMb,(SELECT @rn := 0, @current_user := '') AS varsORDER BYuser_id, create_time DESC) v ON a.user_id = v.user_id AND v.rn = 1;
-
变量初始化:在子查询中,我们使用一个从
b
表和初始化变量的虚拟表(SELECT @rn := 0, @current_user := '' AS vars)
的交叉连接。这允许我们在查询中初始化变量@rn
和@current_user
。 -
变量赋值:在
SELECT
子句中,我们使用@rn
变量来存储每个user_id
分组的行号。IF(@current_user = user_id, @rn + 1, 1)
这部分逻辑用于检查当前行是否与前一行有相同的user_id
。如果是,行号递增;如果不是,行号重置为 1。同时,@current_user := user_id
用于更新当前user_id
的值。 -
排序:在
ORDER BY
子句中,我们按照user_id
和create_time DESC
对结果进行排序,以确保最新的记录具有行号 1。 -
外部查询:在外部查询中,我们执行一个左连接,将
a
表与我们的子查询结果v
连接在一起,只选择每个user_id
的最新记录(即v.rn = 1
)。 -
请注意,这种方法依赖于 MySQL 的变量和排序顺序,因此它的性能可能不如直接在支持窗口函数的 MySQL 版本中好。此外,如果表
b
非常大,这种方法可能会很慢,因为它需要对整个表进行排序。