SQL 中的左连接魔法:你真的会 left join 么?
wptr33 2024-11-21 22:05 27 浏览
(一)Left Join 的含义与特点
在数据库编程中,Left Join(左连接)具有独特的含义和特点。它以左表为基础,无论右表中是否有与之匹配的行,都会从左表返回所有的行。当右表中没有匹配的行时,相应的列会以 null 值填充。这一特点使得 Left Join 在数据查询和分析中非常有用,尤其是当我们需要确保左表中的数据不被遗漏时。例如,在一个电商数据库中,有订单表和客户表。如果我们想获取所有客户的信息以及他们的订单情况,使用 Left Join 可以确保即使某些客户没有下过订单,他们的信息也会被包含在查询结果中。
(二)基本语法与示例
Left Join 的基本语法通常为:“SELECT 列名 FROM 左表 LEFT JOIN 右表 ON 左表.列名 = 右表.列名”。例如,假设有两个表,学生表(students)包含学生的 ID 和姓名,成绩表(grades)包含学生的 ID 和成绩。要查询所有学生以及他们的成绩(如果有),可以使用以下语句:“SELECT students.ID, students.Name, grades.Grade FROM students LEFT JOIN grades ON students.ID = grades.ID”。查询结果中,对于有成绩的学生,会显示相应的成绩,而对于没有成绩的学生,成绩列会显示为 null。
再比如,在 MySQL 中,有文章表(article)和用户表(user)。文章表包含文章的 ID、标题和作者 ID,用户表包含用户的 ID、用户名和邮箱。要列出所有的文章及对应的所属用户,即使没有用户的文章也列出,可以使用如下语句:“SELECT article.aid,article.title,user.username FROM article LEFT JOIN user ON article.uid = user.uid”。返回的查询结果中,对于有对应用户的文章,会显示用户的用户名,而对于没有对应用户的文章,用户名列会显示为 null。
二、Left Join 的注意事项
(一)筛选条件的位置影响
在使用 Left Join 时,筛选条件放置的位置不同会导致结果产生差异。如果将筛选条件放在 ON 子句中,它主要是对右表进行条件过滤,但依然会返回左表的所有行,右表中没有匹配的则补为 NULL。例如,“SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id AND table2.name='特定名称'”,这里的条件只会影响右表的匹配内容,不影响返回行数,即左表的所有行都会被展示出来。
而如果将筛选条件放在 WHERE 子句中,是在临时表生成好后,再对临时表进行过滤。这时已经没有 Left Join 必须返回左边表记录的含义了,条件不为真的就全部过滤掉。例如,“SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.id WHERE table2.name='特定名称'”,如果右表中没有满足这个条件的记录,那么左表中在右表无匹配行的记录也可能会被过滤掉。
(二)多表连接时的考量
在多个 Left Join 连接时,记录条数会发生变化,并且数据库优化器的执行顺序也需要考虑。多个 Left Join 连接时,执行顺序一般是先将左表与第一个右表进行连接,生成一个虚拟表,然后这个虚拟表再与下一个右表进行连接。例如有三个表 table1、table2、table3,执行“SELECT table1.a, table1.b, table1.c FROM table1 LEFT JOIN table2 ON table1.uid = table2.uid LEFT JOIN talbe3 ON table1.uid = table3.uid”,先将 table1 和 table2 组合成一个虚拟表,然后这个虚拟表再和 table3 关联。
关于记录条数,在没有 WHERE 语句情况下,结果条数一般会大于等于主表(即第一个左表)的条数。这是因为如果 SELECT 后面的字段均来自主表,并且所有的关联条件都是为了匹配主表。例如,假设表 A 有 100 条记录,表 B 和表 C 分别有 80 条和 60 条记录,在没有 WHERE 语句的多个 Left Join 连接下,结果条数可能会大于等于 100 条,具体取决于表之间的关联情况。如果 B 表中某个关联关键字出现了 N 次,那么在 A 表中与这个关联关键字相关的记录也会重复 N 次,从而导致结果条数增加。数据库优化器是最终决定执行顺序的地方,但一般会按照 Left Join 的顺序执行,但也不保证完全是那样。
三、Left Join 的实际应用
(一)解决一对多问题
当左表与右表存在一对多关系时,可以采用不同的方法来处理。例如,假设我们有一个用户表和一个订单表,用户表中的一个用户可能在订单表中有多个订单。如果业务需求是查询用户的下单量,可以将 SQL 语句编程如下:
SELECT a.用户 id,a.用户姓名,count(订单 id)
FROM 用户表 a
LEFT JOIN 订单表 b ON a.用户 id = b.用户 id
GROUP BY a.用户 id,a.用户姓名;
通过这种方式,将一对多的问题转化为聚合查询,统计出每个用户的订单数量。
如果业务需求是查询用户的下单明细,可以将 SQL 语句编程如下:
SELECT a.订单 id,a.业务类型,a.单价,a.消费数量,b.用户姓名,b.用户联系方式
FROM 订单表 a
LEFT JOIN 用户表 b ON a.用户 id = b.用户 id;
这样就将一对多的问题转化成多对一的问题,展示每个订单的详细信息以及对应的用户信息。
(二)条件联接查询
在面对大数据表时,运用 where 子句限定条件可以提高查询效率。有两种限定方式,一种是全量查询后 + where 子句,另一种是联接前就先做限定。
全量查询后 + where 子句的方式,如查询用户为男,消费总价大于 1w 元的用户明细:
SELECT a.订单 id,a.业务类型,a.单价,a.消费数量,b.用户姓名,b.用户联系方式
FROM 订单表 a
LEFT JOIN 用户表 b ON a.用户 id = b.用户 id
WHERE b.用户性别 = '男' AND (a.单价 * a.消费数量) > 10000 AND b.用户 id is not null;
联接前就先做限定的方式,如我们需要查询用户在某时间段,访问 A 页面再访问 B 页面的留存用户量,查询的表只有用户行为表:
SELECT count(a.user_id),count(b.user_id)
FROM (SELECT distinct user_id FROM 用户行为表 WHERE date_str = '2016 - 11 - 01' AND url = ‘A 页面路径’) a
LEFT JOIN (SELECT distinct user_id FROM 用户行为表 WHERE date_str = '2016 - 11 - 01' AND url = ‘B 页面路径’) b ON a.user_id = b.user_id
WHERE b.user_id is not null;
两种方式的差异在于,全量查询后 + where 子句是先进行左连接操作,得到一个临时结果集,然后再对这个结果集进行筛选;而联接前就先做限定是在进行左连接操作之前,先对两个表分别进行筛选,然后再进行连接操作,这样可以减少参与连接操作的数据量,提高查询效率。
(三)在实际项目中的具体案例
在实际项目中,Left Join 有很多实用的场景。比如在一个企业的人力资源管理系统中,有员工表和部门表。要查询所有员工以及他们所属的部门信息,可以使用 Left Join。假设员工表包含员工 ID、员工姓名、部门 ID 等字段,部门表包含部门 ID、部门名称等字段。使用以下 SQL 语句:
SELECT e.员工 ID,e.员工姓名,d.部门名称
FROM 员工表 e
LEFT JOIN 部门表 d ON e.部门 ID = d.部门 ID;
这样就可以得到所有员工的姓名以及他们所属的部门名称。即使某些员工没有分配部门,也会在查询结果中显示,部门名称为 null。这个例子体现了 Left Join 在实际项目中的实用性,可以方便地获取多个表中的相关信息,满足不同的业务需求。
相关推荐
- 高性能并发队列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)