Mysql-单个left join 计算逻辑(一对多问题)
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)不同
- group by的方式是先将
col排序。数据库中的group一般使用sort的方法,即数据库会先对col进行排序。而排序的基本理论是,时间复杂为nlogn,空间为1。然后只要单纯的计数就可以了。优点是空间复杂度小,缺点是要进行一次排序,执行时间会较长。 - distinct需要将col列中的全部内容都存储在一个内存中,可以理解为一个
hash结构,key为col的值,最后计算hash结构中有多少个key即可得到结果。很明显,需要将所有不同的值都存起来。内存消耗可能较大。
2)使用场景
| 数据分布 | 去重方式 | 原因 |
|---|---|---|
| 离散 | group | 省空间但慢:distinct空间占用较大,在时间复杂度允许的情况下,group可以发挥空间复杂度优势 |
| 集中 | distinct | 废空间但快:group时间复杂度较大,在空间复杂度允许的情况下,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、具体结构问题具体分析。
参考:https://www.cnblogs.com/qdhxhz/p/10897315.html
Mysql-单个left join 计算逻辑(一对多问题)的更多相关文章
- Mysql-多个left join 计算逻辑
单个left join: (1)一对一:结果表的行数=左表行数 (2)一对多:结果表的行数>左表行数 多个left join: (0)多个left join由上到下,依次生成查询表,原理同单个l ...
- MySQL关联left join 条件on与where不同
以下的文章主要讲述的是MySQL关联left join 条件on与where 条件的不同之处,我们现在有两个表,即商品表(products)与sales_detail(销售记录表).我们主要是通过这两 ...
- MySQL 的各种 join
table th:first-of-type { width: 200px; } join 类型 备注 left [outer] join right [outer] join union [all ...
- MySQL之LEFT JOIN中使用ON和WHRERE对表数据
背景 left join在我们使用mysql查询的过程中可谓非常常见,比如博客里一篇文章有多少条评论.商城里一个货物有多少评论.一条评论有多少个赞等等.但是由于对join.on.where等关键字的不 ...
- 记录一下使用MySQL的left join时,遇到的坑
# 现象 left join在我们使用mysql查询的过程中可谓非常常见,比如博客里一篇文章有多少条评论.商城里一个货物有多少评论.一条评论有多少个赞等等.但是由于对join.on.where等关键字 ...
- MySQL连表操作之一对多
引入 当我们在数据库中创建表的时候,有可能某些列中值内容量很大,而且重复. 例子:创建一个学生表,按学校年纪班级分,表的内容大致如下: id name partment 1 xxx x学校x年级x班级 ...
- MySQL 通过semi join 优化子查询
半连接是MySQL 5.6.5引入的,多在子查询exists中使用,对外部row source的每个键值,查找到内部row source匹配的第一个键值后就返回,如果找到就不用再查找内部row sou ...
- linq 实现group by 不使用group关键字 等同lambad表达式中的group join 查询一对多关系
return from orderInfo in orderEntity.x_s_orderInfo join oState in orderEntity.x_s_oStatuInfo on orde ...
- MySQL的left join中on与where的区别
关于 “A LEFT JOIN B ON 条件表达式” 的一点提醒 ON 条件(“A LEFT JOIN B ON 条件表达式”中的ON)用来决定如何从 B 表中检索数据行,即使on中包含有A表中的列 ...
随机推荐
- 使用Mybatis的TypeHandler加解密数据
使用Mybatis的TypeHandler加解密数据 一.背景 二.解决方案 三.需求 四.实现思路 1.编写一个实体类,凡是此实体类的数据都表示需要加解密的 2.编写一个加解密的`TypeHandl ...
- Python课程笔记 (五)
今天主要学习图形用户界面,更多的还是要我们自己去实际操作,课仿佛上了一半就完了,分享一下课程(这里在SixthClass)的源码: https://gitee.com/wang_ming_er/pyt ...
- cf 11D A Simple Task(状压DP)
题意: N个点构成的无向图,M条边描述这个无向图. 问这个无向图中共有多少个环. (1 ≤ n ≤ 19, 0 ≤ m) 思路: 例子: 4 6 1 2 1 3 1 4 2 3 2 4 3 4 答案: ...
- SSH 信任关系建立
需求hostA通过ssh登陆到hostB,实现免密登陆,以及SCP的免密传送文件 由于hostA要登陆到hostB 首先需要在hostA上生成密钥,使用以下命令 ssh-keygen -t rsa 按 ...
- testNG 注解使用说明
1.TestNG常用注解 @BeforeSuite 标记的方法:在某个测试套件(suite)开始之前运行 @BeforeTest 在某个测试(test)开始之前运行 @BeforeClass 在某个测 ...
- 目录扫描工具 dirsearch 使用详解
介绍 dirsearch 是一个python开发的目录扫描工具.和我们平时使用的dirb.御剑之类的工具一样,就是为了扫描网站的敏感文件和目录从而找到突破口. 特点 多线程 可保持连接 支持多种后缀( ...
- 组件通过props属性传值
组件之间的传值 组件是一个单独功能模块的封装,有属于自己的data和methods,一个组件的 data 选项必须是一个函数 为什么必须是函数:因为只有当data是函数时,不同实例调用同一个组件时才会 ...
- Pytest使用pytest-html和allure生成测试报告
Pytest-html 1.安装命令pip3 install pytest-html,如下图: 执⾏后,会在当前⽬录下⽣成 ⼀个report.html的⽂件,打开后会展示详细的测试报告,执行该命令py ...
- dotnet templating 定制自己的项目模板
由于工作需要,研究了一下VS 项目模板生成的相关内容,本文做一下记录借助.NET Core Template Engine创建一个加单的项目模板. 创建项目代码和配置文件 首先创建一个Minimal ...
- 暑假算法练习Day6
最近开始了实验室的生活,并且学习了bullet journal.希望接下来的每一天都能完成所有的任务. 1012 数字分类 (20 分) 给定一系列正整数,请按要求对数字进行分类,并输出以下 5 个数 ...