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

BUG背景 我们有一个订单表 和 一个 物流表 它们通过 订单ID 进行一对一的关系绑定。但是由于物流表在保存订单信息的时候没有做判断该订单是否已经有物流信息,

这就变成同一个订单id在物流表中存在多条数据,也就变成了本来订单表只有100条纪录,而left join 物流表后,所查询的订单数据远远大于100条。

总结 趁着上面这个问题,自己来复习下join语句distinct关键字,同时说明如何解决就算关联是一对多,但我还是想只显示100条订单数据的方法。

一、理论

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

left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 。

right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录。

inner join(等值连接) 只返回两个表中联结字段相等的行。

二、left join一对一和一对多

1、一对一关联表查询

业务逻辑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) 订单表

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兑换功成2、已发货',
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) 关联查询

这里需要展示订单列表,订单列表中当然需要展示商品信息。

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,所有右表不可能出现多条纪录。

2、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所对应的物流表信息是一对多的关系。

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

一对多并不一定是问题,主要还是看表与表之间的关系。比如:

A表是用户表,B表是订单表。自然也就想到了一个用户可能多次下单。我们假设B表中的用户id在A表中匹配到50个用户id,但是这50个用户id总订单数是500个。这就是合理的一对多关系。

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

1、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`;

2、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`;

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

3、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 最好。

四、distinct

1、作用于单列

select distinct name from A   #name去重

2、作用于多列

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

3、COUNT统计

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

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

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

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

4、distinct必须放在开头

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

补充

1、能用inner join 尽量用inner join。

2、重复数据可能是表结构一对多造成的,这种情况往往是有意义的,比如订单和订单商品明细,算总价的时候,是需要sum多个明细的。

3、如果一对多的多确实没有意义,那就可以考虑用group by 或者 distinct。

4、具体结构问题具体分析。

参考

1、left join百度百科

2、left join的用法

3、SQL中distinct的用法

```
只要自己变优秀了,其他的事情才会跟着好起来(少将15)
```

MySQL(12)---纪录一次left join一对多关系而引起的BUG的更多相关文章

  1. 纪录一次left join一对多关系而引起的BUG

    纪录一次left join一对多关系而引起的BUG MySQL(11)---纪录一次left join一对多关系而引起的bug BUG背景 我们有一个订单表 和 一个 物流表 它们通过 订单ID 进行 ...

  2. MySQL数据库 crud语句 ifnull() 创建新账户 备份数据库 一对多关系 多对多(中间表) 外键约束 自关联 子查询注意事项 DML DDL DQL mysql面试题 truncate与delete的区别

    DML(data manipulation language): 它们是SELECT.UPDATE.INSERT.DELETE,就象它的名字一样,这4条命令是用来对数据库里的数据进行操作的语言 DDL ...

  3. MySQL SELECT语法(三)JOIN语法详解

    源自MySQL 5.7 官方手册:13.2.9.2 JOIN Syntax SELECT select_expr From table_references JOIN... WHERE... 如上所示 ...

  4. linux安装mysql全纪录[包括yum和rpm安装,编码,远程连接以及大小写问题]

    linux安装mysql全纪录[包括yum和rpm安装,编码,远程连接以及大小写问题] 一.查看mysql是否已经安装 使用“whereis mysql”命令来查看mysql安装路径: [root@h ...

  5. mybatis的执行流程 #{}和${} Mysql自增主键返回 resultMap 一对多 多对一配置

    n Mybatis配置 全局配置文件SqlMapConfig.xml,配置了Mybatis的运行环境等信息. Mapper.xml文件即Sql映射文件,文件中配置了操作数据库的Sql语句.此文件需要在 ...

  6. Mysql多表表关联查询 inner Join left join right join

    Mysql多表表关联查询 inner Join left join right join

  7. linq 实现group by 不使用group关键字 等同lambad表达式中的group join 查询一对多关系

    return from orderInfo in orderEntity.x_s_orderInfo join oState in orderEntity.x_s_oStatuInfo on orde ...

  8. 子查询优化成join关联查询时要注意一对多关系

    mysql> select * from t where t.id in (select t1.tid from t1); +------+ | id | +------+ | +------+ ...

  9. MySQL高级知识(二)——Join查询

    前言:该篇主要对MySQL中join语句的七种情况进行总结. 0.准备 join主要根据两表或多表之间列的关系,从这些表中进行数据的查询. 首先创建两张表:tb_emp(员工表)和tb_dept(部门 ...

随机推荐

  1. Flatify分页

    Flatify分页:<ul uib-pagination total-items="siteCount" items-per-page="1" max-s ...

  2. openwrt 修改 banner

    http://www.network-science.de/ascii/ rectangles 风格

  3. 从头认识java-15.1 填充容器(2)-利用Collection的addAll方式

    接着上一章节,我们继续介绍填充容器. 这一章节我们结束还有一种填充容器的方式:addAll 样例: package com.ray.ch15; import java.util.ArrayList; ...

  4. 04 http协议模拟登陆发帖

    <?php require('./http.class.php'); $http = new Http('http://home.verycd.com/cp.php?ac=pm&op=s ...

  5. SAM4E单片机之旅——11、UART之PDC收发

    使用PDC进行数据的收发能减少CPU的开销.这次就使用PDC进行UART数据的接收与发送,同时,也利用TC也实现了PDC的接收超时. PDC是针对外设的DMA控制器.对比DMA控制器,它更为简便,与相 ...

  6. "Installing Software" has encountered a problem---pydev on ubuntu

    "Installing Software" has encountered a problem. An error occurred while collecting items ...

  7. Boost 库编译总结

    1. 下载boost库源码,解压缩. 2. 打开vs2010 工具栏tools 下的visual studio command prompt,运行源码目录下的bootstrap.bat,生成bjam. ...

  8. ZOJ - 3948 Marjar Cola 【循环】

    题目链接 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3948 题意 用 x 个 瓶身 可以 换 一瓶饮料 用 y 个 瓶 ...

  9. 利用ES6中的Proxy和Reflect 实现简单的双向数据绑定

    利用ES6中的Proxy (代理) 和 Reflect 实现一个简单的双向数据绑定demo. 好像vue3也把 obj.defineProperty()  换成了Proxy+Reflect. 话不多说 ...

  10. Oracle序列更新为主键最大值

    我们在使用 Oracle 数据库的时候,有时候会选择使用自增序列作为主键.但是在开发过程中往往会遇到一些不规范的操作,导致表的主键值不是使用序列插入的.这样在数据移植的时候就会出现各种各样的问题.当然 ...