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

【MySQL全面教学】MySQL子查询与高级查询Day7(2026年)

写在前面

大家好,欢迎来到MySQL全面教学系列的第7天!昨天我们学习了多表查询与JOIN,掌握了表与表之间关联查询的技能。今天,我们将进入更高级的查询领域——子查询与高级查询

子查询是嵌套在其他查询中的查询,它可以解决很多复杂的业务问题。而窗口函数和CTE(公用表表达式)则是MySQL 8.0带来的强大特性,让复杂的数据分析变得简单优雅。

这是单表查询系列的最后一篇,内容较多但非常实用,让我们开始吧!


目录

    • 写在前面
    • 一、子查询分类
    • 二、WHERE中的子查询
      • 2.1 IN子查询
      • 2.2 EXISTS子查询
      • 2.3 比较运算符子查询
      • 2.4 ALL和ANY/SOME
    • 三、FROM中的子查询(派生表)
      • 3.1 派生表优化
    • 四、SELECT中的子查询(关联子查询)
    • 五、UNION和UNION ALL
      • 5.1 UNION vs UNION ALL
      • 5.2 UNION注意事项
    • 六、窗口函数(MySQL 8.0+)
      • 6.1 窗口函数语法
      • 6.2 排名函数
      • 6.3 分组排名
      • 6.4 偏移函数(LEAD/LAG)
      • 6.5 聚合窗口函数
    • 七、CTE公用表表达式(WITH子句)
      • 7.1 基本CTE
      • 7.2 多个CTE
      • 7.3 递归CTE
    • 八、实战:复杂业务查询场景
      • 8.1 连续登录天数统计
      • 8.2 分组TOP N
      • 8.3 同期群分析(Cohort Analysis)
    • 九、踩坑提醒与经验之谈
      • 9.1 子查询性能问题
      • 9.2 IN子查询的NULL陷阱
      • 9.3 派生表没有索引
      • 9.4 窗口函数与GROUP BY混用
    • 十、面试高频考点
      • 10.1 IN和EXISTS的区别?
      • 10.2 窗口函数和GROUP BY的区别?
      • 10.3 如何优化子查询?
      • 10.4 CTE和派生表的区别?
      • 10.5 如何实现分组TOP N?
    • 十一、总结
      • 下一步预告
    • 参考资料
    • 互动话题

一、子查询分类

子查询(Subquery)是嵌套在另一个SQL语句中的SELECT语句。根据返回结果的不同,子查询可以分为以下几类:

子查询类型返回值使用场景示例
标量子查询单行单列比较运算SELECT * FROM t WHERE col = (SELECT …)
行子查询单行多列行比较SELECT * FROM t WHERE (a,b) = (SELECT …)
表子查询多行多列FROM子句SELECT * FROM (SELECT …) AS t

二、WHERE中的子查询

2.1 IN子查询

IN用于判断某值是否在子查询返回的结果集中。

-- 查询有订单的用户信息SELECT*FROMusersWHEREuser_idIN(SELECTDISTINCTuser_idFROMorders);-- 查询购买了特定类别商品的用户SELECT*FROMusersWHEREuser_idIN(SELECTDISTINCTo.user_idFROMorders oJOINorder_items oiONo.order_id=oi.order_idJOINproducts pONoi.product_id=p.product_idWHEREp.category_id=1);

2.2 EXISTS子查询

EXISTS用于判断子查询是否返回结果,返回布尔值。

-- 查询有订单的用户(与IN效果相同)SELECT*FROMusers uWHEREEXISTS(SELECT1FROMorders oWHEREo.user_id=u.user_id);-- 查询没有订单的用户SELECT*FROMusers uWHERENOTEXISTS(SELECT1FROMorders oWHEREo.user_id=u.user_id);

IN vs EXISTS对比:

特性INEXISTS
执行方式先执行子查询相关子查询,逐行判断
性能子查询结果小时快子查询结果大时快
NULL处理需注意NULL值不受NULL影响
使用场景固定值列表关联条件复杂时

2.3 比较运算符子查询

-- 查询订单金额大于平均订单金额的记录SELECT*FROMordersWHEREtotal_amount>(SELECTAVG(total_amount)FROMorders);-- 查询每个用户超过其平均订单金额的订单SELECTo1.*FROMorders o1WHEREo1.total_amount>(SELECTAVG(o2.total_amount)FROMorders o2WHEREo2.user_id=o1.user_id);

2.4 ALL和ANY/SOME

-- 查询订单金额大于所有用户平均消费的用户(ALL)SELECT*FROMusers uWHEREtotal_spent>ALL(SELECTAVG(total_amount)FROMordersGROUPBYuser_id);-- 查询订单金额大于任意一个用户平均消费的用户(ANY)SELECT*FROMusers uWHEREtotal_spent>ANY(SELECTAVG(total_amount)FROMordersGROUPBYuser_id);

三、FROM中的子查询(派生表)

子查询在FROM子句中作为临时表使用,必须指定别名。

-- 查询每个用户的订单统计SELECTu.username,t.order_count,t.total_amount,t.avg_amountFROMusers uJOIN(SELECTuser_id,COUNT(*)ASorder_count,SUM(total_amount)AStotal_amount,AVG(total_amount)ASavg_amountFROMordersGROUPBYuser_id)tONu.user_id=t.user_id;

3.1 派生表优化

MySQL 5.7+会对派生表进行合并优化:

-- MySQL会自动将派生表合并到外层查询SELECT*FROM(SELECT*FROMordersWHEREstatus='completed')ASoWHEREo.amount>100;-- 等同于SELECT*FROMordersWHEREstatus='completed'ANDamount>100;

四、SELECT中的子查询(关联子查询)

SELECT中的子查询会为每一行执行一次,称为关联子查询。

-- 查询用户及其订单数量SELECTu.user_id,u.username,(SELECTCOUNT(*)FROMorders oWHEREo.user_id=u.user_id)ASorder_countFROMusers u;-- 查询每个订单的用户消费排名SELECTo.order_id,o.user_id,o.total_amount,(SELECTCOUNT(*)+1FROMorders o2WHEREo2.user_id=o.user_idANDo2.total_amount>o.total_amount)ASrank_in_userFROMorders o;

踩坑提醒:SELECT中的关联子查询性能较差,每行都要执行一次子查询。大数据量时建议使用JOIN替代。


五、UNION和UNION ALL

UNION用于合并多个SELECT语句的结果集。

5.1 UNION vs UNION ALL

特性UNIONUNION ALL
去重自动去重不去重
性能较慢(需要比较)较快
使用场景需要唯一结果允许重复结果
-- UNION去重SELECTcityFROMusersWHEREage>30UNIONSELECTcityFROMusersWHEREtotal_spent>1000;-- UNION ALL保留所有记录SELECT'VIP'ASuser_type,user_id,usernameFROMusersWHEREtotal_spent>10000UNIONALLSELECT'普通',user_id,usernameFROMusersWHEREtotal_spent<=10000;

5.2 UNION注意事项

-- 列数必须相同-- 列名以第一个SELECT为准-- 对应列的数据类型要兼容SELECTuser_id,usernameFROMusersUNIONSELECTorder_id,CAST(total_amountASCHAR)FROMorders;-- 类型转换

六、窗口函数(MySQL 8.0+)

窗口函数是MySQL 8.0引入的强大特性,用于在结果集的"窗口"上进行计算。

6.1 窗口函数语法

function_name(expression)OVER([PARTITIONBYpartition_expression][ORDERBYorder_expression][frame_clause])

6.2 排名函数

函数说明相同值处理
ROW_NUMBER()连续排名相同值不同排名
RANK()跳跃排名相同值同排名,跳过后续
DENSE_RANK()密集排名相同值同排名,不跳过
-- 查询用户消费排名SELECTuser_id,username,total_spent,ROW_NUMBER()OVER(ORDERBYtotal_spentDESC)ASrow_num,RANK()OVER(ORDERBYtotal_spentDESC)ASrank_num,DENSE_RANK()OVER(ORDERBYtotal_spentDESC)ASdense_rank_numFROMusers;

结果示例:

user_idusernametotal_spentrow_numrank_numdense_rank_num
1张三10000111
2李四8000222
3王五8000322
4赵六5000443

6.3 分组排名

-- 按城市分组,查询每个城市的用户消费排名SELECTcity,username,total_spent,RANK()OVER(PARTITIONBYcityORDERBYtotal_spentDESC)AScity_rankFROMusers;

6.4 偏移函数(LEAD/LAG)

-- 查询每月销售额及环比变化SELECTmonth,sales,LAG(sales)OVER(ORDERBYmonth)ASprev_month_sales,sales-LAG(sales)OVER(ORDERBYmonth)ASmonth_over_month,LEAD(sales)OVER(ORDERBYmonth)ASnext_month_salesFROMmonthly_sales;

6.5 聚合窗口函数

-- 累计求和SELECTorder_date,daily_sales,SUM(daily_sales)OVER(ORDERBYorder_date)AScumulative_salesFROMdaily_summary;-- 移动平均SELECTdate,value,AVG(value)OVER(ORDERBYdateROWSBETWEEN6PRECEDINGANDCURRENTROW)AS7_day_avgFROMdaily_data;

七、CTE公用表表达式(WITH子句)

CTE(Common Table Expression)使用WITH子句定义临时结果集,可以多次引用。

7.1 基本CTE

-- 定义CTEWITHuser_statsAS(SELECTuser_id,COUNT(*)ASorder_count,SUM(total_amount)AStotal_spentFROMordersGROUPBYuser_id)-- 使用CTESELECTu.username,us.order_count,us.total_spentFROMusers uJOINuser_stats usONu.user_id=us.user_id;

7.2 多个CTE

WITHorder_statsAS(SELECTuser_id,COUNT(*)ASorder_countFROMordersGROUPBYuser_id),payment_statsAS(SELECTuser_id,SUM(amount)AStotal_paidFROMpaymentsGROUPBYuser_id)SELECTu.username,COALESCE(os.order_count,0)ASorder_count,COALESCE(ps.total_paid,0)AStotal_paidFROMusers uLEFTJOINorder_stats osONu.user_id=os.user_idLEFTJOINpayment_stats psONu.user_id=ps.user_id;

7.3 递归CTE

递归CTE用于处理层级数据:

-- 查询分类的所有子分类WITHRECURSIVE category_treeAS(-- 锚成员:起始点SELECTcategory_id,category_name,parent_id,0ASlevelFROMcategoriesWHEREcategory_id=1-- 从根分类开始UNIONALL-- 递归成员SELECTc.category_id,c.category_name,c.parent_id,ct.level+1FROMcategories cJOINcategory_tree ctONc.parent_id=ct.category_id)SELECT*FROMcategory_tree;

八、实战:复杂业务查询场景

8.1 连续登录天数统计

-- 统计用户最大连续登录天数WITHlogin_with_grpAS(SELECTuser_id,login_date,DATE_SUB(login_date,INTERVALROW_NUMBER()OVER(PARTITIONBYuser_idORDERBYlogin_date)DAY)ASgrpFROMuser_logins)SELECTuser_id,COUNT(*)ASconsecutive_daysFROMlogin_with_grpGROUPBYuser_id,grpORDERBYconsecutive_daysDESC;

8.2 分组TOP N

-- 查询每个类别销量TOP3的商品WITHranked_productsAS(SELECTcategory_id,product_id,product_name,sales_volume,RANK()OVER(PARTITIONBYcategory_idORDERBYsales_volumeDESC)ASrnkFROMproducts)SELECT*FROMranked_productsWHERErnk<=3;

8.3 同期群分析(Cohort Analysis)

-- 用户留存分析WITHuser_cohortsAS(SELECTuser_id,DATE_FORMAT(MIN(order_date),'%Y-%m')AScohort_monthFROMordersGROUPBYuser_id),user_activityAS(SELECTDISTINCTu.user_id,uc.cohort_month,DATE_FORMAT(o.order_date,'%Y-%m')ASactivity_month,PERIOD_DIFF(DATE_FORMAT(o.order_date,'%Y%m'),DATE_FORMAT(uc.cohort_month,'%Y%m'))ASperiod_numFROMorders oJOINuser_cohorts ucONo.user_id=uc.user_id)SELECTcohort_month,period_num,COUNT(DISTINCTuser_id)ASuser_count,ROUND(COUNT(DISTINCTuser_id)*100.0/FIRST_VALUE(COUNT(DISTINCTuser_id))OVER(PARTITIONBYcohort_monthORDERBYperiod_num),2)ASretention_rateFROMuser_activityGROUPBYcohort_month,period_numORDERBYcohort_month,period_num;

九、踩坑提醒与经验之谈

9.1 子查询性能问题

问题:关联子查询性能差

-- 低效写法:每行执行一次子查询SELECTu.*,(SELECTCOUNT(*)FROMorders oWHEREo.user_id=u.user_id)AScntFROMusers u;-- 高效写法:使用JOINSELECTu.*,COALESCE(o.cnt,0)AScntFROMusers uLEFTJOIN(SELECTuser_id,COUNT(*)AScntFROMordersGROUPBYuser_id)oONu.user_id=o.user_id;

经验之谈:能用JOIN就不用子查询,特别是SELECT中的关联子查询。

9.2 IN子查询的NULL陷阱

-- 危险!子查询返回NULL会导致结果为空SELECT*FROMusersWHEREuser_idIN(SELECTmanager_idFROMemployees);-- 如果manager_id有NULL-- 安全写法:排除NULLSELECT*FROMusersWHEREuser_idIN(SELECTmanager_idFROMemployeesWHEREmanager_idISNOTNULL);-- 或者使用EXISTSSELECT*FROMusers uWHEREEXISTS(SELECT1FROMemployees eWHEREe.manager_id=u.user_id);

9.3 派生表没有索引

-- 派生表不能直接使用索引-- 如果需要优化,考虑创建临时表并加索引CREATETEMPORARYTABLEtmp_user_statsASSELECTuser_id,COUNT(*)AScntFROMordersGROUPBYuser_id;ALTERTABLEtmp_user_statsADDINDEXidx_user_id(user_id);-- 然后使用临时表进行JOIN

9.4 窗口函数与GROUP BY混用

-- 错误:窗口函数在GROUP BY之后执行SELECTcategory_id,SUM(sales),RANK()OVER(ORDERBYSUM(sales))-- 错误!FROMproductsGROUPBYcategory_id;-- 正确:使用派生表或CTESELECTcategory_id,total_sales,RANK()OVER(ORDERBYtotal_sales)FROM(SELECTcategory_id,SUM(sales)AStotal_salesFROMproductsGROUPBYcategory_id)t;

十、面试高频考点

10.1 IN和EXISTS的区别?

答案:

  • IN:先执行子查询,将结果缓存,然后外层查询匹配。适合子查询结果小的情况。
  • EXISTS:相关子查询,对外层每行执行子查询,找到匹配即返回。适合子查询结果大的情况。
  • IN遇到NULL会有问题,EXISTS不会。

10.2 窗口函数和GROUP BY的区别?

答案:

  • GROUP BY:将多行聚合成一行,行数减少
  • 窗口函数:保持原有行数,为每行添加计算列
  • GROUP BY后使用聚合函数,窗口函数使用OVER子句

10.3 如何优化子查询?

答案:

  1. 将SELECT中的关联子查询改为JOIN
  2. 将IN子查询改为EXISTS(子查询大时)
  3. 确保子查询的关联字段有索引
  4. 使用派生表时,考虑物化为临时表

10.4 CTE和派生表的区别?

答案:

  • CTE使用WITH定义,可读性更好,可以递归
  • 派生表直接在FROM中定义
  • CTE可以多次引用,派生表需要重复定义
  • MySQL 8.0+推荐优先使用CTE

10.5 如何实现分组TOP N?

答案:使用窗口函数

WITHrankedAS(SELECT*,ROW_NUMBER()OVER(PARTITIONBYgroup_idORDERBYscoreDESC)ASrnFROMscores)SELECT*FROMrankedWHERErn<=N;

十一、总结

今天我们学习了MySQL子查询与高级查询的核心知识:

  1. 子查询分类:标量子查询、行子查询、表子查询
  2. WHERE中的子查询:IN、EXISTS、比较运算符
  3. FROM中的子查询:派生表的使用和优化
  4. SELECT中的子查询:关联子查询的性能问题
  5. UNION/UNION ALL:合并结果集
  6. 窗口函数:ROW_NUMBER、RANK、LEAD/LAG等
  7. CTE:公用表表达式,提高可读性

下一步预告

Day8:MySQL索引原理与优化

明天我们将进入MySQL性能优化的核心领域——索引。你将学习到B+树索引原理、索引类型、索引设计原则,以及如何通过EXPLAIN分析查询性能。索引是数据库优化的基石,掌握它,你就能让查询速度提升百倍!敬请期待!


参考资料

MySQL 8.0 Reference Manual - Subqueries


互动话题

  1. 你在使用子查询时遇到过哪些性能问题?是如何解决的?
  2. 你的MySQL版本是多少?是否已经用上了窗口函数和CTE?
  3. 在实际项目中,你觉得子查询和JOIN哪个更好用?

如果觉得本文对你有帮助,请点赞收藏!明天见!

http://www.jsqmd.com/news/884958/

相关文章:

  • 珍宝黄金回收(十年老店):2026年5月金价波动,东河老街坊的旧金如何卖出好价钱? - 润富黄金珠宝行
  • mybatis执行流程、关联映射、注解开发
  • 收藏!2026年大模型行业爆发,小白程序员黄金入局期,薪资暴涨必看
  • Claude PEST分析实战手册(2024最新版):从政策红线到技术适配,7步构建合规AI决策框架
  • Lovable电商网站搭建全流程拆解(含GitHub可运行源码+AWS部署Checklist)
  • 2026 收藏版|生产级 AI Agent 落地现状剖析,程序员入门大模型必看行业报告
  • 收藏|2026零基础逆袭大模型工程师,三个月实战转型路线干货
  • 如何突破网盘限速瓶颈?LinkSwift直链解析工具让企业文件传输效率提升300%
  • 为内部知识库问答系统集成 Taotoken 提供多模型备选与故障切换
  • AAAI 2025 | VHM:面向遥感图像分析的通用可信视觉语言模型
  • 2026年资质齐全的炸鸡小吃加盟品牌排名 - 资讯焦点
  • 基于M5Stack与SCD40的室内空气质量监测站:从原理到实践
  • SingleFile完整使用指南:掌握网页离线保存的终极解决方案
  • 基于Arduino与DCF77的LED数码管无线电钟设计与实现
  • 基于ESP8266监听模式的低成本空中搜救信号探测系统设计与实现
  • 腾讯元宝GEO排名优化:2026年AI搜索流量抢占的系统性方法论 - 博客湾
  • taotoken多模型聚合api在ubuntu服务器上的稳定部署实践
  • OpenHRMS:如何用开源方案解决中小企业人力资源管理难题?
  • Kali Linux 2024.2 国内镜像源一键配置脚本(附清华、阿里云、中科大源地址)
  • 终极指南:如何用wechat-need-web插件突破微信网页版访问限制
  • 5分钟掌握终极音乐解锁方案:让所有加密音乐重获自由
  • Zip压缩包密码恢复
  • 珍宝黄金回收(十年老店)|2026 年 5 月扬州江都黄金回收行情解读、避坑技巧与 FAQ 在扬州江都区,提到黄金回收,很多老居民第一反应就是珍宝。 - 润富黄金珠宝行
  • 收藏必备|2026 版 AI 大模型应用开发学习指南,程序员转行增收绝佳路径
  • 3步设置解放双手!AzurLaneAutoScript碧蓝航线自动化脚本终极使用指南
  • 2026 年 5 月大连黄金回收避坑指南:添价收黄金奢侈品回收为首选,六家正规机构优势全解析 - 薛定谔的梨花猫
  • 【MySQL全面教学】MySQL索引原理与优化Day8(2026年)
  • 长期使用 Taotoken Token Plan 套餐对项目成本控制的实际影响
  • 河北钢格栅踏步板技术选型指南及合规供应商盘点 - 奔跑123
  • 从分区看设计哲学:深度对比银河麒麟V10、Windows 11与Ubuntu的系统布局