在近三年帮助业务团队搭建数据分析体系的过程中,我接触过大量“从零转行”的数据分析师。他们的简历上都写着“掌握 SQL”,但真正面对一张 2000 万行的订单表时,多半会在一小时内写完查询,却在最后一刻被 WHERE 条件的执行顺序绊倒。我自己的入行经历也验证了同一件事:SQL 入门最关键的部分不是记住多少语法,而是理解数据是怎么被筛选、聚合、关联起来的,以及哪些操作会让查询失控。
所以我想用一篇长文,把数据分析入门 SQL 真正需要学的基础语法,按我认可的顺序讲清楚。
我把 SQL 学习分成三个阶段:能跑通查询、能取对数、能设计出可复用的数据逻辑。绝大多数数据分析岗位面试和实际工作所考察的,并不是你能默写出多少函数,而是你是否知道“从哪张表开始、过滤掉什么、保留什么、按什么粒度汇总”。所以我带新人的第一课从来不讲 SELECT 之外的复杂概念,而是让 TA 先回答三个问题:数据在哪张表?每一行代表什么业务事件?你要按什么维度聚合?
一个典型的例子是这样的:某 SaaS 平台要统计“本月新增客户的首次付费金额”。很多新人会直接对订单表做 SUM,却忘了先用客户维度的“首单时间”过滤出新增客户名单。结果就是,数值算出来了,口径却错了。可见,取数思维的起点是理解“表中行的粒度”,而非理解某个函数。
实际工作中高频使用的 SQL 语法不超过 20 个关键词。以我所在的团队为例,通过对三个月内取数任务日志的统计,SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、JOIN、CASE WHEN、窗口函数等语法覆盖了约 90% 的分析查询。相比之下,存储过程、动态 SQL、复杂函数嵌套,在数据分析和经营决策场景里极少被用到。
只要你能在数据集上独立完成“过滤,关联,聚合,条件判断,排序”这五个动作,就已经具备做数据报表、AB 实验分析和经营诊断的 SQL 基础。

我进入数据分析行业的第一份工作,大量时间都花在从后台导出 Excel、再用透视表手工汇总数据。当时一个项目要复盘 120 天的用户行为数据,Excel 打开文件直接卡死,最后只能用 VLOOKUP 分批次处理,凌晨两点还在对明细。后来带我的同事用四行 SQL 把同样问题解决了,从执行到出数不到 15 秒。这个场景让我真正下定决心系统学习 SQL。
Excel 能处理的行数通常在几十万行以内,而一个中型电商平台一周的订单明细就有上百万行。业务部门的需求是“我要看高价值用户和普通用户的跨品类购买差异”,这句话落到数据层,至少需要关联用户表、订单表、品类维表和用户分群表。在 Excel 里完成这件事,需要经过很多次 VLOOKUP 和数据透视;而在 SQL 里只需要做两次 JOIN 和一次 CASE WHEN 分群。
SQL 的核心价值就是让分析师直接面对明细数据,自己掌握口径,而不是等待别人加工。 我见过太多团队成员因为取数周期太长,放弃了本可以深入分析的问题,转而只做“已有报表的解读”,这就丧失了数据分析的主动性。
这个流程中,最容易被忽略的是“第一步”。很多 SQL 新手拿到需求直接写代码,做完才发现把“付费用户”和“注册用户”混在一个口径里。我的习惯是:动手写 SQL 之前,先花 5 分钟口头复述一次取数逻辑,讲不清就不写。

过去几年,我至少辅导过 50 位初级分析师和转行学习者。他们踩过的坑高度一致,我觉得非常值得专门拆开讲。这些误区不只是“写错了”,而是从根本上影响了你对 SQL 的理解。
这是最大的误区。SQL 读作“结构化查询语言”,它更像一种描述需求的表达方式,而不是像 Python 或 Java 那样需要你控制每一步执行过程。不要纠结“这个循环怎么写”,你要做的是告诉数据库“我要什么”。SQL 中你会反复用到的思维,是集合思维,而不是编程思维。
举个例子,有人想把 A 表和 B 表中“都存在”的用户找出来,第一反应是写循环遍历;学习 SQL 之后才知道,只需要一条 INNER JOIN。还有人习惯先取一个结果,再人为按主键去过滤第二个表,这本质上也是对集合操作的误解。
很多学习者把 SQL 入门理解为“背 SUM、COUNT、DATE_FORMAT”,然后拿函数去套业务。但我见过一个最典型的问题是:同一张支付表,按订单号汇总支付金额和按用户汇总支付金额,两者结果完全不同。不是函数错了,而是汇总粒度不同。
在写任何聚合 SQL 前,你需要先回答:这一行代表什么事件?如果一行代表“一笔订单”,那 COUNT(*) 就是订单数;如果一行代表“一个订单中的一件商品”,那 COUNT(*) 就是商品明细数。粒度判断错误,后面做得越复杂,错得越远。
真实业务表中的数据几乎都不完美。比如退款表可能同一退款单出现两次;用户登录日志可能同一天记录了多次会话;CRM 系统里的客户主数据可能因为导入批次不同,同一客户被存储为多条记录。初学者最容易犯的错误,就是拿到表后直接 SUM 或 COUNT,完全不去检查是否有重复。
我的习惯是所有聚合查询里,先做一次 COUNT(*) 和 COUNT(DISTINCT 主键) 的对比,两个数字不一致,就说明数据有重复。不要小看这一步,它决定了聚合结果是否可信。
MySQL、PostgreSQL、Hive、某型云端数仓,它们的核心语法高度相似,但细节存在差异,比如字符串截取函数、日期函数命名、分页写法。入门时如果只对着某个数据库的专用文档学,换一个环境就会卡壳。更合理的做法是:先掌握标准 SQL 的共性逻辑,把方言差异当成查文档时解决的问题。
我见过一个转行学员,他在 MySQL 里用 LIMIT 10 做分页,到了某型云端数仓发现不支持 LIMIT,于是整段查询都不会写了。如果他当时理解的是“限制返回行数是一个逻辑动作”,而不是死记 LIMIT 这个关键词,就不会被这种小事卡住。

判断一个人 SQL 入门是否扎实,我通常不是看他会不会用 JOIN,而是看他能否讲清楚“SQL 的执行顺序”。大部分教程教你按 SELECT、FROM、WHERE、GROUP BY 的顺序去记忆,但数据库真实的逻辑执行顺序完全不同。之前带过一名新人,他写了一条查询:先进行完聚合,然后在 WHERE 里想去引用聚合后的字段做过滤,于是报错后跑来问我。这不是粗心,而是没有理解 WHERE 和 HAVING 的语义差异。
基于这个顺序,你就能解释很多入门时的困惑:WHERE 不能使用 SELECT 里的别名,因为 SELECT 在 WHERE 之后才执行;HAVING 不能用于未被 SELECT 分组的原始字段,因为它在 GROUP BY 之后执行。真正高效的学习方式,是用逻辑执行顺序去反推每一步应做什么。
WITH AS 拆解复杂逻辑,把“先算 A,再算 B,最后合并”变成可读性强的 SQL 脚本。我面试分析师时,通常不问“会不会用窗口函数”,而是给他一个“计算每个用户首单之后的第二单时间差”场景。这题可以用子查询做,也可以用窗口函数做。能想到窗口函数的候选人,通常对数据结构的理解更深。
存储过程、游标、触发器、动态 SQL、跨库事务,这些在数据分析日常取数中基本用不到。我在一线做分析三年多,自己写过的存储过程一只手数得过来。学习资源有限的情况下,我建议你先跳过这些,把时间花在 CTE 和窗口函数上,它们对提升分析效率的作用明显更大。

为了展示我自己认可的 SQL 基础语法到底怎么串起来,我用一个高频业务指标“月度重复购买率”来完整走一遍。这个指标在很多行业有不同定义,我采用一个相对通用的定义:在当月有购买行为的用户中,在其后一个月再次有购买行为的用户占比。这里需要用到订单表和用户维度,下面直接看代码逻辑。
订单表 orders 的字段包括:order_id(订单号)、user_id(用户ID)、order_date(下单时间)、order_amount(订单金额)。一行代表一个订单。我们需要基于该表找到“2024年5月有购买行为的用户”,然后找出其中“在2024年6月再次购买”的用户数。
WITH monthly_user AS ( SELECT user_id, DATE_FORMAT(order_date, '%Y-%m') AS month, COUNT(DISTINCT order_id) AS order_cnt, SUM(order_amount) AS total_amount FROM orders WHERE order_date >= '2024-01-01' AND order_date GROUP BY user_id, DATE_FORMAT(order_date, '%Y-%m') ) SELECT * FROM monthly_user;
这段代码用 CTE 把订单明细聚合到“用户-月”粒度,相当于先把中间结果变干净,后面做关联时不会导致一对多膨胀。
WITH monthly_user AS ( SELECT user_id, DATE_FORMAT(order_date, '%Y-%m') AS month, COUNT(DISTINCT order_id) AS order_cnt, SUM(order_amount) AS total_amount FROM orders WHERE order_date >= '2024-01-01' AND order_date GROUP BY user_id, DATE_FORMAT(order_date, '%Y-%m') ) SELECT a.month, COUNT(DISTINCT a.user_id) AS month_ buyers, COUNT(DISTINCT b.user_id) AS repeat_buyers, COUNT(DISTINCT b.user_id) / COUNT(DISTINCT a.user_id) AS repeat_rate FROM monthly_user a LEFT JOIN monthly_user b ON a.user_id = b.user_id AND DATE_ADD(a.month, INTERVAL 1 MONTH) = b.month GROUP BY a.month ORDER BY a.month;
第一次用该逻辑计算某电商项目的复购率时,我发现 2024 年 5 月到 6 月的复购率只有 11.7%,低于业务预期。后来我拆到品类维度发现,问题不出在 SQL,而是 6 月有大量新品上线,拉动了新客集中成交,稀释了老客占比。这说明复购率并不总是“越高越好”,要结合新增用户结构一起解读,SQL 只是帮你把数字取准,业务判断要靠人。
同时也观察到另一个问题:如果直接用订单明细做 JOIN 而不是先聚合到用户月粒度,订单量大的用户会产生大量笛卡尔积行,导致重复计算。先做一层聚合可以显著降低中间表大小,这也是“先缩小数据集,再关联”的典型技巧。

学习 SQL 没有放之四海而皆准的路线,我发现它会因为背景差异而不同。接下来我按“零基础转行分析”、“Excel 老手转型”、“已经在用 SQL 但不熟练”三种情况说明我的建议,你在读的时候可以先对照自己属于哪一类。
在真实项目中,我还建议你主动要一个有空值的字段做练习。空值处理是基础语法里最容易出错的部分,很多学习者在练习集上从没见过 NULL,结果在正式工作中一遇到 SUM(amount) 得到 NULL 就慌了。
这类人已经熟悉数据透视表和 VLOOKUP,缺的不是数据感,而是 SQL 的“逻辑视角”。我的建议是:把之前用 Excel 做过的 10 个分析报告,全部挑一个核心指标用 SQL 重新实现一遍。曾经有一个学员跟我说:“我在 Excel 里做同环比很快,但 SQL 里不知道怎么写。”我让他先去理解“去年同期”是怎么定义的,是自然年还是财年,是否包含已退款订单。当他在 Excel 里不假思索的“日期筛选”被翻译成 WHERE order_date BETWEEN ... 时,卡住他的不是语法,而是口径。
如果你已经能独立取数,但感觉自己效率不高,我建议你做两个动作。第一,把过去三个月最常用的 5 个长查询逐行加注释;第二,尝试把其中两个查询重构成 CTE 写法。这样做的目的不是炫技,而是让代码更容易维护。我见过不少老分析师写出来的 SQL 是一长串嵌套子查询,逻辑全挤在几十行里,三个月后自己再看都想不起来。如果你用 CTE 拆成五段,每一段都是一个明确的中间变量,无论是排错还是复用都更快。
我建议初学者先用 SQLite 或 MySQL 本地练习,因为安装简单、数据量可控、报错信息直观。等理解基本逻辑后,再去接触企业级数仓环境,比如 Hive 或某型云端数仓。不要一开始就在一个巨大无比的线上生产库上练习,那样很容易因为超时或者权限问题被打断。

在数据分析场景中,SQL 语法本身是廉价的,真正贵的是判断。你在学习时面临的各种取舍,背后不是“哪种写法更高级”,而是“哪种选择在特定数据环境下更稳定、更可维护”。我在这里给出几种典型取舍判断,并附上我在真实项目中的选择逻辑。
当你需要从两张表中取数时,JOIN 是常见的做法,但并非所有场景都适合 JOIN。如果你只是想为每一行补一个分类属性,JOIN 没问题;如果你需要“在表中找出满足某个条件的最大订单”,子查询或窗口函数往往更清晰。我见过一个查询,为了给订单表附加用户分群信息,某同事把一张 8000 万行的事实用例 JOIN 到一张只有 3 个分群的维表,导致查询跑了 20 分钟。正确做法是 CASE WHEN 直接判断,或先把分群条件压成一个很小的中间表再关联。
GROUP BY 会改变行数,窗口函数不改变行数。如果你需要“既保留明细,又看到总计”,用窗口函数更合适;如果你只关心汇总后的维度,使用 GROUP BY 会比窗口函数减少不必要的开销。以计算“订单金额占整个店铺的百分比”为例,用窗口函数:SUM(amount) OVER(PARTITION BY shop_id) / SUM(amount) OVER(),简洁且准确。用 GROUP BY 就需要多次子查询关联,不直观且易错。
这是我反复强调的原则:JOIN 类型不是技术选择,而是业务口径选择。 你要问的是“是否要保留左表中未匹配上的记录”。比如统计所有用户的订单情况,INNER JOIN 会把没有下单的用户排除掉;LEFT JOIN 则保留所有用户,包括下单金额为 NULL 的用户。用错了 JOIN 类型,数字可能会少 20% 的用户覆盖。初学者最容易犯的错,就是写了 INNER JOIN 却没意识到结果里丢掉的对象。
入门阶段,我建议把 80% 的时间花在“单表过滤聚合 + 多表 JOIN + 子查询/CTE + 窗口函数”四项能力上,而不是去研究各种数据库特性。我在实际带教时看到太多人卡在“把 SQL 学到精通才开始分析”,这想法会拖慢你的成长。实际上只要你敢在生产环境中写一条带 LIMIT 的查询并观察结果,进步速度就会超过反复看十遍教程。
很多人觉得 SQL 只要能跑出结果就行,但我认为这个习惯在未来会付出代价。特别是当你要把一个复杂指标从一个数据表复制到另一个数据表时,可读性决定了你的生产效率。我的建议是:每个查询开头写一段注释,写明取数目的、业务口径、更新频率;每个 CTE 块只做一件事;字段名用英文别名并保持业务含义一致。
也有不少朋友问:现在有很多 BI 工具和 AI 助手,是不是不用学 SQL 了?我的观察是:AI 可以帮你生成 SQL,但它并不了解你的口径,也不了解你的字段。
我给出一个很具体的判断:如果有一天你发现 AI 生成的查询让你无法判断正确与否,那恰恰说明你需要更多 SQL 基础,而不是更少。基础语法的意义,在于让你能够校验 AI 输出的结果,而不只是复制粘贴。

回看这篇文章,我反复强调的不是某个具体函数,而是看待数据的角度。SQL 基础语法真正带给你的,不是“能跑通一段查询”的成就感,而是“面对模糊业务问题时,你敢说我来看看数据”的底气。如果你正准备入门,我的建议是从今天开始,在本地数据库里建一张 100 行的订单表,把 WHERE、GROUP BY、JOIN 这三个动作各练十遍。当你发现自己的注意力不再停留在语法报错上,而是开始思考“这个指标为什么和预期不符”时,你就已经摸到了数据分析的门槛。
下一步,试着用你掌握的语法解决一个真实的业务问题,无论是复购率还是流失率,先取数,再思考。
我刚开始用 SQL 做数据分析时,以为只要把字段名写对,就能得到想要的结果。但实际查询经常出现行数变多、排序不稳定,甚至明明筛选了条件却查不到数据。我想知道这几个基础语法到底应该按什么顺序理解,以及 NULL、重复值和分页这些细节为什么总是出错。
我在带新人做第一轮订单分析时,发现最容易犯的错不是不会写 SELECT,而是没有先明确“查询结果的粒度”。例如,一行代表一个订单,还是一个订单商品明细?如果这个问题没想清楚,后面的筛选、排序和统计都会建立在错误基础上。
推荐先按“取列,筛行,排序,截取”的顺序理解 SQL: 语法作用常见误区我的建议 SELECT选择需要展示的列一开始就使用 SELECT *先明确分析目的,只取必要字段 WHERE过滤原始数据行把聚合条件写进 WHERE先筛选,再考虑分组统计 ORDER BY对结果排序只按一个可能重复的字段排序增加唯一字段作为第二排序条件 LIMIT限制返回行数不排序就直接取前 10 行先 ORDER BY,再 LIMIT 例如,想找最近成交金额最高的 10 笔订单,不应只写 LIMIT 10,而应明确排序逻辑: SELECT order_id, customer_id, paid_amount, paid_at FROM orders WHERE status = 'paid' ORDER BY paid_amount DESC, order_id ASC LIMIT 10;
这里增加 order_id ASC 不是多余的。我的测试中,当多笔订单金额相同时,只按金额排序的结果在不同执行计划或数据写入后可能发生变化;增加唯一字段后,结果才稳定,适合报表复核和分页导出。另一个高频坑是 NULL。
NULL 不是空字符串,也不等于 0,因此 WHERE refund_amount = NULL 永远不能按预期筛出缺失值。应该使用 IS NULL 或 IS NOT NULL
SELECT order_id FROM orders WHERE refund_amount IS NULL;如果查询结果为空,建议按这个顺序排查:先去掉 WHERE 确认表中确实有数据,再逐个恢复筛选条件;接着检查字段类型、大小写、前后空格和 NULL;最后确认时间条件是否包含了正确的边界。很多“SQL 写错”的问题,实际是数据值和自己的假设不一致。
我第一次统计每个渠道的订单量时,把所有条件都写进了 WHERE,后来发现无法筛选“订单量大于 100 的渠道”。我也遇到过 COUNT(*) 比实际用户数大很多的情况,所以想弄清楚 WHERE、GROUP BY、HAVING 和 COUNT 的执行逻辑,以及怎样避免统计口径被重复数据影响。
我在做渠道周报时踩过一个典型坑:原始订单表有 128,436 行,但按渠道统计后,直接使用 COUNT(*) 得出的“用户数”明显偏大。原因是同一个用户可能下过多笔订单,订单行数并不等于用户数。SQL 聚合前,必须先确认你要数的是行、订单、用户,还是去重后的业务实体。
可以把四个语法理解成四个不同阶段: 阶段语法回答的问题 过滤明细行WHERE哪些原始记录进入统计?形成分组GROUP BY按什么维度汇总?计算指标COUNT、SUM、AVG每组要计算什么?过滤分组结果HAVING哪些汇总后的组保留?
例如,统计已支付订单中,成交金额超过 50,000 且订单数至少 100 笔的渠道:
SELECT channel COUNT(*) AS order_count SUM(paid_amount) AS total_paid FROM orders WHERE status = 'paid' GROUP BY channel HAVING COUNT(*) >= 100 AND SUM(paid_amount) > 50000 ORDER BY total_paid DESC;WHERE 发生在分组之前,所以适合过滤订单状态、日期和地区;HAVING 发生在分组之后,所以适合过滤订单数、销售额等聚合结果。把 COUNT(*) >= 100 写进 WHERE,通常会直接报错,或者暴露出对执行顺序的误解。
统计用户数时,我通常会把指标名称写得更具体,避免“数量”这种模糊命名:
SELECT channel COUNT(*) AS order_count COUNT(DISTINCT customer_id) AS paying_user_count SUM(paid_amount) AS total_paid FROM orders WHERE status = 'paid' GROUP BY channel;在一份约 12 万行的订单数据上,某渠道的订单数是 18,420,但去重付费用户只有 7,306,二者相差 2.5 倍左右。如果报表标题写成“用户数”,却使用 COUNT(*),业务方会据此错误判断获客规模。
我的判断标准是:凡是指标名称里出现“用户、客户、商品、订单”,都要明确是否去重,并在 SQL 别名中直接写出来。还要特别注意 NULL。COUNT(*) 会统计所有分组行,而 COUNT(customer_id) 不会统计 customer_id 为 NULL 的行。
两者结果不同并不代表数据库出错,而是统计对象不同。
我在把订单表和用户表关联时,原本预计得到 50 万行,结果 JOIN 后变成了 83 万行,销售额也被重复计算了。后来我才发现关联字段并不唯一。想知道 JOIN 行数变化的规律,以及如何在写 SQL 前判断会不会产生重复数据。
JOIN 最危险的地方不是语法难,而是它会悄悄改变结果集的粒度。我曾经把订单明细表与商品标签表连接,原本想给每条明细补充标签,结果一件商品有 4 个标签,相关明细就被复制成 4 行;后续 SUM 销售额时,金额也同步放大了。
判断 JOIN 是否安全,第一步不是选 INNER 还是 LEFT,而是先确认两张表的关系: 关系典型场景JOIN 后可能的行数风险 一对一订单与唯一订单扩展表通常不变关联键不唯一时会失控 多对一订单与用户表通常不超过左表右表用户键重复会放大 一对多订单与订单明细可能明显增加不能直接按订单汇总金额 多对多商品与多个标签可能成倍增加最容易造成重复统计 INNER JOIN 只保留两边都能匹配的记录,适合分析“有完整关联信息”的对象;
LEFT JOIN 保留左表全部记录,适合检查缺失关联,例如找没有填写用户资料的订单。选择 JOIN 类型,本质上是在选择是否允许主表记录因为关联失败而消失。写 JOIN 前,我会先做两个检查。
第一,检查右表关联键是否唯一:
SELECT user_id, COUNT(*) AS row_count FROM users GROUP BY user_id HAVING COUNT(*) > 1;第二,比较 JOIN 前后的主键数量:
SELECT COUNT(*) AS row_count COUNT(DISTINCT order_id) AS distinct_order_count FROM orders;
SELECT COUNT(*) AS row_count COUNT(DISTINCT o.order_id) AS distinct_order_count FROM orders o LEFT JOIN users u ON o.user_id = u.user_id;如果 JOIN 后总行数上升,但订单去重数不变,说明订单被展开了;如果订单去重数也变化,通常要检查是否用了错误的关联字段或连接条件。实际排查时,不要只看总行数,还要抽取一个被放大的订单,查看它在右表匹配了几条记录。
对于订单和明细这类一对多关系,正确做法通常是先在明细表按订单聚合,再与订单表连接:
SELECT o.order_id o.customer_id d.item_total FROM orders o LEFT JOIN ( SELECT order_id, SUM(quantity * unit_price) AS item_total FROM order_items GROUP BY order_id ) d ON o.order_id = d.order_id;我的经验是,凡是 JOIN 后还要计算 SUM,必须先问一句:“这个金额在 JOIN 后是否仍然只出现一次?”如果答案不确定,就先聚合、去重或建立唯一性检查,而不是直接在最终查询中加 DISTINCT。DISTINCT 可能掩盖重复问题,却无法保证金额、数量等指标没有被放大。
我已经会写 SELECT、WHERE、GROUP BY 和 JOIN,但一遇到“计算每个用户最近一次购买”“找出各部门排名前 3 的员工”就不知道怎么下手。我担心自己只是记住了几个模板,却没有形成解决复杂分析问题的方法,想知道这几类语法在什么场景下分别更合适。
我建议不要把子查询、CTE 和窗口函数当成三个孤立知识点,而要把它们看成“如何把复杂问题拆成中间结果”的不同表达方式。真正重要的不是 SQL 写得短,而是每一步的结果粒度清楚、可以单独验证。
三者可以这样区分: 方式适合场景优点常见问题 子查询一次性的过滤或关联写法直接嵌套过深后难排查 CTE需要拆成多个逻辑步骤可读性和复用性更好误以为一定提升性能 窗口函数保留明细行,同时计算排名、累计值、组内对比避免不必要的自连接PARTITION BY 粒度写错 例如,找出每个用户最近一次已支付订单,先用窗口函数给每个用户的订单排序,再筛选第一行: WITH ranked_orders AS ( SELECT order_id customer_id paid_at paid_amount ROW_NUMBER() OVER ( PARTITION BY customer_id ORDER BY paid_at DESC, order_id DESC ) AS rn FROM orders WHERE status = 'paid' ) SELECT order_id, customer_id, paid_at, paid_amount FROM ranked_orders WHERE rn = 1;
这里 PARTITION BY customer_id 表示每个用户重新编号,ORDER BY paid_at DESC 表示最新订单排在前面。增加 order_id 作为并列时的第二排序条件,是为了避免同一用户在时间完全相同时返回不稳定结果。
如果目标是“每个部门薪资最高的 3 名员工”,窗口函数同样比自连接更直观: WITH ranked_staff AS ( SELECT employee_id department_id salary DENSE_RANK() OVER ( PARTITION BY department_id ORDER BY salary DESC ) AS salary_rank FROM employees ) SELECT employee_id, department_id, salary FROM ranked_staff WHERE salary_rank ROW_NUMBER() 会严格返回每组固定数量的行;
RANK() 遇到并列会跳号;DENSE_RANK() 遇到并列不跳号。比如薪资排名为 100、90、90、80,三者的结果分别是:ROW_NUMBER 为 1、2、3、4,RANK 为 1、2、2、4,DENSE_RANK 为 1、2、2、3。选错函数,结果可能比语法错误更难察觉。
我的练习方法是先用普通聚合写出“每组一个结果”,再问自己是否需要保留明细行。如果需要保留每笔订单,同时展示用户累计消费、订单序号或渠道占比,就优先考虑窗口函数;如果只需要每个渠道一行,GROUP BY 通常更合适。最后,CTE 主要解决的是逻辑组织,不应默认它会让查询更快。
实际性能还要看数据库优化器、数据量、索引和执行计划。学习阶段可以把复杂 SQL 拆成三步:先过滤有效数据,再生成中间指标,最后输出结果。每一步都单独运行并核对行数,通常比一次写完再凭感觉排错更高效。


上一篇:数据分析社招求职,跳槽准备全指南
读者评论
文章把SQL学习重点放在数据粒度、过滤顺序和关联逻辑上,这个角度比较实用。尤其是用COUNT(*)与COUNT(DISTINCT)核对重复数据,适合初学者建立结果校验习惯。
从Excel转向SQL的场景描述很有代入感,但Excel与SQL的耗时差异会受索引、硬件和数据量影响,文中的效率数据更适合作为个案参考,不能直接套用到所有环境。
WHERE、GROUP BY、HAVING和窗口函数的讲解方向清晰,适合入门复习。若能补充完整可运行的示例数据和JOIN后数据膨胀的演示,读者会更容易验证并掌握这些方法。