问题(例子)
有一个订单表,有订单 ID(orderid)、订单日期(orderdate)、数量(qty)三个字段,编写 SQL查询语句,实现去掉重复订单ID,只保留订单日期最大的记录,并按订单ID排序
看到要求去重,首先想到可以用distinct,同时我们也应该可以想到group by也能实现去重,特别是后面还要求了只保留订单日期最大的记录,此时使用group by是特别方便的,按这个思路的代码如下:
SELECT orderid, qty ,MAX(orderdate) as maxOrderDate
FROM T_ORDER
GROUP BY orderid
order by orderid;
按orderid分组去重了,MAX(orderdate)保留订单日期最大的记录, order by orderid排序了,好像三个条件都满足了,而实际的结果是:
虽然mysql没有报错,但是并不代表sql语句没有错误,在select 的qty字段,并没有在group by后面跟着,则会出现一个问题:group by是分组的意思,现在是按orderid进行分组,如果有两条记录的orderid都是A01,它们的qty分别为10、55,那么mysql应该保留哪一条记录呢?
可能我们会想不能都保留吗,都属于A01组不行吗,答案是不行。因为如果是使用了group by进行分组,那么在最终结果的每一行就代表了一个组,这也是group by能进行去重的原因,如果都保留,就说明两行A01分属不同的组,可是我们是对orderid进行分组,相同的orderid就应该是同一个组,这就矛盾了。
所以如果题目没有要求查询qty字段,那么直接
SELECT orderid ,MAX(orderdate) as maxOrderDate
FROM T_ORDER
GROUP BY orderid
order by orderid;
就可以了。但是现在要求还要qty字段,这就是难点所在,我们可以用连接和子查询,思路是:把上面这个查询,作为条件,让一个完整的表去挑选,这里的挑选就是使用判断条件,也就是
SELECT t1.orderid, t1.qty,t1.orderdate
FROM T_ORDER t1
JOIN (
SELECT orderid, MAX(orderdate) AS max_orderdate
FROM T_ORDER
GROUP BY orderid
) t2 ON t1.orderid = t2.orderid AND t1.orderdate = t2.max_orderdate
ORDER BY t1.orderid;
首先,涉及到连接表,那么每一个字段都要标明这个字段是属于哪张表的(t1./t2.),这个完整的表就是t1,连接条件 t1.orderid = t2.orderid 和 t1.orderdate = t2.max_orderdate(因为t2表的orderid和orderdate都是符合条件的),这样一判断后就筛选出t1的符合要求的orderdate,orderid的记录,而记录中也有qty,最后再order by就可以了。
这是带有连接条件的内连接,
- 内连接 (
INNER JOIN
) 要求两个表在连接条件下有匹配的行才会返回结果。这种连接方式确保了主查询orders o
中每个订单的orderdate
是其对应的最大日期 (max_orderdate
),因为这是连接条件的限制。
如果这里的内连接没有加上连接条件,就是笛卡尔积,变成左表的每一行都和右边的所有行进行连接,那么就是5X5=25条记录了
这里不能直接使用左外连接 left join,如果使用了,那么左表完整表会被保留,就起不到过滤对应记录的要求,如果必须使用左外连接 left join,可以这样操作
SELECT o1.orderid, o1.qty,o1.orderdate
FROM T_ORDER o1
LEFT JOIN (SELECT orderid, MAX(orderdate) AS max_orderdate FROM T_ORDER GROUP BY orderid) o2
ON o1.orderid = o2.orderid AND o1.orderdate = o2.max_orderdate
WHERE o2.max_orderdate IS NOT NULL
ORDER BY o1.orderid;
关键点:多了WHERE o2.max_orderdate IS NOT NULL,确保只选择那些在子查询 o2
中有最大日期的订单记录。这样可以避免选择那些没有最大日期匹配的无效记录。
它利用了左连接和子查询的组合来选择每个订单的最新记录。通过左连接,即使某些订单在子查询中找不到最大日期的匹配,它们仍然会保留在结果集中,但是由于 WHERE o2.max_orderdate IS NOT NULL
条件的存在,只有那些有有效最大日期的订单记录才会被选择。
最终结果
那直接使用右连接可以吗?当然可以,记住我们的中心思想:把子查询,作为条件,让一个完整的表去挑选,这里的挑选就是使用判断条件,
使用右连接就是把右表完整保留,而右表都是符合记录的,这样一连接,就是正确答案
SELECT a.orderid, a.qty, a.orderdate
FROM T_ORDER a right join (
SELECT orderid, MAX(orderdate) as maxOrderDate
FROM T_ORDER
GROUP BY orderid
)b on a.orderid= b.orderid and a.orderdate=b.maxOrderDate
order by a.orderid;
总结
- 右连接 (
RIGHT JOIN
): 可以用于确保每个订单都与其最大日期匹配的行,因为它返回右表中的所有行,而左表中没有匹配的行则为 NULL。 - 左连接 (
LEFT JOIN
): 在这种情况下并不适合,因为它可能会导致不完整的结果集,无法确保每个订单都有其最大日期匹配的行。
如有疑问,或者批评指正,欢迎在评论区说明!
附录 建表语句(方便测试实践)
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
`orderid` char(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`orderdate` date NULL DEFAULT NULL,
`qty` int NULL DEFAULT NULL,
INDEX `idx_orderid_orderdate`(`orderid` ASC, `orderdate` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of t_order
-- ----------------------------
INSERT INTO `t_order` VALUES ('A01', '2020-01-01', 10);
INSERT INTO `t_order` VALUES ('A03', '2020-03-01', 50);
INSERT INTO `t_order` VALUES ('A02', '2020-09-01', 100);
INSERT INTO `t_order` VALUES ('A02', '2020-03-03', 55);
INSERT INTO `t_order` VALUES ('A01', '2020-09-03', 55);