当前位置: 首页 > news >正文

MySQL进阶学习笔记:从单表查询到多表关联的深度解析(万字详解)

MySQL进阶学习笔记:从单表查询到多表关联的深度解析(万字详解)

发布时间:2025-11-12
标签:MySQL、数据库、SQL进阶、多表查询、聚合函数、分组查询、SQL执行顺序
适用人群:MySQL初学者进阶、开发工程师查缺补漏、需要夯实数据库基础的学习者

前言

数据库是后端开发的“基石”,而MySQL作为开源数据库的标杆,其查询能力直接决定了业务系统的效率与稳定性。继基础知识学习后,本文聚焦MySQL进阶查询核心,从单表查询的约束细节、聚合函数原理,到分组查询的底层逻辑,再到多表查询的关联技巧,结合“原理+案例+避坑点”的形式展开,总字数超万字,力求覆盖开发中90%以上的高频场景。

本文所有案例均基于真实业务场景设计,代码可直接复制运行,建议配合自己的数据库环境实操练习,加深理解。

一、单表查询:约束与数据操作的底层逻辑

单表查询是MySQL的入门,但“简单”背后藏着很多开发中容易踩坑的细节,尤其是约束规则数据删除操作的区别,直接影响数据完整性与系统性能。

1. 主键约束(PRIMARY KEY):表的“唯一身份证”

主键是表中用于唯一标识每条记录的字段,是数据库设计的“第一原则”,其核心特性与设计规范如下:

(1)主键的三大核心特性
  • 唯一性:表中任意两条记录的主键值不能重复,确保每条数据可被唯一定位;
  • 非空性:主键字段不允许存储NULL值,避免“无标识”的数据存在;
  • 不可变更性:主键值一旦插入,不建议修改(若作为外键关联其他表,修改会导致关联断裂)。
(2)主键的两种常见类型
  • 单一主键:用一个字段作为主键,最常用的是INT类型+AUTO_INCREMENT自增策略,示例:

    CREATE TABLE `user` (id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户唯一ID',  -- 自增单一主键username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',phone VARCHAR(11) NOT NULL UNIQUE COMMENT '手机号',create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
    
    • 优势:结构简单、查询效率高、自增策略避免主键冲突;
    • 注意:AUTO_INCREMENT仅支持整数类型,且一张表只能有一个自增字段。
  • 联合主键:用多个字段组合作为主键,适用于“单一字段无法唯一标识记录”的场景(如订单明细表,需order_id+product_id联合唯一),示例:

    CREATE TABLE `order_item` (order_id INT NOT NULL COMMENT '订单ID',product_id INT NOT NULL COMMENT '商品ID',quantity INT NOT NULL COMMENT '购买数量',price DECIMAL(10,2) NOT NULL COMMENT '购买单价',-- 联合主键:订单ID+商品ID唯一标识一条订单项PRIMARY KEY (order_id, product_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
    
    • 注意:联合主键中所有字段都不能为NULL,且修改时需同时考虑所有字段。
(3)主键设计的避坑点
  • ❌ 不要用业务字段作为主键:如手机号、身份证号,业务变更(如用户换手机号)会导致主键值修改,风险极高;
  • ❌ 不要用UUID作为主键(除非分布式场景):UUID是字符串,查询时索引效率低于整数;且自增主键的“有序性”更适合InnoDB的B+树索引结构;
  • ✅ 优先选择INT UNSIGNEDBIGINT UNSIGNEDINT支持最大21亿数据,BIGINT支持更大数据量,避免数据量增长导致主键溢出。

2. DELETE vs TRUNCATE:数据删除的“天壤之别”

开发中经常需要删除表数据,但DELETETRUNCATE的底层逻辑完全不同,误用可能导致数据丢失或性能问题,以下是全方位对比

对比维度DELETE语句TRUNCATE语句
操作类型DML(数据操纵语言)DDL(数据定义语言)
事务支持支持事务回滚(执行后可ROLLBACK不支持事务回滚(执行即生效,无法撤销)
自增主键处理保留自增计数器(下次插入从最大值+1开始)重置自增计数器(下次插入从1开始)
条件筛选支持WHERE子句(可删除部分数据)不支持条件筛选(只能清空全表数据)
触发器触发会触发表上的DELETE触发器不触发任何触发器
执行速度慢(逐行删除,记录日志)快(直接清空表数据,不记录每行日志)
锁机制行级锁(仅锁定删除的行)表级锁(锁定整个表,期间其他操作阻塞)
适用场景需删除部分数据、需回滚、需触发触发器需快速清空全表、无需回滚、无触发器依赖
(3)实战场景选择
  • 场景1:删除“2024年以前的无效订单” → 用DELETE(需条件筛选):
    DELETE FROM `order` WHERE create_time < '2024-01-01';
    
  • 场景2:清空“测试表”的所有数据,准备新一轮测试 → 用TRUNCATE(快速高效):
    TRUNCATE TABLE `test_user`;
    
  • 场景3:删除数据后需要回滚 → 必须用DELETE(结合事务):
    BEGIN;  -- 开启事务
    DELETE FROM `user` WHERE username = 'test';
    -- 确认无误后提交,有误则ROLLBACK
    COMMIT;  -- 或 ROLLBACK;
    

3. 单表查询的常见应用场景

单表查询看似简单,但结合WHEREORDER BYLIMIT等子句,可覆盖大部分基础业务需求,以下是高频场景案例

(1)基础条件筛选
  • 场景:查询“年龄大于25岁、性别为女”的用户:
    SELECT id, username, age, phone 
    FROM `user` 
    WHERE age > 25 AND gender = 'female';
    
  • 注意:AND优先级高于OR,多条件组合时建议用括号明确逻辑:
    -- 查询“年龄>25且性别为女” 或 “VIP用户”
    SELECT * FROM `user` WHERE (age > 25 AND gender = 'female') OR vip_level > 0;
    
(2)模糊查询
  • 场景:查询“用户名包含‘张’”的用户,用LIKE关键字:
    -- %表示任意字符(0个或多个),_表示单个字符
    SELECT * FROM `user` WHERE username LIKE '%张%';  -- 包含“张”
    SELECT * FROM `user` WHERE username LIKE '张_';    -- 第二个字符任意,如“张三”“张四”
    
  • 避坑点:LIKE '%张'LIKE '%张%'会导致索引失效(前缀模糊匹配无法使用索引),大数据量场景建议用FULLTEXT全文索引替代。
(3)范围查询
  • 场景:查询“2024年1月1日至2024年12月31日注册”的用户:
    SELECT * FROM `user` 
    WHERE create_time BETWEEN '2024-01-01 00:00:00' AND '2024-12-31 23:59:59';
    
  • 等价于:
    SELECT * FROM `user` 
    WHERE create_time >= '2024-01-01' AND create_time <= '2024-12-31 23:59:59';
    
(4)排序与分页
  • 场景:查询“年龄最大的前10个用户”,按年龄降序、创建时间升序排序:
    SELECT * FROM `user` 
    ORDER BY age DESC, create_time ASC 
    LIMIT 0, 10;  -- 分页:从第0条开始,取10条(LIMIT 起始索引, 条数)
    
  • 注意:LIMIT的起始索引从0开始,分页公式为LIMIT (page-1)*pageSize, pageSize(如第2页,每页10条:LIMIT 10, 10)。

二、单表查询:聚合函数与分组查询的深度剖析

聚合函数和分组查询是MySQL中“数据统计分析”的核心,也是面试高频考点,其细节直接影响统计结果的正确性与效率。

1. 聚合函数:原理、细节与效率优化

聚合函数用于对“一组数据”进行计算并返回单个结果,MySQL支持的常用聚合函数有COUNT()SUM()AVG()MAX()MIN(),以下是逐个拆解

(1)COUNT():统计行数的“门道”

COUNT()是最常用的聚合函数,但不同用法的统计逻辑和效率差异极大:

  • COUNT(*):统计表中所有行数,包含NULL值的行(因为它统计的是“行的数量”,而非某个字段的值);

    • 示例:统计用户表的总记录数:
      SELECT COUNT(*) AS total_user FROM `user`;
      
    • 效率:COUNT(*)是MySQL优化得最好的用法,InnoDB引擎会直接读取“聚簇索引”的行数,无需逐行扫描字段。
  • COUNT(字段名):统计“该字段非NULL值的行数”,若字段有NULL值则忽略;

    • 示例:统计“填写了邮箱”的用户数(email字段允许NULL):
      SELECT COUNT(email) AS email_user FROM `user`;
      
    • 效率:需逐行判断字段是否为NULL,效率低于COUNT(*),尤其字段无索引时。
  • COUNT(1):统计“1”的数量(相当于给每行加一个虚拟字段“1”),包含NULL值的行

    • 示例:统计用户总数:
      SELECT COUNT(1) AS total_user FROM `user`;
      
    • 效率:与COUNT(*)几乎一致,InnoDB会优化为直接统计行数,无需扫描字段。
  • COUNT(DISTINCT 字段名):统计“字段非NULL且不重复的值的行数”;

    • 示例:统计“不同年龄段”的用户数:
      SELECT COUNT(DISTINCT age) AS distinct_age FROM `user`;
      
    • 注意:DISTINCT会去重,效率较低,大数据量场景需谨慎使用。

结论:统计总行数优先用COUNT(*)COUNT(1),统计非NULL字段数用COUNT(字段名),去重统计用COUNT(DISTINCT 字段名)

(2)SUM()与AVG():数值计算的细节
  • SUM(字段名):计算字段的总和,忽略NULL

    • 示例:计算所有订单的总金额:
      SELECT SUM(amount) AS total_amount FROM `order`;
      
    • 注意:若字段类型为非数值型(如VARCHAR),会报错;若所有值都是NULL,返回NULL(可结合IFNULL处理:IFNULL(SUM(amount), 0))。
  • AVG(字段名):计算字段的平均值,底层逻辑是SUM(字段名)/COUNT(字段名)忽略NULL

    • 示例:计算用户的平均年龄:
      SELECT AVG(age) AS avg_age FROM `user`;
      
    • 避坑点:若需“包含NULL值的平均”(如NULL视为0),需先处理NULL
      SELECT AVG(IFNULL(age, 0)) AS avg_age FROM `user`;
      
(3)MAX()与MIN():极值查询的效率
  • MAX(字段名)/MIN(字段名):查询字段的最大/最小值,忽略NULL
    • 示例:查询最高订单金额和最低订单金额:
      SELECT MAX(amount) AS max_amount, MIN(amount) AS min_amount FROM `order`;
      
    • 效率:若字段有索引,会直接通过索引获取极值,效率极高(无需全表扫描);若无索引,则需全表扫描。

2. 分组查询(GROUP BY):规则、原理与避坑

分组查询用于将数据按“指定字段”分组后,对每组数据进行聚合计算,其核心是"分组字段决定聚合范围”,以下是必须掌握的细节:

(1)分组查询的核心规则

MySQL中分组查询有一个“铁律”:SELECT子句中,未被聚合函数包裹的字段,必须出现在GROUP BY子句中

  • 正确示例:按部门分组,统计每个部门的人数和平均薪资:

    SELECT dept_id,  -- 未聚合字段,出现在GROUP BY中COUNT(*) AS emp_count,  -- 聚合字段AVG(salary) AS avg_salary  -- 聚合字段
    FROM `employee`
    GROUP BY dept_id;  -- 分组字段
    
  • 错误示例:SELECT中包含未在GROUP BY中的非聚合字段:

    -- 错误:name未被聚合,也未在GROUP BY中
    SELECT dept_id, name, COUNT(*) AS emp_count FROM `employee` GROUP BY dept_id;
    
    • 原因:同一部门有多个员工,name字段无法确定返回哪一个值,MySQL 5.7及以上版本会直接报错(低版本可能返回随机值,风险极高)。
(2)HAVING:分组后的筛选条件

WHERE用于“分组前筛选行”,HAVING用于“分组后筛选分组结果”,两者的核心区别是:HAVING支持聚合函数,WHERE不支持

  • 示例:按部门分组,筛选“平均薪资>8000”的部门:

    SELECT dept_id,AVG(salary) AS avg_salary
    FROM `employee`
    WHERE salary > 3000  -- 分组前:先排除薪资<3000的员工
    GROUP BY dept_id
    HAVING avg_salary > 8000;  -- 分组后:筛选平均薪资>8000的部门
    
  • 注意:HAVING的执行顺序在GROUP BY之后,因此可以使用GROUP BY中定义的聚合别名(如avg_salary)。

(3)分组去重:原理与应用

分组去重的本质是“按分组字段聚合后,自然去除重复值”,常见场景是“查询每个分组的唯一值列表”。

  • 示例1:按部门分组,查询每个部门的所有岗位类型(去重):

    SELECT dept_id,GROUP_CONCAT(DISTINCT position) AS positions  -- DISTINCT去重,GROUP_CONCAT拼接
    FROM `employee`
    GROUP BY dept_id;
    
    • 结果:positions字段会以逗号分隔每个部门的唯一岗位(如“开发,测试,产品”)。
  • 示例2:按用户分组,查询每个用户的订单ID列表(去重):

    SELECT user_id,GROUP_CONCAT(DISTINCT order_id) AS order_ids
    FROM `order`
    GROUP BY user_id;
    
(4)分组查询的高级用法:ROLLUP子句

ROLLUP用于在分组结果的基础上,生成“总计行”和“小计行”,适用于多级统计场景。

  • 示例:按部门和岗位分组,统计人数,并生成部门小计和总总计:
    SELECT dept_id,position,COUNT(*) AS emp_count
    FROM `employee`
    GROUP BY dept_id, position WITH ROLLUP;
    
    • 结果说明:
      • 普通行:每个部门-岗位的人数;
      • 小计行:positionNULL的行,代表该部门的总人数;
      • 总计行:dept_idposition均为NULL的行,代表所有部门的总人数。

3. SQL语句的执行顺序:理解查询的“底层流程”

SQL语句的写法顺序和执行顺序完全不同,牢记执行顺序是解决复杂查询问题的关键,以下是MySQL中SELECT语句的执行步骤(按序号顺序执行):

1. FROM → 2. WHERE → 3. GROUP BY → 4. HAVING → 5. SELECT → 6. DISTINCT → 7. ORDER BY → 8. LIMIT
(1)执行顺序解析
  • 1. FROM:确定查询的数据源(表或视图);
  • 2. WHERE:筛选FROM子句中数据的行(分组前筛选);
  • 3. GROUP BY:将筛选后的行按指定字段分组;
  • 4. HAVING:筛选分组后的结果(分组后筛选);
  • 5. SELECT:选择需要返回的字段(此时才会计算字段值和别名);
  • 6. DISTINCT:对返回的字段去重;
  • 7. ORDER BY:按指定字段对结果排序;
  • 8. LIMIT:限制返回的行数(分页)。
(2)执行顺序的实战验证

理解执行顺序可以解决很多“看似奇怪”的问题:

  • 问题1:为什么WHERE子句中不能使用SELECT定义的别名?
    答:因为WHERE执行在SELECT之前,此时别名还未定义。

    • 错误示例:
      -- 错误:WHERE中使用SELECT的别名avg_age
      SELECT AVG(age) AS avg_age FROM `user` WHERE avg_age > 25;
      
    • 正确示例:用子查询或HAVING(若有分组):
      -- 方法1:子查询
      SELECT * FROM (SELECT AVG(age) AS avg_age FROM `user`) AS temp WHERE avg_age > 25;-- 方法2:若有分组,用HAVING
      SELECT dept_id, AVG(age) AS avg_age FROM `user` GROUP BY dept_id HAVING avg_age > 25;
      
  • 问题2:为什么ORDER BY可以使用SELECT定义的别名?
    答:因为ORDER BY执行在SELECT之后,此时别名已经定义。

    • 示例:
      SELECT username, age AS user_age FROM `user` ORDER BY user_age DESC;
      

三、多表查询:关联方式与实战场景

多表查询是MySQL进阶的核心,也是开发中最常用的查询方式,其本质是“通过关联条件将多个表的数据整合为一个结果集”。本节从“无用但基础的交叉查询”到“常用的四种连接”,再到“灵活的子查询”,完整覆盖多表查询的所有核心知识点。

1. 交叉查询(笛卡尔积):原理与风险

交叉查询是多表查询的“基础形式”,但开发中几乎不用,因为它会产生大量冗余数据,其核心是“无关联条件的多表组合*。

(1)原理

交叉查询会将多个表的所有记录进行“两两组合”,结果集的行数=表1行数×表2行数×…×表n行数,这种组合称为“笛卡尔积”。

(2)语法
-- 方式1:逗号分隔表
SELECT 字段列表 FROM 表1, 表2 [, 表3 ...];-- 方式2:CROSS JOIN关键字
SELECT 字段列表 FROM 表1 CROSS JOIN 表2 [CROSS JOIN 表3 ...];
(3)示例与风险

现有user表(2条记录)和order表(3条记录),执行交叉查询:

SELECT u.username, o.order_no FROM `user` u, `order` o;
  • 结果行数:2×3=6行,包含“每个用户与每个订单的组合”,其中大部分是无意义的关联(如用户A的订单包含用户B的订单)。
(4)何时使用?

仅在需要“生成所有可能组合”的场景(如生成测试数据、笛卡尔积运算)临时使用,必须配合WHERE子句筛选有效数据,否则会导致性能灾难(如两张10万行的表,交叉查询结果为100亿行)。

2. 四种常用多表连接:内连接、左外连接、右外连接、全连接

开发中99%的多表查询都是用这四种连接,核心区别是“是否保留不匹配的记录”,以下是逐一详解:

(1)内连接(INNER JOIN):取两表的“交集”
  • 核心逻辑:仅返回“两表中满足关联条件”的记录,不保留任何一方的不匹配记录。

  • 语法

    SELECT 字段列表
    FROM 表1
    INNER JOIN 表2 ON 表1.关联字段 = 表2.关联字段
    [INNER JOIN 表3 ON 表2.关联字段 = 表3.关联字段 ...];
    
    • 简化写法:可省略INNER,直接写JOIN
  • 实战示例:查询“有订单的用户及其订单信息”(仅返回有订单的用户):

    SELECT u.id AS user_id,u.username,o.id AS order_id,o.order_no,o.amount
    FROM `user` u
    JOIN `order` o ON u.id = o.user_id;  -- 关联条件:用户ID=订单的用户ID
    
  • 注意:内连接的关联条件必须写在ON子句中,若误写在WHERE子句中,逻辑一致但可读性差。

(2)左外连接(LEFT OUTER JOIN):保留左表的“全部记录”
  • 核心逻辑:返回“左表的所有记录”+“右表中满足关联条件的记录”,右表无匹配的记录,对应字段填NULL

  • 语法

    SELECT 字段列表
    FROM 左表
    LEFT OUTER JOIN 右表 ON 关联条件
    [LEFT OUTER JOIN 表3 ON 关联条件 ...];
    
    • 简化写法:可省略OUTER,直接写LEFT JOIN
  • 实战示例:查询“所有用户及其订单信息”(包含无订单的用户):

    SELECT u.username,o.order_no  -- 无订单的用户,order_no为NULL
    FROM `user` u
    LEFT JOIN `order` o ON u.id = o.user_id;
    
  • 避坑点:左连接后筛选右表字段,需写在WHERE子句中,而非ON子句:

    -- 错误:ON子句中筛选右表,会变成内连接效果
    SELECT u.username, o.order_no 
    FROM `user` u
    LEFT JOIN `order` o ON u.id = o.user_id AND o.amount > 1000;-- 正确:WHERE子句筛选右表,保留左表所有用户,仅显示金额>1000的订单
    SELECT u.username, o.order_no 
    FROM `user` u
    LEFT JOIN `order` o ON u.id = o.user_id
    WHERE o.amount > 1000 OR o.amount IS NULL;
    
(3)右外连接(RIGHT OUTER JOIN):保留右表的“全部记录”
  • 核心逻辑:与左外连接相反,返回“右表的所有记录”+“左表中满足关联条件的记录”,左表无匹配的记录,对应字段填NULL

  • 语法

    SELECT 字段列表
    FROM 左表
    RIGHT OUTER JOIN 右表 ON 关联条件;
    
    • 简化写法:可省略OUTER,直接写RIGHT JOIN
  • 实战示例:查询“所有订单及其用户信息”(包含无对应用户的异常订单):

    SELECT u.username,  -- 异常订单的username为NULLo.order_no,o.amount
    FROM `user` u
    RIGHT JOIN `order` o ON u.id = o.user_id;
    
  • 技巧:右连接可以转化为左连接(交换表的位置),可读性更高:

    -- 等价于上面的右连接
    SELECT u.username, o.order_no FROM `order` o LEFT JOIN `user` u ON o.user_id = u.id;
    
(4)全连接(FULL OUTER JOIN):保留两表的“全部记录”
  • 核心逻辑:返回“左表所有记录+右表所有记录”,两表无匹配的记录对应字段填NULL

  • MySQL的特殊性:MySQL不直接支持FULL OUTER JOIN,需通过“左连接+右连接+UNION”模拟实现。

  • 语法

    -- 左连接结果 + 右连接结果(去重)
    SELECT 字段列表 FROM 左表 LEFT JOIN 右表 ON 关联条件
    UNION
    SELECT 字段列表 FROM 左表 RIGHT JOIN 右表 ON 关联条件;
    
    • 注意:UNION会自动去重,若需保留重复记录用UNION ALL(效率更高)。
  • 实战示例:查询“所有用户和所有订单的关联数据”(包含无订单的用户和无用户的订单):

    SELECT u.username, o.order_no 
    FROM `user` u LEFT JOIN `order` o ON u.id = o.user_id
    UNION
    SELECT u.username, o.order_no 
    FROM `user` u RIGHT JOIN `order` o ON u.id = o.user_id;
    

3. 子查询(嵌套查询):灵活的“查询套查询”

子查询是将一个SELECT语句嵌套在另一个SQL语句中,作为“条件、数据源或字段”,其核心优势是“将复杂查询拆解为简单的子问题”。根据子查询的位置,可分为WHERE子查询、FROM子查询、SELECT子查询三类。

(1)WHERE子句中的子查询:条件筛选

子查询返回单个值或多个值,作为主查询WHERE子句的筛选条件,是最常用的子查询类型。

  • ① 单值子查询:子查询返回1行1列,主查询用=><等单值运算符。

    • 示例:查询“与‘张三’同部门的员工”(子查询先查张三的部门ID):
      SELECT username, dept_id 
      FROM `employee`
      WHERE dept_id = (SELECT dept_id FROM `employee` WHERE username = '张三');
      
    • 注意:若子查询返回多行,会报错,需用多值运算符。
  • ② 多值子查询:子查询返回多行1列,主查询用INNOT INANYALL等多值运算符。

    • 示例1:查询“研发部或销售部的员工”(子查询先查两个部门的ID):
      SELECT username, dept_id 
      FROM `employee`
      WHERE dept_id IN (SELECT id FROM `dept` WHERE name IN ('研发部', '销售部'));
      
    • 示例2:查询“薪资高于研发部所有员工”的员工:
      SELECT username, salary 
      FROM `employee`
      WHERE salary > ALL (SELECT salary FROM `employee` WHERE dept_id = 1);  -- 1是研发部ID
      
  • ③ 存在性子查询:用EXISTSNOT EXISTS判断子查询是否返回结果(返回TRUEFALSE),效率常高于IN

    • 示例:查询“有订单的用户”(等价于内连接,但EXISTS效率更高):
      SELECT username 
      FROM `user` u
      WHERE EXISTS (SELECT 1 FROM `order` o WHERE o.user_id = u.id);
      
    • 优势:EXISTS只要子查询找到一条匹配记录就停止搜索,大数据量场景效率优于IN
(2)FROM子句中的子查询:派生表

子查询返回多行多列的结果集,作为主查询的“临时表”(称为派生表),必须给派生表起别名,否则语法报错。

  • 语法

    SELECT 派生表字段
    FROM (子查询) AS 派生表别名
    [JOIN 其他表 ON 关联条件]
    [WHERE 筛选条件];
    
  • 实战示例1:查询“2025年订单金额前10的用户”(子查询先筛选2025年的订单):

    SELECT u.username,o.order_no,o.amount
    FROM `user` u
    JOIN (-- 子查询:筛选2025年的订单,按金额降序SELECT user_id, order_no, amount FROM `order` WHERE create_time >= '2025-01-01'ORDER BY amount DESCLIMIT 10
    ) AS o ON u.id = o.user_id;
    
  • 实战示例2:统计“每个用户的订单总数和总金额”(子查询先聚合订单数据):

    SELECT u.username,o.order_count,o.total_amount
    FROM `user` u
    LEFT JOIN (-- 子查询:按用户ID聚合订单SELECT user_id,COUNT(*) AS order_count,SUM(amount) AS total_amountFROM `order`GROUP BY user_id
    ) AS o ON u.id = o.user_id;
    
(3)SELECT子句中的子查询:标量子查询

子查询返回单个值(1行1列),作为主查询SELECT子句中的一个字段(称为标量子查询),常用于“动态获取关联字段值”。

  • 语法

    SELECT 主表字段,(子查询) AS 子查询字段别名
    FROM 主表;
    
  • 实战示例:查询“每个员工的姓名、部门名称”(子查询查对应部门名):

    SELECT username,(SELECT name FROM `dept` d WHERE d.id = e.dept_id) AS dept_name
    FROM `employee` e;
    
  • 避坑点:标量子查询必须返回1行1列,若返回多行或多列会报错;若无匹配结果,返回NULL

4. 扩展语法:CASE WHEN(条件分支的“语法糖”)

CASE WHEN是MySQL中最灵活的条件判断函数,支持“简单等值判断”和“复杂搜索判断”,在多表查询中常用于“字段值转换”“数据分类”“动态筛选”等场景。

(1)两种核心语法
  • ① 简单CASE格式:适用于“字段与固定值的等值判断”。

    CASE 字段/表达式WHEN 值1 THEN 结果1WHEN 值2 THEN 结果2...ELSE 默认结果  -- 可选,无匹配时返回NULL
    END AS 别名;
    
  • ② 搜索CASE格式:适用于“非等值判断”或“复杂条件组合”。

    CASEWHEN 条件1 THEN 结果1WHEN 条件2 THEN 结果2...ELSE 默认结果  -- 可选
    END AS 别名;
    
(2)多表查询中的实战场景
  • 场景1:字段值转换(多表关联展示)
    关联商品表、分类表和库存表,将“数字状态”转换为“文字描述”:

    SELECT p.name AS 商品名称,c.name AS 分类名称,-- 库存状态转换CASEWHEN s.stock >= 100 THEN '库存充足'WHEN s.stock > 0 THEN '库存紧张'ELSE '缺货'END AS 库存状态,-- 商品状态转换(简单CASE格式)CASE p.statusWHEN 1 THEN '上架'WHEN 0 THEN '下架'ELSE '待审核'END AS 商品状态
    FROM product p
    JOIN category c ON p.category_id = c.id
    LEFT JOIN stock s ON p.id = s.product_id;
    
  • 场景2:数据分类统计(结合GROUP BY)
    关联用户表和订单表,按城市和消费等级统计用户数:

    SELECTu.city,-- 按订单总金额划分消费等级CASEWHEN SUM(o.amount) >= 10000 THEN '高消费用户'WHEN SUM(o.amount) >= 5000 THEN '中消费用户'WHEN SUM(o.amount) > 0 THEN '低消费用户'ELSE '无消费用户'END AS consumer_level,COUNT(u.id) AS user_count
    FROM `user` u
    LEFT JOIN `order` o ON u.id = o.user_id
    GROUP BY u.city, consumer_level;
    
  • 场景3:动态条件筛选(HAVING子句)
    关联员工表和部门表,按部门动态筛选薪资条件:

    SELECTe.name AS 员工姓名,d.name AS 部门名称,e.salary AS 月薪
    FROM emp e
    JOIN dept d ON e.dept_id = d.id
    HAVING CASEWHEN d.name = '销售部' THEN e.salary >= 5000  -- 销售部薪资≥5000WHEN d.name = '研发部' THEN e.salary >= 8000  -- 研发部薪资≥8000ELSE TRUE  -- 其他部门不筛选
    END;
    
(3)CASE WHEN的嵌套使用

支持多层CASE WHEN嵌套,处理更复杂的条件逻辑(但嵌套层数不宜过多,影响可读性)。

  • 示例:先判断部门,再判断薪资等级:
    SELECTusername,dept_id,salary,CASEWHEN dept_id = 1 THEN  -- 研发部CASE WHEN salary >= 10000 THEN '高级研发' ELSE '初级研发' ENDWHEN dept_id = 2 THEN  -- 销售部CASE WHEN salary >= 8000 THEN '高级销售' ELSE '初级销售' ENDELSE '其他岗位'END AS 岗位等级
    FROM `employee`;
    

四、总结与实战建议

本文从单表查询的约束、数据操作,到聚合函数、分组逻辑,再到多表查询的关联方式与子查询、CASE WHEN语法,完整覆盖了MySQL进阶查询的核心知识点。以下是实战中的关键建议

  1. 优先用JOIN替代子查询:大部分子查询可以转化为JOIN,JOIN的可读性和效率通常更高(尤其是大数据量场景);
  2. 避免SELECT :只查询需要的字段,减少数据传输量和内存占用;
  3. 索引优化:多表查询的关联字段(如外键)、筛选条件字段、排序字段建议建立索引,提升查询效率;
  4. 测试边界条件:注意NULL值、空表、大数据量的场景,避免统计结果错误或性能崩溃;
  5. 复杂查询拆分:将复杂的多表查询拆分为多个简单查询,或用派生表逐步聚合,提高可读性和可维护性。

如果对某部分内容需要更深入的解析(如索引优化、事务与锁、分库分表),可以评论区留言,后续会补充专题内容。学习MySQL的核心是“多练+多踩坑”,建议结合自己的业务场景,将本文的案例改写为符合实际需求的SQL,真正做到“学以致用”。

http://www.dtcms.com/a/601084.html

相关文章:

  • docker一键部署PDF免费工具箱stirling-PDF
  • CI/CD工具Arbess,从安装到入门零基础教程
  • PG预写式日志解码的艺术与应用
  • 通过Swift CSP评估提高金融安全
  • 高可用与高性能数据库配置实践分析(pgSql clickhouse)
  • android sharepreference 的替代品
  • 网站流量分成营销型网站5要素
  • 科技向暖,银发无忧:十五五规划中智慧养老的温度革命
  • UE5 的 Waterline Pro 6的浮力作用机制解析
  • Selenium WebDriver的工作原理?
  • UE5 C++ 定时器 案例练习
  • 6 个成熟的 JS 开源视频编辑项目
  • 网站建设管理维护责任书格式怎样在网站做链接
  • Flutter---Stream
  • 佛山企业网站建设机构南明区住房和城乡建设局网站上
  • 仓颉三方库开发实战:Simple HTTP Server 实现详解
  • 做360网站官网还是百度济南有做五合一网站公司
  • 详细解读视频生成模型Wan2.1代码
  • Cortex-M3-STM32F1 开发:(二十二)HAL 库开发 ➤ STM32 中断逻辑优先级计算
  • THC63LVD1027D一款10位双链路LVDS信号中继器芯片,支持WUXGA分辨率视频数据传输THC63LVD1027支持30位数据通道方案
  • 考研规划手册
  • MongoDB中 client_connection和database和collection之间的关系
  • 建筑网站建设赏析外贸公司用什么建网站
  • [智能体设计模式] 第4章:反思(Reflection)
  • 系统架构设计师与考研408在IT基础设施能力考核上的全面对比研究
  • 饮用水品牌营销型网站手机网站主页
  • 亿网中国网站管理系统绍兴网站网站建设
  • 基于web宿舍管理系统的设计与实现
  • 利用idea创建springboot多模块项目
  • C++仿muduo库高并发服务器项目:Poller模块