SQL left join 左表合并去重技巧总结
wptr33 2024-11-21 22:04 23 浏览
作者:ZhaoYingChao88
zyc88.blog.csdn.net/article/details/83002882
建表:
CREATE TABLE `table1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(60) DEFAULT NULL,
`age` varchar(200) DEFAULT NULL,
`sponsor_id` varchar(20) DEFAULT NULL COMMENT '业务发起人',
`gmt_create_user` int(11) NOT NULL COMMENT '创建人id',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
`gmt_modified_user` int(11) DEFAULT NULL COMMENT '修改人id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT='测试表1';
CREATE TABLE `table2` (
`kid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(60) DEFAULT NULL,
`sponsor_id` varchar(20) DEFAULT NULL COMMENT '业务发起人',
`type` int(11) NOT NULL COMMENT '创建人id',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
`gmt_modified_user` int(11) DEFAULT NULL COMMENT '修改人id',
PRIMARY KEY (`kid`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT='测试表2';
插入数据:
INSERT INTO `table1`(`id`, `name`, `age`, `sponsor_id`, `gmt_create_user`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (1, 't1', '11', '10', 1, '2018-10-10 20:34:03', NULL, NULL);
INSERT INTO `table1`(`id`, `name`, `age`, `sponsor_id`, `gmt_create_user`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (2, 't2', '12', '10', 2, '2018-10-10 20:34:03', NULL, NULL);
INSERT INTO `table1`(`id`, `name`, `age`, `sponsor_id`, `gmt_create_user`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (3, 't3', '13', '10', 3, '2018-10-10 20:34:03', NULL, NULL);
INSERT INTO `table1`(`id`, `name`, `age`, `sponsor_id`, `gmt_create_user`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (4, 't4', '14', '20', 4, '2018-10-10 20:34:03', NULL, NULL);
INSERT INTO `table2`(`kid`, `name`, `sponsor_id`, `type`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (1, 't1', '10', 1, '2018-10-10 20:38:10', NULL, NULL);
INSERT INTO `table2`(`kid`, `name`, `sponsor_id`, `type`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (2, 't2', '10', 1, '2018-10-10 20:38:10', NULL, NULL);
INSERT INTO `table2`(`kid`, `name`, `sponsor_id`, `type`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (3, 't3', '10', 1, '2018-10-10 20:38:10', NULL, NULL);
INSERT INTO `table2`(`kid`, `name`, `sponsor_id`, `type`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (4, 't4', '10', 1, '2018-10-10 20:38:10', NULL, NULL);
INSERT INTO `table2`(`kid`, `name`, `sponsor_id`, `type`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (5, 't5', '10', 1, '2018-10-10 20:38:10', NULL, NULL);
INSERT INTO `table2`(`kid`, `name`, `sponsor_id`, `type`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (6, 't6', '10', 1, '2018-10-10 20:38:10', NULL, NULL);
INSERT INTO `table2`(`kid`, `name`, `sponsor_id`, `type`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (7, 't7', '10', 2, '2018-10-10 20:38:10', NULL, NULL);
INSERT INTO `table2`(`kid`, `name`, `sponsor_id`, `type`, `gmt_create`, `gmt_modified`, `gmt_modified_user`) VALUES (8, 't1', '11', 1, '2018-10-10 20:38:10', NULL, NULL);
查询异常:
SELECT
a.*,
b.type
FROM
table1 a
LEFT JOIN table2 b ON a.sponsor_id = b.sponsor_id
WHERE
b.type = 1
AND a.sponsor_id = 10;
简单说明问题出现的原因:
MySQL left join 语句格式为:A LEFT JOIN B ON 条件表达式
left join 是以A表为基础,A表即左表,B表即右表。
左表(A)的记录会全部显示,而右表(B)只会显示符合条件表达式的记录,如果在右表(B)中没有符合条件的记录,则记录不足的地方为NULL。
使用left join, A表与B表所显示的记录数为 1:1 或 1:0,A表的所有记录都会显示,B表只显示符合条件的记录。
但如果B表符合条件的记录数大于1条,就会出现1:n的情况,这样left join后的结果,记录数会多于A表的记录数。
所以解决办法 都是从一个出发点出发,使A表与B表所显示的记录数为 1:1对应关系。
解决方法:
使用非唯一标识的字段做关联
1 DISTINCT
select DISTINCT(id) from a left join b on a.id=b.aid DISTINCT查询结果是 第一个表唯一的数据 重复的结果没显示出来
SELECT
DISTINCT(a.id), a.*,
b.type
FROM
table1 a
LEFT JOIN table2 b ON a.sponsor_id = b.sponsor_id
WHERE
b.type = 1
AND a.sponsor_id = 10;
SELECT
DISTINCT a.*,
b.type
FROM
table1 a
LEFT JOIN table2 b ON a.sponsor_id = b.sponsor_id
WHERE
b.type = 1
AND a.sponsor_id = 10;
2 GROUP BY
select * from a left join(select id from b group by id) as b on a.id=b.aid拿出b表的一条数据关联 使A表与B表所显示的记录数为 1:1对应关系。
SELECT
a.*,
b.type
FROM
table1 a
LEFT JOIN ( SELECT * FROM table2 GROUP BY sponsor_id ) AS b ON a.sponsor_id = b.sponsor_id
WHERE
b.type = 1
AND a.sponsor_id = 10;
3 max取唯一
select * from a left join (select max(id) from table group by id) as b on a.id=b.aid 拿出b表的最后一条数据关联
SELECT
a.*,
b.type
FROM
table1 a
LEFT JOIN ( SELECT MAX( kid ), type, sponsor_id FROM table2 GROUP BY sponsor_id ) AS b ON a.sponsor_id = b.sponsor_id
WHERE
b.type = 1
AND a.sponsor_id = 10;
4 IN巧用
SELECT
a.*
FROM
table1 a
WHERE
a.sponsor_id IN ( SELECT sponsor_id FROM table2 WHERE type = 1 AND sponsor_id = 10 );
SELECT
a.*,
1
FROM
table1 a
WHERE
a.sponsor_id IN ( SELECT sponsor_id FROM table2 WHERE type = 1 AND sponsor_id = 10 );
相信对于熟悉SQL的人来说,LEFT JOIN非常简单,采用的时候也很多,但是有个问题还是需要注意一下。假如一个主表M有多个从表的话A B C …..的话,并且每个表都有筛选条件,那么把筛选条件放到哪里,就得注意喽。
(搜索公众号Java知音,回复“2021”,送你一份Java面试题宝典)
比如有个主表M,卡号是主键。
有个从表A,客户号、联系方式是联合主键,其中联系方式,1-座机,2-手机号码
如果想要查询所有卡号对应的手机号码两个字段,很简单,SQL语句如下:
SELECT A.卡号,B.手机号码
FROM A
LEFT JOIN B
ON A.客户号=B.客户号
WHERE B.联系方式='2'
相信很多人这样写,估计实际工作中也会看到这样的语句,并不是说这么写一定会错误,实际SQL表达的思想一定是要符合业务逻辑的。
前面已经说清楚,所有卡号对应的手机号码。所有卡号,所以首先肯定以A表作为主表,并且左关联B表,这样A表所有的卡号一定会显示出来,但是如果B表的筛选条件放到最外层,这样就相当于将A表关联B表又做了一遍筛选,结果就是
就会筛选出来这么一条数据,丢失了A表中其他的卡号。
实际工作中表结构肯定没这么简单,关联的表也会很多,当有很多条件时,最好这么写
SELECT A.卡号,B.手机号码
FROM A
LEFT JOIN (
SELECT * FROM B
B.联系方式='2'
)B
ON A.客户号=B.客户号
这么写的话,A表中的数据肯定会完全保留,又能与B表的匹配,不会丢失数据。
PS:
- 表结构
- Left Join
- Right Join
- Inner Join
- 表的关联修改和删除
- 笛卡尔积
1、表结构
左A右B
2、Left Join
示例:2.1
Select * From A left join B on A.aid = B.bid;
left join是以A表的记录为基础的,A可以看成左表,B可以看成右表,left join是以左表为准的。换句话说,左表A的记录将会全部表示出来,而右表B只会显示符合搜索条件的记录(例子中为: A.aid = B.bid),B表记录不足的地方均为NULL.
- A表所有记录都会显示,A表中没有被匹配的行(如aid=5、6的行)相应内容则为NULL。
- 返回的记录数一定大于A表的记录数,如A表中aid=7行被B表匹配了3次(因为B表有三行bid=7)。
注意:在Access中A.aid、B.bid不能缩写成aid、bid,否则会提示“不支持链接表达式”,这一点不同于Where查询。
3、Right Join
示例:3.1
Select * From A right join B on A.aid = B.bid;
仔细观察一下,就会发现,和left join的结果刚好相反,这次是以右表(B)为基础的,A表不足的地方用NULL填充。
4、Inner Join
示例:4.1
Select * From A inner join B on A.aid = B.bid;
这里只显示出了 A.aid = B.bid的记录.这说明inner join并不以谁为基础,它只显示符合条件的记录。
inner join 等同于Where查询如:
Select * From A, B Where A.aid = B.bid
5、表的关联修改和删除
5.1修改
示例:5.1.1
update A left join B on A.aid = B.bid
set A.aname = B.bname
上述SQL实际操作的表为"Select * From A left join B on A.aid = B.bid",因此Access会提示更新13条记录(Select查询出的记录就是13条)。对比“示例:2.1”返回的结果,分析update后的A表:
- aid=5、6的记录,被更新为NULL
- aid=7的记录,被更新了3次,依次是“b1997-1”、“b1997-2”、“b1997-3”,因此其结果为最后一次更新“b1997-3”
对于上述SQL同样可以将“A.aname = B.bname”改成“B.bname = A.aname”,执行后B表将会被修改,但是执行后B表会增加三行“0, a2005-1;0, a2005-2;0, a2006”,这也不难理解,因为Left Join执行后,B表会出现三行空值。
示例:5.1.2
Where条件查询在上面的SQL中同样可以使用,其作用的表也是Select查询出的关联表。如下SQL
update A left join B on A.aid = B.bid
set A.aname = B.bname
where A.aid <> 5
执行后A表的结果:
对比第一次update可以发现,aid=5的并没有被更新。
这里只讲述left join,因为right join 和 inner join的处理过程等同于left join。另外Access中update语句中不能含有From关键字,这一点不同于其他数据库。
5.2删除
在Access中是否可以通过Left Join、Right Join、Inner Join来删除某张表的记录
示例:5.2.2
Delete From A inner join B on A.aid = B.bid
where B.bname = "b1991"
上述SQL的本意是删除A表中aid=1的记录,但执行后表A和表B均未发生任何变化。若想实现此目的,下述SQL可以实现
Delete From A
Where A.aid In (Select bid From B Where B.bname="b1991")
6、笛卡尔积
如果A表有20条记录,B表有30条记录,则二者关联后的笛卡尔积工20*30=600条记实录。也就是说A表中的每条记录都会与B表的所有记录关联一次,三种关联方式实际上就是对“笛卡尔积”的处理方式不同。
相关推荐
- 高性能并发队列Disruptor使用详解
-
基本概念Disruptor是一个高性能的异步处理框架,是一个轻量的Java消息服务JMS,能够在无锁的情况下实现队列的并发操作Disruptor使用环形数组实现了类似队列的功能,并且是一个有界队列....
- Disruptor一个高性能队列_java高性能队列
-
Disruptor一个高性能队列前言说到队列比较熟悉的可能是ArrayBlockingQueue、LinkedBlockingQueue这两个有界队列,大多应用在线程池中使用能保证线程安全,但其安全性...
- 谈谈防御性编程_防御性策略
-
防御性编程对于程序员来说是一种良好的代码习惯,是为了保护自己的程序在不可未知的异常下,避免带来更大的破坏性崩溃,使得程序在错误发生时,依然能够云淡风轻的处理,但很多程序员入行很多年,写出的代码依然都是...
- 有人敲门,开水开了,电话响了,孩子哭了,你先顾谁?
-
前言哎呀,这种情况你肯定遇到过吧!正在家里忙活着,突然——咚咚咚有人敲门,咕噜咕噜开水开了,铃铃铃电话响了,哇哇哇孩子又哭了...我去,四件事一起来,人都懵了!你说先搞哪个?其实这跟我们写Java多线...
- 面试官:线程池如何按照core、max、queue的执行顺序去执行?
-
前言这是一个真实的面试题。前几天一个朋友在群里分享了他刚刚面试候选者时问的问题:"线程池如何按照core、max、queue的执行循序去执行?"。我们都知道线程池中代码执行顺序是:co...
- 深入剖析 Java 中线程池的多种实现方式
-
在当今高度并发的互联网软件开发领域,高效地管理和利用线程资源是提升程序性能的关键。Java作为一种广泛应用于后端开发的编程语言,为我们提供了丰富的线程池实现方式。今天,就让我们深入探讨Java中...
- 并发编程之《彻底搞懂Java线程》_java多线程并发解决方案详解
-
目录引言一、核心概念:线程是什么?...
- Redis怎么实现延时消息_redis实现延时任务
-
一句话总结Redis可通过有序集合(ZSET)实现延时消息:将消息作为value,到期时间戳作为score存入ZSET。消费者轮询用ZRANGEBYSCORE获取到期消息,配合Lua脚本保证原子性获取...
- CompletableFuture真的用对了吗?盘点它最容易被误用的5个场景
-
在Java并发编程中,CompletableFuture是处理异步任务的利器,但不少开发者在使用时踩过这些坑——线上服务突然雪崩、异常悄无声息消失、接口响应时间翻倍……本文结合真实案例,拆解5个最容易...
- 接口性能优化技巧,有点硬_接口性能瓶颈
-
背景我负责的系统到2021年初完成了功能上的建设,开始进入到推广阶段。随着推广的逐步深入,收到了很多好评的同时也收到了很多对性能的吐槽。刚刚收到吐槽的时候,我们的心情是这样的:...
- 禁止使用这5个Java类,每一个背后都有一段"血泪史"
-
某电商平台的支付系统突然报警:大量订单状态异常。排查日志发现,同一笔订单被重复支付了三次。事后复盘显示,罪魁祸首竟是一行看似无害的SimpleDateFormat代码。在Java开发中,这类因使用不安...
- 无锁队列Disruptor原理解析_无锁队列实现原理
-
队列比较队列...
- Java并发队列与容器_java 并发队列
-
【前言:无论是大数据从业人员还是Java从业人员,掌握Java高并发和多线程是必备技能之一。本文主要阐述Java并发包下的阻塞队列和并发容器,其实研读过大数据相关技术如Spark、Storm等源码的,...
- 线程池工具及拒绝策略的使用_线程池处理策略
-
线程池的拒绝策略若线程池中的核心线程数被用完且阻塞队列已排满,则此时线程池的资源已耗尽,线程池将没有足够的线程资源执行新的任务。为了保证操作系统的安全,线程池将通过拒绝策略处理新添加的线程任务。...
- 【面试题精讲】ArrayBlockingQueue 和 LinkedBlockingQueue 区别?
-
有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
程序员的开源月刊《HelloGitHub》第 71 期
-
详细介绍一下Redis的Watch机制,可以利用Watch机制来做什么?
-
假如有100W个用户抢一张票,除了负载均衡办法,怎么支持高并发?
-
如何将AI助手接入微信(打开ai手机助手)
-
Java面试必考问题:什么是乐观锁与悲观锁
-
SparkSQL——DataFrame的创建与使用
-
redission YYDS spring boot redission 使用
-
一文带你了解Redis与Memcached? redis与memcached的区别
-
如何利用Redis进行事务处理呢? 如何利用redis进行事务处理呢英文
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)