SQL关联查询————LEFT JOIN关键字的使用

引言

关联查询一直是非常重要的SQL使用技巧。

在一次查询操作中,使用mybatis进行条件查询,在没有使用 LEFT JOIN 关键字的情况下是这样写的:

    <!-- 查找成员 --><select id="selectUsers" resultMap="selectUsers_ResultMap">SELECT *FROMsys_user u, sys_user_role ur, sys_role r, sys_dept d<where><if test="roleId != null">AND ur.user_id = u.user_idAND ur.role_id = r.role_idAND r.role_id = #{roleId}</if><if test="deptId != null">AND u.department_id = d.dept_idAND u.department_id = #{deptId}</if><if test="username != null">AND u.username = #{username}</if></where></select>

这个查询操作的需求是:根据roleId(角色id)、deptId(部门id)、username(账号)查找用户列表。

虽然可以正常执行,但是查询的结果并不完全正确。

BUG重现

上述查询SQL看似逻辑比较严谨,该关联的都画了等号,但是查询结果是错误的:

首先,进行接口调用,查询所有的 roleId = 12 的用户

真实数据

  • role表:role_id = 12

  • user_role表:role_id = 12 ——>user_id = 31

  • user表:user_id = 31 ——> department_id = 1

  •  dept表:dept_id = 1 ——> 研发总监部

数据库user_id = 31的用户department_id = 1 ,即“研发总监部”。

查询结果

上图,是通过swagger API 间接调用的接口,执行的就是引言中的SQL语句,可以看到,虽然 roleId = 12 查询正常,如果不细心可能不会发现这个问题,部门信息为什么会是 deptId = 9 的 “炼丹部” ?

错误原因分析

经过仔细思考,得出结论:

在错误的SQL中,我们的 ID关联条件 都写在了WHERE子句中,且通过动态SQL进行分支执行。

这就导致了:由于只传入了roleId = 12 的条件,而deptId未作为查询条件传入,此时 user.department_id = dept.dept_id 也不会对查询进行外键约束,换言之,这个约束条件可能会被WHERE动态拼接后的SQL所舍弃。因此,导致了查询结果中用户部门的对应关系与数据库实际的对应关系不一致的情况。

引入LEFT JOIN 

 解决方案

清晰了问题的症结所在,那么如何解决问题呢?

我们的要求是不论WHERE子句中的查询条件有或没有,都要将 id 进行关联。不论是 user.role_id = role.role_id ,还是user.department_id = dept.dept_id 都要在任何查询情况下进行关联,这样就可以得出与数据库对应关系相符的数据。

最终加入LEFT JOIN后的SQL是这样的

<!-- 查找成员 此SQL必须用left join,因为如果将关联条件写在where中,分支将会忽略未执行的关联条件,导致查询结果出错--><select id="selectUsers" resultMap="selectUsers_ResultMap">SELECT *FROM (SELECT u.*, ur.role_idFROM sys_user uLEFT JOIN sys_user_role urON  u.user_id = ur.user_id) u_roleLEFT JOIN sys_role r ON u_role.role_id = r.role_idLEFT JOIN sys_dept d ON u_role.department_id = d.dept_id<where><if test="roleId != null">AND r.role_id = #{roleId}</if><if test="deptId != null">AND u_role.department_id = #{deptId}</if><if test="username != null">AND u_role.username = #{username}</if></where></select>

声明一个问题:为什么会有子查询?

子查询主要是解决 user 表、user_role 表、role 表之间的关联关系,其中user_role 表是一个只存储 user_id 和 role_id 的中间表,这条子查询仅仅适用于 一个用户只拥有一个角色的情况(角色是用户一个分组,本来可以完全不用中间表,但是为了后期扩展为用户-角色 呈多对多的关系,故加入user_role 中间表)。

针对于上述实际的SQL语句来说,这条子查询,仅仅是将 用户所对应的唯一的 role_id 拼接到了user表的末尾,并连同user表的所有数据一同查出。这里其实也可以使用一个LEFT JOIN ,但是由于子查询中的WHERE 子句一定会执行,因此这样写也是可以的。

另外注意:

子查询一定要记得加别名,否则SQL执行会报错!!

子查询一定要记得加别名,否则SQL执行会报错!!

子查询一定要记得加别名,否则SQL执行会报错!!

 修改后测试

同样只传入 roleId = 12 查询全部用户:

可以看到,查出的用户已经与数据库的关联信息保持一致了,其他的成员也都是如此。说明,我们的SQL执行结果符合我们的期望。

复习LEFT JOIN

定义

LEFT JOIN 关键字会从左表 (table_name1) 那里返回所有的行,即使在右表 (table_name2) 中没有匹配的行。

 语法

SELECT column_name(s)
FROM table_name1
LEFT JOIN table_name2 
ON table_name1.column_name=table_name2.column_name 

 

综上,就是对于SQL关联查询的爬坑随笔,比较 隐蔽的一个错误。希望能够对大家有所帮助。欢迎文末留言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java知识点全面汇总

&#xfeff;&#xfeff; 相关概念 面向对象的三个特征 封装&#xff0c;继承&#xff0c;多态&#xff0c;这个应该是人人皆知&#xff0c;有时候也会加上抽象。 多态的好处 允许不同类对象对同一消息做出响应&#xff0c;即同一消息可以根据发送对象的不同而采用多种不…

Git初学札记(八)————版本回退

引言 不论是使用svn还是git&#xff0c;版本回退都是一个非常重要的功能。 EGit版本回退 在History视图中&#xff0c;我们可以看到我们的commit历史&#xff0c;选中任意一个commit版本&#xff0c;右键reset —>Hard &#xff0c;即可回退到指定版本。 注意&#xff0c…

Java实现用户头像上传(修改默认文件大小限制)

概述 每次说起文件上传&#xff0c;就不得不提一下前端的实现方式&#xff0c;说来也奇怪&#xff0c;本博主最热门的博客居然也是文件上传&#xff0c;3万多的访问量占了总访问量的一多半&#xff1a;《传统form表单提交方式的文件上传与文件存储》&#xff0c;而且&#xff…

垃圾回收机制和JVM垃圾回收常见算法

垃圾回收机制和JVM垃圾回收常见算法垃圾回收的好处和特点&#xff1a; 好处&#xff1a; 1. 提高编程效率&#xff1b; 2. 垃圾回收机制保护程序的完整性。特点&#xff1a; 1. 只能回收无用对象的内存空间&#xff0c;对其他物理资源无能为力&#xff1b; 2. 为了更快回收不再…

Java 多线程 —— 死锁与锁的错误用法

引言 死锁状态的大致情况是&#xff1a;Thread_1在获得A对象的锁后&#xff0c;紧接着去请求B对象的锁 &#xff0c;Thread_2在获得了B对象的锁后&#xff0c;紧接着又去请求A对象的锁&#xff0c;如下图&#xff1a; 一、模拟一个死锁 public class DeadLockDemo {static cl…

Java零基础并发编程入门

Java零基础并发编程入门并发编程主要包括&#xff1a; 线程&#xff0c;同步&#xff0c;future&#xff0c;锁&#xff0c;fork/join&#xff0c; volatile&#xff0c;信号量&#xff0c;cas&#xff08;原子性&#xff0c;可见性&#xff0c;顺序一致性&#xff09;&#xf…

Java内置锁——synchronized

一、给对象加把锁 synchronized关键字是Java唯一内置的互斥锁&#xff0c;通过关键字 synchronized 可以保证同一时刻只有一个线程获得某个同步代码块的执行权&#xff0c;但不会导致其他线程执行非同步方法时阻塞。 当获得锁的线程执行完同步代码块后&#xff0c;线程会将锁…

处理对象(toString()方法详解和==与equals方法的区别)

处理对象&#xff08;toString&#xff08;&#xff09;方法详解和与equals方法的区别&#xff09;toString&#xff08;&#xff09;是一个非常特殊的方法&#xff0c;它是一个自我描述的方法。当程序员直接打印该对象的时候&#xff0c;系统会输出该对象的“自我描述”的信息…

Java并发编程实战——volatile

引言 Java 语言提供了一种弱同步机制——volatile 变量。它的作用是确保将变量的更新操作通知到其他线程。 当把变量声明为volatile后&#xff0c;编译器和运行时都会注意到这个变量是共享的&#xff0c;因此不会将该变量上的操作与其他内存操作一起重排序。 另外&#xff0…

Java中类的加载顺序介绍(ClassLoader)

Java中类的加载顺序介绍(ClassLoader)1、ClassNotFoundExcetpion   我们在开发中&#xff0c;经常可以遇见java.lang.ClassNotFoundExcetpion这个异常&#xff0c;今天我就来总结一下这个问题。对于这个异常&#xff0c;它实质涉及到了java技术体系中的类加载。Java的类加载机…

UP装机部署步骤大纲

Linux装机 插上网线&#xff0c;然后&#xff0c;Ubuntu系统安装&#xff08;略&#xff09; 更改root密码 以装机时设置的用户登陆后&#xff0c;sudo修改root用户密码&#xff0c;然后退出&#xff0c;重新登录root。 $ sudo passwd root 下载并安装JDK $ java (根据提…

Java 多线程 —— wait 与 notify

引言 认识一下 Object 类中的两个和多线程有关的方法&#xff1a;wait 和 notify。 wait&#xff0c;当前线程进入 WAITING 状态&#xff0c;释放锁资源。 notify&#xff0c;唤醒等待中的线程&#xff0c;不释放锁资源。 一、使用 wait-notify 实现一个监控程序 实现一个…

重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

重写equal()时为什么也得重写hashCode()之深度解读以及equal方法与hashCode方法渊源转载自&#xff1a;http://blog.csdn.net/javazejian/article/details/51348320 今天这篇文章我们打算来深度解读一下equal方法以及其关联方法hashCode()&#xff0c;我们准备从以下几点入手分…

Java8————Optional

引言 Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true&#xff0c;调用get()方法会返回该对象。 Optional 是个容器&#xff1a;它可以保存类型T的值&#xff0c;或者仅仅保存null。Optional提供很多有用的方法&#xff0c;这样我们就不用显式进…

问题反馈信息处理平台开发过程

问题反馈信息处理平台开发过程 “问题反馈信息处理平台”是一个将用户反馈上来的出错信息进行处理和收集的一个平台。 这个项目主要都是在实习的时候由我一个人进行开发&#xff0c;我导师在旁边进行指导完成的。 该项目的技术架构主要是&#xff1a; 前端主要基于Vue框架的…

Java8————Base64

Base64&#xff1f; Base64是一种用64个字符来表示任意二进制数据的方式。 对于二进制文件如图片、exe、音频、视频等&#xff0c;包含很多无法显示和打印的字符&#xff0c;如果希望能够通过记事本这样的文本处理软件处理二进制数据&#xff0c;就需要一个二进制转字符串的转…

大众点评后端项目解析

restful Api是一种设计风格&#xff1a; 启动前端项目&#xff1a;在前端项目根目录 npm install&#xff1a;加载依赖包 npm run mock&#xff1a;提供模拟数据的接口&#xff0c;前端脱离于后台&#xff1b;start /b npm run mock&#xff08;于后台运行&#xff09; npm…

Java中类及方法的加载顺序

代码展示 请运行下面代码&#xff0c;查看运行结果&#xff0c;并带着问题&#xff0c;尝试第二次debug程序。 class A {private static int numA;private int numA2;static {System.out.println("A的静态字段 : " numA);System.out.println("A的静态代码块…

新手入门教程-------Spring Boot中集成RabbitMQ

AMQP&#xff1a;是Advanced Message Queuing Protocol的简称&#xff0c;高级消息队列协议&#xff0c;是一个面向消息中间件的开放式标准应用层协议。 定义了以下特性&#xff1a; 消息方向消息队列消息路由&#xff08;包括&#xff1a;点到点和发布-订阅模式&#xff09;可…

Java 多线程 —— ReentrantLock 与 Condition

引言 ReentrantLock 是 JUC 下的一个功能强劲的锁工具&#xff0c;支持公平锁、非公平锁&#xff0c;以及多等待队列的 Condition 。 也常常被称为“手动锁”。本篇博客主要分析它的使用方法以及 Condition 实现的一个生产者消费者模式。 一、可替代 synchronized 的手动锁 …