MySQL(12)---纪录一次left join一对多关系而引发的BUG

MySQL(11)---纪录一次left join一对多关系而引发的bug

BUG背景 咱们有一个订单表 和 一个 物流表 它们经过 订单ID 进行一对一的关系绑定。可是因为物流表在保存订单信息的时候没有作判断该订单是否已经有物流信息,
这就变成同一个订单id在物流表中存在多条数据,也就变成了原本订单表只有100条纪录,而left join 物流表后,所查询的订单数据远远大于100条。
总结 趁着上面这个问题,本身来复习下join语句distinct关键字,同时说明如何解决就算关联是一对多,但我仍是想只显示100条订单数据的方法。html

1、理论

先再讲下关联表查询的几种表达式,网上找了一张图,经过这张图就能理解全部关联查询的含义。mysql

left join(左联接) 返回包括左表中的全部记录和右表中联结字段相等的记录 。
right join(右联接) 返回包括右表中的全部记录和左表中联结字段相等的记录。
inner join(等值链接) 只返回两个表中联结字段相等的行。sql


2、left join一对一和一对多

一、一对一关联表查询

业务逻辑1 有两张表,一张商品表、一张商品订单表回显订单列表的时候须要订单表关联商品表,以下数据库

1)商品表编码

DROP TABLE IF EXISTS `t_product`;
CREATE TABLE `t_product` (
  `product_id` char(32) NOT NULL DEFAULT '' COMMENT '主键ID',
  `pro_name` varchar(64) DEFAULT NULL COMMENT '商品名称',
  `cash` double(10,2) DEFAULT '0.00' COMMENT '商品价格',
  `pro_code` varchar(32) DEFAULT NULL COMMENT '商品编号',
  PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';

INSERT INTO `t_product` (`product_id`, `pro_name`, `cash`, `pro_code`)
VALUES
    ('1','小米',888.00,'001'),
    ('2','华为',1888.00,'002');

2) 订单表code

DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
  `order_id` char(32) NOT NULL DEFAULT '' COMMENT '主键ID',
  `product_id` char(32) DEFAULT NULL COMMENT '商品ID',
  `sale_amount` double(16,2) DEFAULT '0.00' COMMENT '订单金额',
  `order_number` varchar(40) DEFAULT NULL COMMENT '订单编码',
  `status` int(2) DEFAULT '1' COMMENT '订单状态 0订单无效1兑换功成二、已发货',
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单表';

INSERT INTO `t_order` (`order_id`, `product_id`, `sale_amount`, `order_number`, `status`)
VALUES
    ('1','1',888.00,'001001',1),
    ('2','2',1888.00,'001002',1);

3) 关联查询htm

这里须要展现订单列表,订单列表中固然须要展现商品信息。blog

select o.`order_id`,o.`sale_amount`,p.`pro_name` from t_order o left join t_product p on o.`product_id`=p.`product_id`;

运行结果排序

这两张表不多是一对多的关系,由于左表关联右表的主键ID,全部右表不可能出现多条纪录。内存

二、left join有一对多关联查询

业务逻辑2 这里是逻辑也是有两张表,一张订单表、一张物流表。订单表和上面同样,数据也一致。

物流表

DROP TABLE IF EXISTS `t_logistics`;
CREATE TABLE `t_logistics` (
  `logistics_id` char(32) NOT NULL DEFAULT '' COMMENT '主键ID',
  `order_id` char(32) DEFAULT NULL COMMENT '订单ID',
  `logistics_company_name` varchar(32) DEFAULT NULL COMMENT '物流公司名称',
  `courier_number` varchar(32) DEFAULT NULL COMMENT '快递单号',
  PRIMARY KEY (`logistics_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='物流信息表';

INSERT INTO `t_logistics` (`logistics_id`, `order_id`, `logistics_company_name`, `courier_number`)
VALUES
    ('1','1','顺丰','001'),
    ('2','1','顺丰','002');
    ('3','2','中通','003');

注意 这张表数据是有问题的,由于不可能一个订单同时有两条物流信息,可是你不能彻底排除这条表里存在两条相同订单编号,由于左表绑定的不是右表的主键ID,这可能就是保留物流信息的时候没有判断该订单已经保存物流信息,而引发的数据重复问题。

那么这个时候问题来了。

select o.`order_id`,o.`sale_amount`,l.`logistics_company_name` from t_order o left join t_logistics l on o.`order_id`=l.`order_id`;

运行结果

咱们发现,订单列表已经有三条纪录,但按照常理应该展现两条。

注意 因此从这里咱们能够得知,若是你在left join 时,须要显示的数据的左表数据不能重复时,那么就须要 on 后面的表它们的对应关系是一对一的关系。显然这里对于order_id为1所对应的物流表信息是一对多的关系。


3、如何解决一对多的问题

一对多并不必定是问题,主要仍是看表与表之间的关系。好比:
A表是用户表,B表是订单表。天然也就想到了一个用户可能屡次下单。咱们假设B表中的用户id在A表中匹配到50个用户id,可是这50个用户id总订单数是500个。这就是合理的一对多关系。

那么若是你业务逻辑确定显示一对一的关系,而表关系确实一对多的关系,就像上面的订单表和物流表同样。怎么解决,这里有两种解决方案。

一、group by

关键点 把一对多的问题转化成聚合查询

select o.`order_id`,o.`sale_amount`,l.`logistics_company_name` from t_order o left join t_logistics l on o.`order_id`=l.`order_id` group by o.`order_id`;

二、distinct

select distinct o.`order_id`,o.`sale_amount`,l.`logistics_company_name` from t_order o left join t_logistics l on o.`order_id`=l.`order_id`;

它所得的的结果和上面是同样的。

三、group by 和 distinct 比较

1)、不一样

  • distinct须要将col列中的所有内容都存储在一个内存中,能够理解为一个hash结构,key为col的值,最后计算hash结构中有多少个key便可获得结果。很明显,须要将全部不一样的值都存起来。内存消耗可能较大。
  • 而group by的方式是先将col排序。而数据库中的group通常使用sort的方法,即数据库会先对col进行排序。而排序的基本理论是,时间复杂为nlogn,空间为1。而后只要单纯的计数就能够了。优势是空间复杂度小,缺点是要进行一次排序,执行时间会较长。

2)、使用场景

数据分布 去重方式 缘由
离散 group distinct空间占用较大,在时间复杂度容许的状况下,group 能够发挥空间复杂度优点
集中 distinct distinct空间占用较小,能够发挥时间复杂度优点

3)、两个极端

  • 数据列的全部数据都同样,即去重计数的结果为1时,用distinct最佳。
  • 若是数据列惟一,没有相同数值,用group 最好。

4、distinct

一、做用于单列

select distinct name from A   #name去重

二、做用于多列

select distinct name, age from A  #根据name和age两个字段来去重的

三、COUNT统计

select count(distinct name) from A;   #表中name去重后的数目

注意: count是不能统计多个字段的,下面的SQL在SQL Server和Access中都没法运行。

若想使用多个字段,请使用嵌套查询,以下:

select count(*) from (select distinct name, age from A) AS B;

四、distinct必须放在开头

select age, distinct name from A;   #会提示错误,由于distinct必须放在开头

补充

一、能用inner join 尽可能用inner join。
二、重复数据多是表结构一对多形成的,这种状况每每是有意义的,好比订单和订单商品明细,算总价的时候,是须要sum多个明细的。
三、若是一对多的多确实没有意义,那就能够考虑用group by 或者 distinct。
四、具体结构问题具体分析。


参考

一、left join百度百科

二、left join的用法

三、SQL中distinct的用法




只要本身变优秀了,其余的事情才会跟着好起来(少将15)
相关文章
相关标签/搜索