深入解析MySQL SELECT语句:从基础查询到复杂数据加工流水线
1. 从“查户口”到“做报表”理解SELECT语句的完整工作流如果你刚开始接触数据库或者已经写了不少SQL但总觉得只是“会用”那么今天咱们就来聊聊MySQL里最核心、也最容易被轻视的SELECT语句。很多人觉得SELECT不就是“查数据”吗SELECT * FROM users谁不会写。但当你面对一个需要“统计每个部门上个月销售额最高的前三位员工”的需求时可能就会对着屏幕发愣不知道WHERE、GROUP BY、HAVING、ORDER BY、LIMIT这几个子句该怎么组合谁先谁后。其实一个完整的SELECT查询就像一次严谨的数据加工流水线。FROM是去仓库取原料WHERE是初步筛选掉不合格的原料GROUP BY是把原料按类别分堆HAVING是检查分好的堆是否满足要求ORDER BY是把最终产品按规则排列LIMIT是只取出指定数量的产品。这个顺序FROM-WHERE-GROUP BY-HAVING-SELECT-ORDER BY-LIMIT是MySQL内部执行的逻辑顺序而不是我们书写的顺序理解这一点是写出高效、正确SQL的关键。接下来我们就沿着这条流水线把每个环节掰开揉碎了讲清楚。2. FROM子句你的数据从哪里来FROM子句是整个查询的起点它定义了数据的来源。这听起来简单但里面的门道不少直接影响到查询的性能和正确性。2.1 单表查询一切的基础最基本的用法就是从一张表里取数据。SELECT * FROM employees;这条语句会从employees表中取出所有列的所有行。但在生产环境我强烈建议你永远不要轻易使用SELECT *。原因有三第一它不明确别人或未来的你看到代码不知道到底返回了哪些字段第二如果表结构发生变化比如新增了存储大文本或图片的列SELECT *会毫无必要地传输大量数据拖慢性能第三在程序代码中按索引位置获取字段值极易出错。正确的做法是显式地列出需要的字段名。SELECT employee_id, first_name, last_name, department_id FROM employees;2.2 多表连接关系的艺术现实中的数据很少孤立存在。FROM更强大的能力在于连接JOIN多张表。这里最常见的坑就是混淆几种JOIN的类型。INNER JOIN内连接只返回两个表中连接条件匹配的行。这是最常用的连接方式。SELECT e.employee_id, e.name, d.department_name FROM employees e INNER JOIN departments d ON e.department_id d.department_id;这条语句只查询那些有明确部门的员工。如果一个新员工还没分配部门department_id为NULL或者departments表里没有对应的部门ID这条记录就不会出现在结果里。LEFT JOIN左外连接返回左表employees的所有行即使右表departments中没有匹配的行。如果右表无匹配则结果中右表的部分全部为NULL。SELECT e.employee_id, e.name, d.department_name FROM employees e LEFT JOIN departments d ON e.department_id d.department_id;这条语句会列出所有员工包括那些还没分配部门的。这在做数据统计或生成报告时非常有用确保主表数据不丢失。关于RIGHT JOIN和FULL OUTER JOINRIGHT JOIN右外连接与LEFT JOIN相反返回右表所有行。而FULL OUTER JOIN全外连接返回左右两表的所有行不匹配处用NULL填充。但在MySQL中原生并不支持FULL OUTER JOIN这是一个常见的误区。如果需要实现全外连接的效果通常需要通过LEFT JOIN ... UNION ... RIGHT JOIN的方式来模拟这一点在面试和实际工作中都经常被问到。实操心得在多表关联时务必使用表别名如e,d这能让查询更清晰并且在字段名重复时避免歧义。另外关于LEFT JOIN和INNER JOIN的选择我的经验是当你需要确保结果集必须包含主表的全部记录时例如统计所有用户的订单包括没下过单的用户用LEFT JOIN当你只关心有关联关系的记录时用INNER JOIN它的效率通常更高。2.3 子查询作为数据源FROM后面不仅可以跟表名还可以跟一个子查询派生表。这常用于需要先对数据进行一轮聚合或过滤再在此基础上进行复杂操作的场景。SELECT dept_avg.dept_id, dept_avg.avg_salary, e.name FROM ( SELECT department_id AS dept_id, AVG(salary) AS avg_salary FROM employees GROUP BY department_id HAVING AVG(salary) 10000 ) AS dept_avg INNER JOIN employees e ON dept_avg.dept_id e.department_id WHERE e.salary dept_avg.avg_salary;这个查询先找出平均工资超过10000的部门及其平均工资派生表dept_avg然后再关联员工表找出工资高于本部门平均工资的员工。注意派生表必须有别名AS dept_avg。3. WHERE与HAVING筛选的艺术与边界WHERE和HAVING都用于过滤数据但它们的操作对象和时机有本质区别混淆它们是新手最常犯的错误之一。3.1 WHERE子句行级过滤的守门员WHERE在GROUP BY分组之前执行它作用于原始的每一行数据。你可以把它想象成原料进入加工流水线前的质检员。SELECT department_id, COUNT(*) as emp_count, AVG(salary) as avg_salary FROM employees WHERE hire_date 2023-01-01 -- 只筛选2023年以后入职的员工 GROUP BY department_id;这里WHERE先过滤出所有在2023年后入职的员工记录然后再对这些记录进行按部门分组和统计。WHERE子句中不能使用聚合函数如COUNT,AVG,SUM因为此时分组还没发生MySQL不知道你要对谁进行聚合。常见的WHERE条件运算符比较运算符,或!,,,,逻辑运算符AND,OR,NOT范围匹配BETWEEN ... AND ...,IN (value1, value2, ...)模糊匹配LIKE(配合%和_通配符)REGEXP空值判断IS NULL,IS NOT NULL踩坑记录判断字段是否为NULL一定要用IS NULL而不是 NULL。因为在SQL标准中NULL与任何值包括NULL本身的比较结果都是UNKNOWN而不是TRUE或FALSE。WHERE column NULL这个条件永远无法匹配到任何行这是一个经典的坑。3.2 HAVING子句组级过滤的质检员HAVING在GROUP BY分组之后执行它作用于分组后的结果集即每一个分组。它是分组流水线上的质检员检查成品的质量。SELECT department_id, COUNT(*) as emp_count, AVG(salary) as avg_salary FROM employees WHERE hire_date 2023-01-01 GROUP BY department_id HAVING COUNT(*) 5 AND AVG(salary) 8000; -- 筛选员工数大于5且平均工资高于8000的部门这里HAVING子句使用了聚合函数COUNT(*)和AVG(salary)因为它是在分组统计完成后对统计结果每个部门的人数和平局工资进行过滤。它筛选掉那些人数太少或平均工资不达标的部门分组。3.3 WHERE vs HAVING核心区别与选用策略为了更清晰我们用一个表格来对比特性WHERE 子句HAVING 子句执行顺序在分组 (GROUP BY)之前在分组 (GROUP BY)之后操作对象原始数据表的行分组后的组聚合结果能否使用聚合函数不能可以且常用索引利用可以有效利用索引大幅提升性能通常不能利用索引因为是对中间结果过滤典型用途过滤掉不需要参与分行的行如时间范围、状态条件过滤掉不满足条件的分组结果如数量、总和、平均值阈值选用策略能用WHERE解决的绝不用HAVING。因为WHERE在分组前过滤减少了需要处理的数据量性能优势巨大。例如想找销售额超过10000的销售员应该先WHERE sales 10000过滤个人再GROUP BY而不是先按人分组求和再用HAVING SUM(sales) 10000。必须对聚合结果进行过滤时才用HAVING。比如“找出订单总数超过10笔的客户”这里的“订单总数”是聚合结果必须用HAVING COUNT(order_id) 10。4. GROUP BY与聚合函数数据分箱与统计当我们需要回答“每个”、“各类”、“各组”这样的问题时GROUP BY就登场了。它将数据行分成不同的“组”或“桶”然后允许我们对每个组进行统计计算。4.1 GROUP BY的基本用法SELECT department_id, COUNT(employee_id) AS employee_count FROM employees GROUP BY department_id;这条语句按department_id将员工分组并计算每个部门的员工数量。SELECT后面跟着的字段要么是GROUP BY子句中出现的字段department_id要么是包裹在聚合函数里的字段COUNT(employee_id)。这是GROUP BY的一条硬性规则。4.2 常用的聚合函数COUNT()计数。COUNT(*)统计所有行数COUNT(column)统计该列非NULL值的行数。SUM()求和。仅对数值型字段有效。AVG()求平均值。仅对数值型字段有效忽略NULL值。MAX()/MIN()求最大/最小值。适用于数值、日期、字符串等多种类型。GROUP_CONCAT()MySQL特有将同一组内的字符串值连接成一个字符串。非常实用。SELECT department_id, GROUP_CONCAT(first_name ORDER BY hire_date SEPARATOR , ) AS members FROM employees GROUP BY department_id;这会列出每个部门所有员工的名字按入职日期排序用逗号分隔。4.3 多列分组与WITH ROLLUPGROUP BY可以按多列进行分组生成更细粒度的统计。SELECT department_id, job_title, AVG(salary) FROM employees GROUP BY department_id, job_title;这会计算每个部门内、每个职位的平均工资。WITH ROLLUP是GROUP BY的一个扩展它会在结果中添加额外的“小计”和“总计”行。SELECT department_id, job_title, SUM(salary) FROM employees GROUP BY department_id, job_title WITH ROLLUP;结果中每个department_id分组结束后会有一行该部门的工资小计job_title为NULL最后还会有一行所有部门的总计department_id和job_title均为NULL。这在制作汇总报表时极其方便。注意事项使用GROUP BY时SELECT列表中的非聚合列必须出现在GROUP BY子句中否则MySQL在严格模式下会报错在非严格模式下可能返回无意义的值从每组中任意选取一行。这是一个重要的数据一致性保障。5. ORDER BY与LIMIT结果的呈现与约束数据经过筛选、分组、过滤后我们通常需要以某种顺序呈现或者只取其中的一部分。这就是ORDER BY和LIMIT的职责。5.1 ORDER BY给结果排序ORDER BY指定结果集的排序方式。默认是升序ASC降序需要用DESC。SELECT name, salary, hire_date FROM employees WHERE department_id 5 ORDER BY salary DESC, hire_date ASC; -- 先按工资降序工资相同再按入职日期升序你可以按多个字段排序优先级从左到右。排序可以基于SELECT列表中的列、表达式甚至是列别名。SELECT name, salary, (salary * 0.1) AS bonus FROM employees ORDER BY bonus DESC; -- 使用列别名排序性能提示ORDER BY如果操作大量数据且排序字段没有索引可能会引发文件排序Using filesort这是一个比较耗时的操作。对于需要频繁排序的查询考虑在排序字段上建立索引。5.2 LIMIT限制返回行数LIMIT用于限制查询结果返回的行数常用于分页查询和获取Top N记录。-- 获取工资最高的前10名员工 SELECT name, salary FROM employees ORDER BY salary DESC LIMIT 10; -- 经典的分页查询LIMIT offset, row_count -- 获取第6到第15条记录每页10条的第二页 SELECT name, salary FROM employees ORDER BY salary DESC LIMIT 10 OFFSET 5; -- 或者写作 LIMIT 5, 10LIMIT的两个参数第一个是偏移量从0开始第二个是返回的行数。LIMIT 5, 10表示跳过前5条取接下来的10条。分页查询的深坑与优化当偏移量OFFSET非常大时比如LIMIT 1000000, 20MySQL需要先扫描并跳过前100万条记录效率极低。一个常见的优化策略是使用“基于游标的分页”或“基于索引的分页”。例如记录上一页最后一条记录的ID或排序字段值下一页查询时用WHERE id last_id ORDER BY id LIMIT 20这样就能利用索引快速定位避免大偏移量扫描。6. SELECT语句的执行顺序与编写顺序这是理解复杂SQL的钥匙。我们书写的顺序和数据库引擎执行的顺序是不同的。书写顺序我们写SQL的顺序SELECT-FROM-WHERE-GROUP BY-HAVING-ORDER BY-LIMIT执行顺序数据库实际处理的顺序FROMJOIN: 确定数据来源进行表连接。WHERE: 对连接后的原始数据行进行过滤。GROUP BY: 对过滤后的数据进行分组。HAVING: 对分组后的结果进行过滤。SELECT:计算选择列表中的表达式包括聚合函数、普通列、别名等。注意SELECT子句中的别名在这一步才被赋值这就是为什么不能在WHERE中使用SELECT中定义的别名但可以在ORDER BY中使用。ORDER BY: 对最终的结果集进行排序。LIMIT: 限制返回的行数。理解这个顺序就能明白为什么WHERE里不能用别名和聚合函数而HAVING和ORDER BY可以。也能明白为什么先WHERE过滤再GROUP BY通常性能更好。7. 综合实战一个复杂查询的拆解与优化让我们用一个接近真实的场景来串联所有知识点“找出2023年每个部门销售额排名前3的销售员且该销售员的总销售额必须超过10万元最后按部门编号和销售额排名排序。”假设我们有orders订单表和employees员工表。第一步明确需求与数据流时间范围2023年 (WHERE)按部门和销售员分组 (GROUP BY)组过滤销售员总销售额10万 (HAVING)组内排序每个部门内按销售额排名 (子查询/窗口函数)取Top 3 (LIMITin subquery)最终排序按部门、排名排序 (ORDER BY)第二步分步构建查询在MySQL 8.0以下版本我们需要使用子查询和变量来模拟排名。-- 首先计算每个销售员在2023年的总销售额 WITH sales_summary AS ( SELECT e.department_id, e.employee_id, e.name AS salesperson_name, SUM(o.amount) AS total_sales FROM employees e INNER JOIN orders o ON e.employee_id o.salesperson_id WHERE o.order_date 2023-01-01 AND o.order_date 2024-01-01 -- WHERE过滤行 AND e.job_title Sales Representative -- 假设只查销售代表 GROUP BY e.department_id, e.employee_id, e.name -- GROUP BY分组 HAVING SUM(o.amount) 100000 -- HAVING过滤组 ) -- 然后为每个部门内的销售员计算排名 , ranked_sales AS ( SELECT department_id, salesperson_name, total_sales, -- 使用变量计算部门内的销售额排名 rank : IF(current_dept department_id, rank 1, 1) AS sales_rank, current_dept : department_id FROM sales_summary CROSS JOIN (SELECT current_dept : NULL, rank : 0) AS vars -- 初始化变量 ORDER BY department_id, total_sales DESC -- ORDER BY确定排名依据 ) -- 最后筛选出每个部门的前三名并按要求排序 SELECT department_id, salesperson_name, total_sales, sales_rank FROM ranked_sales WHERE sales_rank 3 -- 在排名结果上过滤Top 3 ORDER BY department_id, sales_rank; -- 最终排序第三步MySQL 8.0 的现代化写法使用窗口函数如果使用MySQL 8.0或更高版本代码会简洁优雅得多性能也通常更好。SELECT * FROM ( SELECT e.department_id, e.employee_id, e.name AS salesperson_name, SUM(o.amount) AS total_sales, -- 使用窗口函数为每个部门内的销售员生成排名 ROW_NUMBER() OVER (PARTITION BY e.department_id ORDER BY SUM(o.amount) DESC) AS sales_rank FROM employees e INNER JOIN orders o ON e.employee_id o.salesperson_id WHERE o.order_date 2023-01-01 AND o.order_date 2024-01-01 AND e.job_title Sales Representative GROUP BY e.department_id, e.employee_id, e.name HAVING SUM(o.amount) 100000 ) AS ranked_data WHERE sales_rank 3 ORDER BY department_id, sales_rank;窗口函数ROW_NUMBER()在GROUP BY和HAVING之后执行它在每个部门PARTITION BY内部按照销售额ORDER BY降序分配唯一的排名。这种方法逻辑清晰易于理解和维护。优化思考在orders.order_date和employees.job_title上建立索引能加速WHERE过滤。在orders.salesperson_id上建立索引能加速JOIN操作。如果orders表数据量巨大可以考虑按order_date进行分区这样WHERE条件的时间范围查询可能只需扫描少数分区性能提升显著。通过这个完整的例子你应该能感受到SELECT语句各个子句如何环环相扣共同完成一个复杂的数据查询需求。从明确需求、选择数据源(FROM/WHERE)、分组聚合(GROUP BY/HAVING)、到排序限制(ORDER BY/LIMIT)每一步都考验着你对数据流和SQL执行顺序的理解。

相关新闻

智慧园区AR导览和传统电子屏比哪个好

智慧园区AR导览和传统电子屏比哪个好

在智慧园区的数字化转型进程中,信息触达终端的演进始终是一个核心议题。过去十年,我们见证了从静态指示牌到LCD电子屏,再到如今基于增强现实(AR)技术的空间计算导览系统的迭代。对于许多园区管理者和技术决策者而言&am…

2026/10/17 13:12:10 阅读更多 →
Agentic AI:为什么团队接了Demo,效率反而更慢?

Agentic AI:为什么团队接了Demo,效率反而更慢?

聊《Agentic AI真能提效吗?先看流程里最慢的那一步》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要最近业务方提了个需求:做一个能自动处理工单的智能Agent,从读取用户描述、…

2026/10/17 13:12:12 阅读更多 →
Codex 个人用很香,为什么团队接入后反而拖了后腿?

Codex 个人用很香,为什么团队接入后反而拖了后腿?

聊《Codex到底能不能干活?别只看 Demo 和跑分》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要摘要:Codex 在个人开发者手里是神兵利器,但当我把它接入团队项目时&#xff0c…

2026/10/17 13:17:45 阅读更多 →

最新新闻

ELMAN神经网络在天然气日负荷预测中的实战应用与优势解析

ELMAN神经网络在天然气日负荷预测中的实战应用与优势解析

简介:本资源是一份面向能源预测领域初学者与MATLAB实践者的ELMAN神经网络时间序列建模实战包,聚焦天然气消费量的短期趋势预测问题,适用于能源管理、智能调度及机器学习入门教学场景。压缩包共6个文件(290KB)&#xff…

2026/10/18 12:15:19 阅读更多 →
Linux下C库函数文件操作:从缓冲区到性能优化的实战指南

Linux下C库函数文件操作:从缓冲区到性能优化的实战指南

最近处理一批日志文件时,我遇到一个很有意思的现象:同样的数据导入需求,直接用read()逐字节读慢得让人着急,换成fread()之后几乎是瞬间完成。问题不出在代码逻辑,而出在缓冲区。这篇文章我想把Linux下C库函数操作文件这…

2026/10/18 12:15:19 阅读更多 →
企业估值中AI文献综述平台评估:能力拆解、评分表与落地避坑

企业估值中AI文献综述平台评估:能力拆解、评分表与落地避坑

做企业估值的都清楚一个绕不开的矛盾:你想真正判断一家公司的技术壁垒,就得去啃论文、读临床数据、翻专利,但这些材料越积越多,光靠人工读完再整理,速度根本跟不上决策节奏。我最近在为一个生物技术公司做估值&#xf…

2026/10/18 12:15:19 阅读更多 →
产品测试与试验汇总实操指南:从矩阵设计到缺陷闭环

产品测试与试验汇总实操指南:从矩阵设计到缺陷闭环

做了这么多年产品测试和试验,我越来越觉得,真正考验功夫的不是某一项试验能不能跑通,而是把几十项甚至上百项测试和试验汇总成一份能支撑决策的结论。很多人以为汇总就是拉个Excel表、贴几张报告封面,但实际做起来,从测…

2026/10/18 12:15:19 阅读更多 →
JMeter 5.6.2 运行必备:JDK 17 环境配置与中文插件实战指南

JMeter 5.6.2 运行必备:JDK 17 环境配置与中文插件实战指南

简介:本资源为Apache JMeter 5.6.2官方完整版压力测试工具安装包,面向软件测试工程师、性能测试初学者及Java技术栈开发人员,用于Web应用、数据库、FTP及各类协议服务的高并发负载与稳定性验证。压缩包为RAR格式,大小88.26MB&…

2026/10/18 12:15:19 阅读更多 →
Agent工具调用三年演进:从function call到MCP,三种方案的设计取舍与TaoToken实践

Agent工具调用三年演进:从function call到MCP,三种方案的设计取舍与TaoToken实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:14:19 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →