通过一个例子,帮助理解group by和连接

问题(例子)

有一个订单表,有订单 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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值