MySQL复杂SQL之多表联查/子查询详细介绍(最新整理)

本文主要是介绍MySQL复杂SQL之多表联查/子查询详细介绍(最新整理),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《MySQL复杂SQL之多表联查/子查询详细介绍(最新整理)》掌握多表联查(INNERJOIN,LEFTJOIN,RIGHTJOIN,FULLJOIN)和子查询(标量、列、行、表子查询、相关/非相关、...

mysql 中复杂 SQL 的核心部分:多表联查子查询。这是数据库操作中处理关联数据的强大工具。

核心目标: 从多个相互关联的表中组合和提取所需的数据。

第一部分:多表联查 (JOIN Operations)

当你的数据模型设计良好(遵循规范化原则)时,数据会分散在多个表中,通过主键-外键关系连接。JOIN 操作就是用来基于这些关系将多个表中的行组合起来。

1. 连接的类型 (JOIN Types)

a. INNER JOIN (内连接 / 等值连接)

  • 作用: 返回两个表中连接字段值相等的所有行组合。如果某行在其中一个表中没有匹配的行,则不会出现在结果中。
  • 语法:
SELECT 列名列表
FROM 表1
[INNER] JOIN 表2 ON 表1.关联字段 = 表2.关联字段
[WHERE 条件];
-- INNER 关键字通常可省略

示例: 查询所有有订单的客户信息(假设 customers 表有 customer_idorders 表有 customer_id 外键)

SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
-- 结果只包含那些在customers表中有记录且在orders表中至少有一个订单的客户。

图示: 两个集合的交集部分。

b. LEFT [OUTER] JOIN (左外连接)

  • 作用: 返回左表 (表1) 的所有行,即使在右表 (表2) 中没有匹配的行。对于左表中存在而右表中没有匹配的行,右表相关的列将显示为 NULL
  • 语法:
SELECT 列名列表
FROM 表1
LEFT [OUTER] JOIN 表2 ON 表1.关联字段 = 表2.关联字段
[WHERE 条件];
-- OUTER 关键字通常可省略

示例: 查询所有客户及其订单(包括没有下过单的客户)

SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id;
-- 结果包含所有客户。对于没有订单的客户,o.order_id 和 o.order_date 会是 NULL。

图示: 整个左集合 + 与右集合的交集部分。右集合独有的部分被舍弃。

c. RIGHT [OUTER] JOIN (右外连接)

  • 作用:LEFT JOIN 相反。返回右表 (表2) 的所有行,即使在左表 (表1) 中没有匹配的行。对于右表中存在而左表中没有匹配的行,左表相关的列将显示为 NULL
  • 语法:
SELECT 列名列表
FROM 表1
RIGHT [OUTER] JOIN 表2 ON 表1.关联字段 = 表2.关联字段
[WHERE 条件];
-- OUTER 关键字通常可省略
  • 示例: 查询所有订单及其对应的客户信息(包括那些可能关联到无效客户的订单 - 这种情况在良好设计的数据模型中较少见,但语法支持)
SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id;
-- 结果包含所有订单。如果某个订单的 customer_id 在 customers 表中找不到,则 c.customer_id 和 c.name 会是 NULL。
  • 图示: 整个右集合 + 与左集合的交集部分。左集合独有的部分被舍弃。
  • 注意: RIGHT JOIN 在实际应用中不如 LEFT JOIN 常见,因为通常可以通过调整表顺序使用 LEFT JOIN 达到相同目的。

d. FULL [OUTER] JOIN (全外连接)

  • 作用: 返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表相关的列将显示为 NULL。如果两个表中有匹配的行,则进行连接。
  • 语法 (MySQL 不支持直接的 FULL OUTER JOIN,需用 UNION 模拟):
SELECT 列名列表
FROM 表1
LEFT JOIN 表2 ON 表1.关联字段 = 表2.关联字段
UNION [ALL] -- 通常用 UNION 去重,如果确定不会有重复或需要保留重复则用 UNION ALL
SELECT 列名列表
FROM 表1
RIGHT JOIN 表2 ON 表1.关联字段 = 表2.关联字段
WHERE 表1.关联字段 IS NULL; -- 排除掉左连接中已包含的匹配行

示例: 查询所有客户和所有订单(包括没有订单的客户和没有对应客户的订单)

SELECT c.customer_id, c.name, o.order_id, o.order_datewww.chinasem.cn
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
UNION
SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
RIGHT JOIN orders o ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL; -- 只取右连接中左表为NULL的部分(即orders有而customers没有的行)
  • 图示: 左集合 + 右集合的并集。

e. CROSS JOIN (交叉连接 / 笛卡尔积)

  • 作用: 返回两个表中所有可能的行组合。结果集的行数是 表1行数 * 表2行数通常不是你想要的结果,除非明确需要所有组合。
  • 语法:
SELECT 列名列表
FROM 表1
CROSS JOIN 表2;
-- 或者使用隐式连接(不推荐):
SELECT 列名列表
FROM 表1, 表2;

示例: 生成所有产品和所有尺寸的组合

SELECT p.product_name, s.size_name
FROM products p
CROSS JOIN sizes s;

2. 多表连接 (Joining More Than Two Tables)

  • 可以连续使用多个 JOIN 子句连接多个表。
  • 语法:
SELECT ...
FROM 表1
JOIN 表2 ON 条件
JOIN 表3 ON 条件 -- 条件可以是表2和表3的关系,或者表1和表3的关系(较少见)
...
[WHERE ...];

示例: 查询订单的详细信息(客户名、订单日期、产品名、数量)

SELECT c.name, o.order_date, p.product_name, od.quantity
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_details od ON o.order_id = od.order_id
JOIN products p ON od.product_id = p.product_id;

3. 自连接 (Self Join)

  • 作用: 将表与其自身连接。常用于表示层次结构(如员工-经理关系、类别-父类别)。
  • 技巧 需要使用表别名 (Alias) 来区分同一个表的两个“实例”。
  • 示例: 查询员工及其经理的名字
SELECT e1.employee_name AS Employee, e2.employee_name AS Manager
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.employee_id;
-- 使用 LEFT JOIN 是因为顶级经理没有上级(manager_id 为 NULL)

4. 自然连接 (NATURAL JOIN) 和 USING 子句

  • NATURAL JOIN: 自动连接所有同名列。强烈不推荐使用! 因为它依赖于列名匹配,不明确且容易出错。
SELECT ... FROM table1 NATURAL JOIN table2; -- 避免使用

USING 子句: 当连接的两个表具有完全相同名称的关联字段时,可以用 USING 简化 ON

SELECT c.customer_id, c.name, o.order_id, o.order_date
FROM customers c
JOIN orders o USING (customer_id); -- 等价于 ON c.customer_id = o.customer_id

第二部分:子查询 (Subqueries)

子查询是指嵌套在另一个 SQL 查询(主查询)内部的查询。子查询的结果被外部查询使用。

1. 子查询的位置 (Where Subqueries Can Be Used)

  • SELECT 子句(标量子查询)
  • FROM 子句(派生表/内联视图)
  • WHERE 子句(最常用)
  • HAVING 子句INSERT / UPDATE / DELETE 语句的 VALUESSET 部分

2. 子查询的主要类型

a. 标量子查询 (Scalar Subquery)

  • 特点: 返回单个值(一行一列)。
  • 用途: 可以出现在任何期望单个值的地方(如 SELECT 列表、WHERE 条件中的比较运算符右侧)。
  • 示例: 查询价格高于平均价格的产品
SELECT product_name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);

示例:SELECT 列表中使用(为每行计算一个相关值)

SELECT order_id, order_date,
       (SELECT COUNT(*) FROM order_details od WHERE od.order_id = o.order_id) AS item_count
FROM orders o;

b. 列子查询 (Column Subquery)

  • 特点: 返回单列多行
  • 用途: 常与 IN, ANY/SOME, ALL 运算符一起用在 WHEREHAVING 子句中。
  • 示例 (IN): 查询至少订购过一次’Coffee’产品的客户
SELECT customer_id, name
FROM customers
WHERE customer_id IN (
    SELECT DISTINCT o.customer_id
    FROM orders o
    JOIN order_details od ON o.order_id = od.order_id
    JOIN products p ON od.product_id = p.product_id
    WHERE p.product_name = 'Coffee'
);
  • 示例 (ANY/SOME): 查询价格大于任何电子产品价格的非电子产品 (> ANY 等价于 > (SELECT MIN(price) FROM ... WHERE category='Electronics'))
SELECT product_name, price
FROM products
WHERE category <> 'Electronics'
  AND price > ANY (
      SELECT price
      FROM products
      WHERE category = 'Electronics'
  );
  • 示例 (ALL): 查询价格大于所有电子产品价格的非电子产品 (> ALL 等价于 > (SELECT MAX(price) FROM ... WHERE category='Electronics'))
SELECT product_name, price
FROM products
WHERE category <> 'Electronics'
  AND price > ALL (
      SELECT price
      FROM products
      WHERE category = 'Electronics'
  );

c. 行子查询 (Row Subquery)

  • 特点: 返回单行多列
  • 用途: 与行比较运算符一起使用(较少见)。
  • 示例: 查找与特定员工(ID=123)在同一个部门和同一个职位级别的员工
SELECT employee_id, name, department, job_level
FROM employees
WHERE (department, job_level) = (
    SELECT department, job_level
    FROM employees
    WHERE employee_id = 123
)
AND employee_id <> 123; -- 排除自己

d. 表子查询 / 派生表 (Table Subquery / Derived Table)

  • 特点: 返回一个结果集(多行多列)
  • 用途: 必须出现在 FROM 子句中,并且必须有别名。
  • 作用: 简化复杂查询,创建临时中间结果集。
  • 示例: 计算每个类别的平均价格,并找出高于其类别平均价格的产品
SELECT p.product_id, p.product_name, p.category, p.price, cat_avg.avg_price
FRkkGvlAOM products p
JOIN (
    SELECT category, AVG(price) AS avg_price
    FROM products
    GROUP BY category
) cat_avg ON p.category = cat_avg.category
WHERE p.price > cat_avg.avg_price;

3. 相关子查询 vs. 非相关子查询

非相关子查询 (Uncorrelated Subquery):

  • 子查询可以独立运行,不依赖于外部查询。
  • 执行过程:先执行子查询得到结果集,然后外部查询使用这个结果集。
  • 上面大部分示例都是非相关的。

相关子查询 (Correlated Subquery):

  • 子查询不能独立运行,它引用了外部查询中的列。
  • 执行过程:外部查询取出一行,传递给子查询;子查询基于外部行中的值执行;外部查询根据子查询返回的结果判断是否保留该行;重复此过程处理外部查询的每一行。
  • 效率提示: 相关子查询通常比非相关子查询或 JOIN 慢,因为它需要对外部查询的每一行都执行一次子查询。优化时需谨慎。
  • 示例: 查询那些订单总额超过 1000 的客户 (在 WHERE 中使用相关子查询)
SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id -- 关联条件
    GROUP BY o.customer_id
    HAVING SUM(o.total_amount) > 1000
);
-- 或者更高效的方式可能是使用 JOIN + GROUP BY + HAVING

示例:SELECT 列表中使用相关子查询 (如之前的 item_count 例子)

4. EXISTS 和 NOT EXISTS

专门用于相关子查询(但也可以用于非相关)。

  • EXISTS (subquery): 如果子查询返回至少一行,则结果为 TRUE
  • NOT EXISTS (subquery): 如果子查询返回零行,则结果为 TRUE
  • 非常高效,因为只要子查询找到一行匹配,EXISTS 就立即返回 TRUE,不需要处理所有结果。
  • 示例 (EXISTS): 查询至少下过一个订单的客户 (等价于前面的 IN 示例,但可能更高效)
SELECT customer_id, name
FROM customers c
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id -- 关联条件
);

示例 (NOT EXISTS): 查询从未下过订单的客户

SELECT customer_id, name
FROM customers c
WHERE NOT EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.customer_id = c.customer_id -- 关联条件
);

关键注意事项与最佳实践

  • 明确连接条件 (ON Clause): 总是显式地写出连接条件 (ON 或 USING)。避免隐式连接(逗号分隔表名)和 NATURAL JOIN,它们容易出错且不清晰。
  • 表别名 (Aliases): 在多表查询和子查询中,使用简短、有意义的表别名 (FROM table AS alias 或 FROM table alias)。这能极大提高可读性和避免列名歧义。
  • 性能考虑:
    • 索引是关键! 确保连接字段 (ON 子句中的列) 和 WHERE 子句中频繁过滤的列上有索引。
    • 理解 INNER JOIN 通常比 OUTER JOIN 快。
    • 相关子查询可能导致性能问题(Nested Loops)。如果可能,尝试将其重写为 JOIN。
    • 大型 IN 子查询可能效率低下,考虑用 JOIN 或 EXISTS 替代。
    • 派生表(FROM 中的子查询)可能会阻止某些优化。有时可以用 WITH (Common Table Expression - CTE) 在 MySQL 8.0+ 中更清晰地表达。
  • NULL 值处理: 在连接条件 (ON) 或 WHERE 子句中使用涉及可能为 NULL 的列进行比较时(如 col1 = col2),如果 col1 或 col2 为 NULL,该行通常不会匹配(因为 NULL = NULL 是 UNKNOWN/NULL)。如果需要匹配 NULL,需使用 IS NULL 显式处理。
  • 可读性和维护性:
    • 合理缩进和格式化复杂的 SQL。
    • 分解非常复杂的查询。使用 CTE (WITH 子句,MySQL 8.0+) 或临时视图js(如果支持)将查询步骤模块化。
    • 注释解释复杂的逻辑。
  • 测试: 逐步构建复杂查询。先从一个简单的部分开始,验证结果,然后逐步添加 JOIN 或子查询。使用 LIMIT 测试大数据集查询的性能。
  • 选择 JOIN 还是子查询? 没有绝对答案。通常:
    • 需要组合多个表的数据显示时,JOIN 更自然。
    • 用于过滤或计算聚合值的条件检查时,子查询(尤其是 EXISTS/NOT EXISTS)可能更直观或更高效。
    • 分析执行计划 (EXPLAIN) 是确定哪种方式性能更http://www.chinasem.cn好的最终手段。

总结

掌握多表联查 (INNER JOIN, LEFT J编程OIN, RIGHT JOIN, FULL JOIN) 和子查询(标量、列、行、表子查询、相关/非相关、EXISTS/NOT EXISTS)是进行复杂数据库查询的基础。理解它们的工作原理、适用场景以及性能影响至关重要。通过实践、关注索引、编写清晰的 SQL 并利用 EXPLAIN 分析,你将能够高效地从关联的数据库表中提取所需的信息。记住,清晰性和性能往往是相辅相成的。

到此这篇关于MySQL复杂SQL(多表联查/子查询)详细讲解的文章就介绍到这了,更多相关mysql多表联查/子查询内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于MySQL复杂SQL之多表联查/子查询详细介绍(最新整理)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154980

相关文章

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

MySQL中On duplicate key update的实现示例

《MySQL中Onduplicatekeyupdate的实现示例》ONDUPLICATEKEYUPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突,则会执行更新操作,而不是抛... 目录1/ ON DUPLICATE KEY UPDATE的简介2/ ON DUPLICATE KEY UP

MySQL分库分表的实践示例

《MySQL分库分表的实践示例》MySQL分库分表适用于数据量大或并发压力高的场景,核心技术包括水平/垂直分片和分库,需应对分布式事务、跨库查询等挑战,通过中间件和解决方案实现,最佳实践为合理策略、备... 目录一、分库分表的触发条件1.1 数据量阈值1.2 并发压力二、分库分表的核心技术模块2.1 水平分

Python与MySQL实现数据库实时同步的详细步骤

《Python与MySQL实现数据库实时同步的详细步骤》在日常开发中,数据同步是一项常见的需求,本篇文章将使用Python和MySQL来实现数据库实时同步,我们将围绕数据变更捕获、数据处理和数据写入这... 目录前言摘要概述:数据同步方案1. 基本思路2. mysql Binlog 简介实现步骤与代码示例1

基于C#实现PDF转图片的详细教程

《基于C#实现PDF转图片的详细教程》在数字化办公场景中,PDF文件的可视化处理需求日益增长,本文将围绕Spire.PDFfor.NET这一工具,详解如何通过C#将PDF转换为JPG、PNG等主流图片... 目录引言一、组件部署二、快速入门:PDF 转图片的核心 C# 代码三、分辨率设置 - 清晰度的决定因

Java中HashMap的用法详细介绍

《Java中HashMap的用法详细介绍》JavaHashMap是一种高效的数据结构,用于存储键值对,它是基于哈希表实现的,提供快速的插入、删除和查找操作,:本文主要介绍Java中HashMap... 目录一.HashMap1.基本概念2.底层数据结构:3.HashCode和equals方法为什么重写Has

使用shardingsphere实现mysql数据库分片方式

《使用shardingsphere实现mysql数据库分片方式》本文介绍如何使用ShardingSphere-JDBC在SpringBoot中实现MySQL水平分库,涵盖分片策略、路由算法及零侵入配置... 目录一、ShardingSphere 简介1.1 对比1.2 核心概念1.3 Sharding-Sp

Java实现复杂查询优化的7个技巧小结

《Java实现复杂查询优化的7个技巧小结》在Java项目中,复杂查询是开发者面临的“硬骨头”,本文将通过7个实战技巧,结合代码示例和性能对比,手把手教你如何让复杂查询变得优雅,大家可以根据需求进行选择... 目录一、复杂查询的痛点:为何你的代码“又臭又长”1.1冗余变量与中间状态1.2重复查询与性能陷阱1.

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

Mac电脑如何通过 IntelliJ IDEA 远程连接 MySQL

《Mac电脑如何通过IntelliJIDEA远程连接MySQL》本文详解Mac通过IntelliJIDEA远程连接MySQL的步骤,本文通过图文并茂的形式给大家介绍的非常详细,感兴趣的朋友跟... 目录MAC电脑通过 IntelliJ IDEA 远程连接 mysql 的详细教程一、前缀条件确认二、打开 ID