MYSQL联合查询

发布于:2025-03-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

什么是联合查询:

一些联合查询的方法大致通用的思路:

一、内连接:

 基本语法:

示例:

 查询目标1:

查询目标2:

二、外连接:

基本语法:

示例:

 查询目标1:

查询目标2:

注意:

三、自连接:

基本语法:

示例:

查询目标1:

查询目标2:

注意:

1. 必须使用表别名

2. 明确条件的逻辑方向

3. 选择合适的连接类型

5. 处理 NULL 值

四、子查询:

1.单行子查询:

基本语法:

例子:

2.多行子查询:

基本语法:

 例子:

3.多列子查询:

基本语法:

例子:

4.在 from 子句中使用子查询:

基本语法:

例子:

五、合并查询:

1、union 操作符

基本语法:

例子:

2、union all 操作符

基本语法:

例子:

六、插入查询结果:

 基本语法:

例子1(表结构完全一致):

例子2(表结构不完全一致):

查询语句执行顺序总结:


什么是联合查询:

        数据库设计时通常遵循规范化原则,数据会分散到多个表中(例如学生表、课程表、成绩表),需要通过联合查询关联这些表。单一表可能无法提供完整信息,联合查询可以合并多个维度的数据。

        比如我需要得到课程为数学科目低于60分的学生;由于数据都分散在不同的表中,所以需要联合查询得到完整的数据。

一些联合查询的方法大致通用的思路:

1. 确定哪几张表参与查询。(确定 join ... on ...)

2. 根据表与表之间的关系,过滤掉无效的数据。( join ... on 表1.关联字段 = 表2.关联字段)

3. 通过 where 条件进一步过滤结果。

4.精减查询列表,得到想要的结果。(给列起别名)

一、内连接:

 基本语法:

select 列名1, 列名2, ...
from 表1 (表1别名)
join 表2 (表2别名)
  on 表1.关联字段 = 表2.关联字段
join 表3 (表3别名)
  on 表2或表1.关联字段 = 表3.关联字段
-- 可继续添加更多  join...
where 其他条件
  1. 逐层关联:通过多次使用 join,逐步将新表与已连接的结果关联。

  2. 明确关联条件:每个 join 必须通过 on 指定匹配条件。

  3. ON:指定两个表的关联条件(通常是外键关联)。

  4. 表1 和 表2:可以是同一个表(自连接),也可以是不同的表。

  5. 匹配原则:仅返回两个表中关联字段值相等的行。

  6. 过滤机制:不满足条件的行会被完全排除。

  7. 使用别名会使得代码更简洁。

示例:

 查询目标1:

查询学生姓名、课程名称及对应的授课教师。

select s.name as 学生姓名,
       c.course_name as 课程名称,
       t.teacher_name as 老师姓名
 from students s
  join courses c
   on s.student_id = c.student_id   -- 先关联学生和选课表
    join teachers t
     on c.course_id = t.course_id;  -- 再关联选课表和教师表

查询结果:

查询目标2:

查询张三的课程名称及对应的授课教师。

select s.name as 学生姓名,
       c.course_name as 课程名称,
       t.teacher_name as 老师姓名
 from students s
  join courses c
   on s.student_id = c.student_id   -- 先关联学生和选课表
    join teachers t
     on c.course_id = t.course_id  -- 再关联选课表和教师表
        where s.name = '张三'; -- 筛选条件:学生姓名为张三

查询结果:

 值得注意的是:

尽量少对大表进行关联查询,且一般表关联的个数不能超过三个。

二、外连接:

外连接用于返回至少一个表的所有行,并根据关联条件匹配另一个表的行。若某行在另一表中无匹配,则对应字段用 null 填充。外连接分为三种类型:

  1. 左外连接(left join):保留左表所有行。

  2. 右外连接(right join):保留右表所有行。

  3. 全外连接:保留左右表所有行(MySQL 不支持)。

基本语法:

左外连接:

select 列名
from 左表
left join 右表 
  on 左表.关联字段 = 右表.关联字段;
  • 保留左表所有行,右表无匹配时填充 null 。

右外连接:

select 列名
from 左表
right join 右表 
  on 左表.关联字段 = 右表.关联字段;
  • 保留右表所有行,左表无匹配时填充 null 

示例:

 查询目标1:

使用左外连接查询未选课学生:

首先,上面的两个表可以看到,选课表的 student_id 没有 等于 3 的 数据,所以选课表是缺失的,那么,就可以确定 学生表就是基准表了

select s.student_id, s.name 
from student s
left join courses c
on s.student_id = c.student_id -- 左外连接学生表和选课表
where c.student_id is null -- 筛选右表未匹配的行

查询结果:

关键逻辑解析

  1. 左外连接:保留 students 表的所有行。

    • 已选课的学生(张三、李四)会匹配到 courses 表的记录。

    • 未选课的学生(王五)在 courses 表中无匹配,courses 表的字段会填充为 NULL

  2. 过滤条件where c.student_id is null

    • 筛选出 courses 表中无匹配的行(即未选课的学生)。

查询目标2:

使用右外连接查询未下单的客户:

select c.name as 未下单的客户
from orders o
right join customers c
on o.customer_id = c.customer_id 
where o.customer_id is null;

查询结果:

注意:

 值得注意的是:

在mysql执行的过程中,右外连接会被转换成左外连接。并且,我们一般也习惯使用左外连接。

三、自连接:

        自连接是自己与自己取笛卡尔积(相当于连接的两张表是同一张表),可以把行转换成列,在查询的时候可以使用 where 条件对结果进行过滤,或者说实现行与行之间的比较。 

基本语法:

select 列名1, 列名2, ...
from 表1 (表1别名)
join 表2 (表2别名)
  on 表1.关联字段 = 表2.关联字段
where 其他条件

这里的表1和表2就是同一张表

  • from 表1 别名1:为表指定第一个别名,用于代表表的一种角色
  • join 表2 别名2:再次引用同一个表,并为其指定第二个别名,代表表的另一种角色。
  • on 别名1.列名 = 别名2.列名:指定连接条件,通常是两个别名表中具有关联关系的列。

示例:

查询目标1:

 查询每个员工及其直接上级的姓名:

select e1.name as 员工姓名 , e2.name as 上级姓名
from employees e1
left join employees e2 -- 自连接,用 left join 包含无上级的员工
on e1.manager_id = e2.emp_id;
  • left join:使用左外连接,确保即使某个类别没有父类别(在这个例子中可以理解为该员工没有上级,manager_id 为 null),该类别的信息也会出现在结果中。

别名为 e1 的表可以作为员工表(基准表),别名为 e2 的表作为 上级表 ,他们两个本质就是同一张表。,所以 ,on 连接条件 则是 e1.manager_id = e2.emp_id ,要避免 e2.manager_id = e1.emp_id ,因为员工的 manager_id 对应上级的 emp_id

查询结果:

查询目标2:

 找到同一客户购买的不同商品组合:

select o1.customer as 客户id,o1.product as 商品1 ,o2.product as 商品2
from orders o1 
join orders o2
on o1.customer_id = o2.customer_id -- 同一客户
where o1.order_id < o2.order_id    -- 避免重复组合(如 (手机,耳机) 和 (耳机,手机)

查询结果:

注意:

以下是 自连接查询中 ON 连接条件 的核心注意事项:

1. 必须使用表别名

  • 原因:自连接需将同一张表视为两个逻辑表,必须用别名区分。

2. 明确条件的逻辑方向

  • 核心原则:on 后的条件顺序决定数据的关联方向。

  • 示例(比如查询目标1)的场景(员工与上级):

    • 正确条件e1.manager_id = e2.emp_id
      (员工的 manager_id 关联上级的 emp_id

    • 错误条件:e2.manager_id = e1.emp_id
      (变成“上级的上级是当前员工”,逻辑完全相反)

3. 选择合适的连接类型

  • join:仅返回匹配的行(如父子分类必须成对存在)。
    • left join:保留主表所有行,即使无匹配(比如查询目标1的无上级的员工)。

5. 处理 NULL 值

  • 场景:根节点(如顶级分类、无上级的员工)的关联字段可能为 null

  • 建议

    • 使用 left join保留无匹配的行。

    • 在应用层(比如java方面)处理 null(如显示“无上级”)。(推荐方法)

四、子查询:

子查询是把⼀个 select 语句的结果当做别⼀个 select 语句的条件,也叫嵌套查询。

1.单行子查询:

 单行子查询返回单行单列的结果,常用于 where 子句中与主查询进行比较。

基本语法:

select * from table1 where col_name1 {= | IN} (
 select col_name1 from table2 where col_name2 {= | IN} [(
 select ...)
 ] ...
)

 下面查询语句得到的 col_name1 的查询结果, 会给到 上面的 col_name1 作为 上面查询语句的 where条件。

并且子查询可以无限嵌套,但是要注意嵌套的深度。

例子:

查询工资高于公司平均工资的员工:

select name,salary
from employees         -- 表
where salary > (
    select avg(salary) -- 子查询返回单个值(平均工资)
    from employees     
);

2.多行子查询:

 多行子查询返回多行单列的结果,通常需要使用 in 操作符与主查询进行比较。

基本语法:

select 列名
from 表名
where 列名 in (select 列名 from 表名 where 条件);

 例子:

查询选修了课程编号为 101 或 102 的学生:

select name 
from students -- 表
where students_id in (
    select stuidents_id -- 学生的id编号
    from courses    
    where courese_id in (101,102)   -- 子查询返回多个 student_id
);

这里的 courese_id 是课程的编号,里面的查询得到选了课程编号为101或102的 学生id;外面的查询通过得到的学生的id得到学生名字。

3.多列子查询:

 单行子查询和多行子查询都只返回⼀列数据,多列子查询中可以返回多个列的数据,外层查询与嵌套的内层查询的列要匹配。

基本语法:

select 列名
from 表名
where(列1, 列2) in  (select 列1, 列2 from 表名 where 条件);

例子:

查找与张三所在部门和工资相同的员工:

select name,dept,salary
from employees
where (dept,salary) in (
    select dept,salary
    from employees
    where name = '张三'  -- 子查询返回两列:部门、工资   
);

4.在 from 子句中使用子查询:

 当⼀个查询产生结果时,MySQL自动创建⼀个临时表,然后把结果集放在这个临时表中,最终返回 给用户,在 from 子句中也可以使用临时表进行子查询或表连接操作。

基本语法:

select 列名
from (select 列名 from 表名 where 条件) as 别名
where 条件;
  • from 子句中的子查询:将一个完整的 SELECT 语句放在 FROM 子句中,其查询结果会被视为一张临时表,供外层查询操作。

  • 别名要求:子查询必须有一个别名,以便外层查询引用它。

  • 外层查询只能引用子查询结果中的列,下面是个示范:

  • select dept_id, avg_salary
    from (
        select dept_id, avg(salary) as avg_salary
        from employees
        group by dept_id
    ) as dept_avg  -- 临时表名为 dept_avg
    where avg_salary > 5000;  -- 正确:引用了子查询的列

    外层查询只能使用 dept_avg 临时表中的 dept_id 和 avg_salary 列。

select dept_id, name  -- 错误:name 不在子查询的结果中!
from (
    select dept_id, avg(salary) as avg_salary
    from employees
    group by dept_id
) as dept_avg;

此时会报错,因为子查询的临时表中没有 name 列。

例子:

假设需要找到平均工资高于公司平均工资的部门:

select dept_id, avg_salary
from (
    select dept_id, AVG(salary) AS avg_salary
    from employees
    group by dept_id
) as dept_avg
where avg_salary > (select avg(salary) from employees);

代码执行逻辑:

1. 执行 from 子句中的子查询(派生表 dept_avg

-- 子查询逻辑
select dept_id, avg(salary) as avg_salary
from employees
group by dept_id;
  • 从 employees 表读取数据。

  • 按 dept_id 分组,计算每个部门的平均工资(avg(salary))。

  • 生成临时表 dept_avg,包含两列:dept_id 和 avg_salary

2. 执行  where 子句中的子查询(标量子查询)

-- 子查询逻辑
select avg(salary) from employees;
  • 从 employees 表读取所有记录的 salary

  • 计算全局平均工资(不分组)。

  • 返回一个标量值(单值),例如 7000

3. 将  where 子查询结果与 dept_avg 临时表关联

主查询逻辑:

select dept_id, avg_salary
from dept_avg  -- 已生成的临时表
where avg_salary > 7000;  -- 假设子查询结果为7000
  • 从临时表 dept_avg 中筛选出 avg_salary > 7000 的部门。

  • 返回最终结果。

五、合并查询:

1、union 操作符

union 操作符用于合并两个或多个 select 语句的结果集,它会自动去除重复的行。

基本语法:

select column1, column2, ...
from table1
union
select column1, column2, ...
from table2;

例子:

假设有两个表 table1 和 table2,结构和数据如下:

-- 创建 table1 表并插入数据
create table table1 (
    id int,
    name varchar(50)
);
insert into table1 (id, name) values (1, 'Alice');
insert into table1 (id, name) values (2, 'Bob');

-- 创建 table2 表并插入数据
create table table2 (
    id int,
    name varchar(50)
);
insert into table2 (id, name) values (2, 'Bob');
insert into table2 (id, name) values (3, 'Charlie');

-- 使用 union 合并查询结果
select id, name from table1
union
select id, name from table2;

执行结果:

 结果将包含 table1 和 table2 中的所有不重复的行。

 要注意的是:查询的两个表的列,除了列的数量要对应,对应位置的列的数据类型也需要兼容。

2、union all 操作符

union all 操作符也用于合并两个或多个 select 语句的结果集,但它不会去除重复的行。

基本语法:

select column1, column2, ...
from table1
union all
select column1, column2, ...
from table2;

例子:

-- 创建 table1 表并插入数据
create table table1 (
    id int,
    name varchar(50)
);
insert into table1 (id, name) values (1, 'Alice');
insert into table1 (id, name) values (2, 'Bob');

-- 创建 table2 表并插入数据
create table table2 (
    id int,
    name varchar(50)
);
insert into table2 (id, name) values (2, 'Bob');
insert into table2 (id, name) values (3, 'Charlie');

-- 使用 union 合并查询结果
select id, name from table1
union all
select id, name from table2;

执行结果:

 结果包了含 table1 和 table2 中的所有行。

要注意的是:查询的两个表的列,除了列的数量要对应,对应位置的列的数据类型也需要兼容。

六、插入查询结果:

 基本语法:

insert into target_table (column1, column2, ...)
select column1, column2, ...
from source_table
where condition;
  • insert into target_table:指定要插入数据的目标表。
  • (column1, column2, ...):可选参数,指定要插入数据的列名。如果省略,则默认按照目标表的列顺序插入。
  • select column1, column2, ...:从源表中选择要插入的数据。
  • from source_table:指定源表。
  • where condition:可选参数,用于筛选源表中的数据。

例子1(表结构完全一致):

-- 创建 employees 表
create table employees (
    id int,
    name varchar(50),
    salary decimal(10, 2)
);

-- 插入示例数据
insert into employees (id, name, salary) values
(1, 'Alice', 6000),
(2, 'Bob', 4000),
(3, 'Charlie', 7000);

-- 创建 new_employees 表
create table new_employees (
    id int,
    name varchar(50),
    salary decimal(10, 2)
);

-- 插入查询结果
insert into new_employees (id, name, salary)
select id, name, salary
from employees
where salary > 5000;

-- 查看 new_employees 表中的数据
select * from new_employees;

插入数据后,查询的结果

 

例子2(表结构不完全一致):

-- 创建源表
create table source_table2 (
    id int,
    name varchar(50),
    age int,
    salary decimal(10, 2)
);

-- 插入示例数据
insert into source_table2 (id, name, age, salary) values
(1, 'Alice', 25, 5000.00),
(2, 'Bob', 30, 6000.00);

-- 创建目标表,只包含部分列
create table target_table2 (
    id int,
    name varchar(50)
);

-- 插入查询结果,明确指定列
insert into target_table2 (id, name)
select id, name from source_table2;

-- 查看目标表数据
select * from target_table2;

插入数据后,查询结果:

 这里 source_table2 比 target_table2 多了 age 和 salary 两列,但通过明确指定要插入的列 id 和 name,依然可以将源表中对应列的数据插入到目标表中。

查询语句执行顺序总结:

例如这一条查询语句:

select distinct id, name, avg(age) from student join class on student.class_id = class.id where class.id = 1 group by class.id having avg(age) > 10 order by student.id asc limit 100;

执行顺序依次为:

先确定数据来源(from),接着进行表连接(join on),筛选符合条件的记录(where),对记录分组(group by),筛选分组结果(having),选取指定列(select),去重(distinct),排序(order by),限制结果数量(limit)。