ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

数据分析师SQL要学到什么程度?掌握这些技能就够了

数据分析师SQL要学到什么程度?掌握这些技能就够了 总有朋友私信问我数据分析师的 SQL 到底要学到什么程度是不是得像 DBA 那样把执行计划、锁、日志恢复都背下来才算合格最近又有几位准备转行数据分析的朋友拿同样的问题来问我我觉得这个问题不是一两句话能说清的干脆写一篇完整的答案。我的结论很明确数据分析师学 SQL不需要以 DBA 的标准要求自己但也不能停留在“能跑通就行”的水平。SQL 是工具是语言是表达业务问题的方式。学到什么程度才够不由 SQL 本身决定而由你每天要回答什么问题、解决什么业务难题决定。这篇文章我会从能力定位、技能清单、查询规范、慢 SQL 优化实战、面试考察点五个角度拆开讲最后给出我对“这个程度”的明确判断。1. 先想清楚数据分析师的 SQL学到“够用”不等于“将就”1.1 你的定位是“用数的人”不是“管数的人”我在团队里经常看到两种极端。一种人把大量精力花在研究数据库底层原理、事务机制、锁、主从复制上业务问题反而处理得很慢代码写得又长又绕另一种人只会在表里做简单的 SELECT拿到什么用什么结果算错了也不知道怎么排查。这两种做法都偏了。数据分析师的工作重心是“用数据回答问题”SQL 是手段不是目的。围绕 SQL 和数据分析师这两个关键词首先要分清角色定位DBA 管数负责性能、备份、权限、容灾数仓工程师建模负责 ETL、分层、调度、规范数据分析师用数负责取数、验证假设、输出结论。三者技能栈当然有交叠但深度方向完全不同。你不能要求一个分析师把数据库内核研究透同样一个只会写简单 SELECT 的分析师也走不远因为业务问题稍微复杂一点他就被卡住了。1.2 把 SQL 能力拆成四个层次查得对、查得快、查得省、查得稳我习惯把数据分析师的 SQL 能力分成四层这个框架也经常用来给团队新人做自测。查得对是最低要求。结果不能错口径不能含糊。同样的“用户数”在不同表里定义不同用 COUNT(DISTINCT user_id) 还是 COUNT(user_id) 结果差很远错了自己都发现不了这是最要命的。查得快是效率要求。一个查询要跑半小时和跑 3 秒对分析节奏的影响完全不同。业务方上午问的问题你下午才给结果决策窗口早就过去了。查得省是成本意识。在大数据平台上全表扫描一次就要烧掉不少计算资源。能先过滤再关联、能用分区裁剪就绝不拖家带口扫描全年数据这不是 DBA 才需要懂的每一个写 SQL 的人都该有这个意识。查得稳是工程素养。你写的查询三个月后还能被自己和同事看懂逻辑清晰、可复用、经得起 review。临时表满天飞、字段含义全靠猜的 SQL今天能跑明天换个需求就变成了定时炸弹。1.3 对照检查你现在卡在第几层下面这组问题可以帮你快速定位自己的阶段。如果你能独立写多表关联和聚合统计但一遇到去重场景就怀疑人生说明你还在第二层附近如果你写窗口函数需要现查语法遇到性能问题只会加索引甚至不知道要不要加那你距离“写得好”还有一段路如果你已经能做到拿到需求先想口径、再想表结构、最后动手写查询而且经常思考“这个 join 会不会让行数膨胀”那你已经比大多数人都强了。我见过不少干了三五年的分析师SQL 水平其实一直停留在“会写”的阶段。他们不缺练习量缺的是对 SQL 背后执行逻辑的理解。这个问题后面第三章会展开这里先记住一句话数据分析师的 SQL 进阶不是背越来越多的函数而是把执行逻辑、业务逻辑和代码逻辑三者对齐。2. 必修技能清单数据分析师日常最常用的 SQL 能力地图2.1 基础查询与聚合连书写都要形成肌肉记忆的部分基础查询的门槛很低但“熟练”和“看过教程”完全不是一回事。一个合格的数据分析师写下面这段查询应该像喝水一样自然不需要查文档SELECT city, COUNT(DISTINCT user_id) AS uv, SUM(amount) AS gmv, AVG(amount) AS avg_order_amount FROM orders WHERE created_at 2024-01-01 AND created_at 2024-02-01 AND status paid GROUP BY city HAVING SUM(amount) 100000 ORDER BY gmv DESC LIMIT 20;这段代码覆盖了 SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT 这些最核心的子句。注意 HAVING 和 WHERE 的区别WHERE 在分组前过滤行HAVING 在分组后过滤组。如果把SUM(amount) 100000写进 WHERESQL 直接报错因为 WHERE 执行时聚合结果还不存在。很多新手在这里反复踩坑其实就是没理解逻辑执行顺序。另外要养成一个习惯日期过滤尽量用created_at 2024-01-01 AND created_at 2024-02-01这种左闭右开区间而不是BETWEEN 2024-01-01 AND 2024-01-31。原因很简单如果数据里混着 1 月 31 日 23:59:59 之后的记录BETWEEN 可能把 2 月 1 号凌晨的数据也带进来。口径偏差往往就是这么产生的。2.2 多表关联学会判断“该不该关联”实际业务里数据几乎都是拆开的用户表、订单表、商品表、支付表各管一摊要拿完整视图就必须 JOIN。数据分析师最常见的关联是事实表和维度表比如订单表关联用户表拿城市、性别、年龄段关联商品表拿类目关联商家表拿地域。写 JOIN 之前先问自己三个问题主表是谁粒度是什么关联后行数会不会变SELECT u.city, COUNT(DISTINCT o.order_id) AS order_cnt, SUM(o.amount) AS gmv FROM orders o LEFT JOIN users u ON o.user_id u.user_id WHERE o.created_at 2024-01-01 AND o.created_at 2024-02-01 GROUP BY u.city;这里主表是订单表粒度是订单。如果用户表里有一条订单对应多行用户数据比如用户在多个城市都注册过关联后订单就会膨胀COUNT(DISTINCT order_id) 还能救一下SUM(amount) 就被翻倍了。我处理过一个真实案例订单表关联购物车明细表时没注意多对多关系GMV 足足虚增了 3 倍最后花了整整两天追溯才定位到问题。所以我的习惯是复杂 join 之前先分别查两张表的行数和主键唯一性确认关联不产生膨胀后再写完整查询。这个步骤多花三十秒能省下后面排查错误的一小时。2.3 去重与空值每个分析师都绕不开的两个动作去重这个词在 SQL 学习里出现频率很高但很多人只会 DISTINCT。DISTINCT 适合简单去重比如COUNT(DISTINCT user_id)统计独立用户数但它只能整行去重不能“按业务键保留一条”。更常见也更实用的做法是用窗口函数配合分区去重。比如客户表里同一个用户在多个渠道重复注册你想按注册时间最早的那条保留SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY created_at ASC ) AS rn FROM customers ) t WHERE rn 1;这是数据分析师最该熟练掌握的去重姿势之一。它比 DISTINCT 灵活也比先 GROUP BY 再回表查询更可控。面试题里考“去重”时面试官真正想看的就是你能不能写出上面这种按业务规则保留一条的查询而不是只会 DISTINCT。空值处理也是日常高频操作。数据库里 NULL 和空字符串不是一回事NULL 参与计算会污染结果SUM(amount)遇到 NULL 不会报错但会跳过如果业务上需要把 NULL 当作 0就要用 COALESCESELECT user_id, COALESCE(SUM(amount), 0) AS total_amount FROM orders GROUP BY user_id;清洗数据时常见的情况是字段里既有 NULL 又有空字符串还有字符串 NULL需要分情况统一。一个比较完整的处理方式是先看数据分布再用 CASE WHEN 把各种“伪空值”统一成标准 NULL最后用 COALESCE 给默认值。很多数据分析师直接在查询里硬编码结果业务口径一变SQL 全要重写。2.4 窗口函数从“能算”到“好算”的分水岭窗口函数是数据分析师 SQL 能力的分水岭。它解决的是“每组内排名、累计、对比”这一类问题不写窗口函数也能算但代码会又长又笨性能也差。排名类最常用三个ROW_NUMBER、RANK、DENSE_RANK。它们的区别要刻在脑子里ROW_NUMBER 即使并列也强制分出 1、2、3RANK 有并列会跳号1、1、3DENSE_RANK 有并列不跳号1、1、2。TopN 场景基本用 ROW_NUMBER比如取每个城市 GMV 最高的订单SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY city ORDER BY amount DESC ) AS rn FROM orders ) t WHERE rn 3;聚合类窗口函数可以算累计值。比如每个用户按时间累加的消费金额用SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at)就能拿到不需要自关联。偏移类函数 LAG 和 LEAD 更是同比环比、留存分析的神器。比如算每个用户本月消费较上月的差额SELECT user_id, month, amount, amount - LAG(amount, 1) OVER ( PARTITION BY user_id ORDER BY month ) AS mom_change FROM monthly_amount;窗口函数最大的价值不只是写法优雅而是执行效率高。它通常只需要一次扫描而传统写法往往要多次子查询和自连接。对于千万级以上的表性能差距非常明显。从热词来看很多人都在搜“sql窗口函数”说明大家已经意识到这是进阶必学项我建议直接把它当成和 SELECT 一样的基础能力来练。3. 从“会写”到“写得好”这是拉开差距的地方3.1 理解 SQL 的逻辑执行顺序你就成功了一半很多取数错误和优化失败根源都在于没搞懂 SQL 的逻辑执行顺序。书写顺序是 SELECT、FROM、WHERE但真正的执行逻辑顺序完全不是这样。标准逻辑顺序大致是先 FROM/JOIN 拿到基础数据集再 WHERE 过滤行然后 GROUP BY 分组接着 HAVING 过滤组之后才轮到 SELECT 计算和投影最后才是 ORDER BY 和 LIMIT。这个顺序解释了为什么 WHERE 里不能引用 SELECT 中定义的别名因为 SELECT 还没执行也解释了为什么 WHERE 过滤能显著减少 GROUP BY 的数据量所以好习惯是先过滤再聚合而不是先聚合再过滤。优化慢 SQL 时第一条思路永远是“能不能让 WHERE 干掉更多行”理解了执行顺序你自然知道为什么。3.2 写查询时最常见的四个性能杀手第一个杀手是 SELECT *。数据分析师贪图省事直接把全部字段拉出来实际上可能只需要三列。多余的字段既增加 IO 和网络传输在宽表场景下还可能涉及不必要的回表读取。正确写法是显式列出需要的字段。第二个杀手是在索引列上套函数。比如WHERE DATE(created_at) 2024-01-01表面上是在过滤日期实际上 DATE 函数把每行数据都转换了一遍索引直接失效数据库只能全表扫描。正确写法是created_at 2024-01-01 00:00:00 AND created_at 2024-01-02 00:00:00。简单改写性能差几十倍很常见。第三个杀手是隐式类型转换。字符串字段和数字比较比如WHERE order_no 123456如果 order_no 是 VARCHAR数据库会把每一行的 order_no 都转成数字再比较索引同样失效。你以为是等值查询其实变成了全字段扫描。第四个杀手是前置模糊匹配LIKE %关键词。只要通配符出现在字符串开头B 树索引就用不上只能从头扫到尾。如果确实需要搜索类需求应该考虑搜索引擎或全文索引而不是在 SQL 里硬查。3.3 懂一点索引原理真的不吃亏索引到底是个什么东西往简单说它就是表的目录。没有索引的查询像在一本没有目录的书里从头翻到尾有索引则像查了页码直接翻到那一页。数据库里最常见的 B 树索引支持等值查询和范围查询也支持 ORDER BY 排序加速。数据分析师不需要会建索引的原理推导但至少要知道几点主键会默认带索引WHERE 条件里的列有索引会快很多联合索引遵守最左前缀原则(city, created_at)这样的联合索引能加速WHERE city 上海 AND created_at ...但单独查created_at用不上。当一条查询从秒级优化到毫秒级、发现瓶颈就在索引时你就会感谢当初多看了一眼 B 树。不过也要提醒一句索引不是越多越好。每个索引都会增加写入开销和存储成本还会干扰优化器的选择。数据分析师在分析库上写只读查询遇到慢 SQL 时可以提建议、和 DBA 沟通但不要自己在一个生产业务库上随手加索引这是权限边界问题。4. 慢 SQL 优化实战一条跑了 6 分钟的查询怎么缩到 8 秒4.1 现场还原一次典型的“先写再说”某个业务周会上运营临时要“最近 30 天各城市 GMV 和下单用户数”。我正准备直接从订单大表里跑数发现有同事的 SQL 跑了 6 分多钟还没出结果。我先让他把当前 SQL 发出来一眼就看出了好几个问题SELECT a.city, DATE_FORMAT(a.created_at, %Y-%m-%d) AS dt, SUM(a.amount) AS gmv, COUNT(DISTINCT a.user_id) AS uv FROM ( SELECT * FROM order_info WHERE DATE(created_at) BETWEEN 2024-06-01 AND 2024-06-30 ) a LEFT JOIN dim_city b ON a.city_id b.city_id GROUP BY a.city, dt;这个查询踩了前面说的好几个坑子查询里直接 SELECT *把几十个字段全拉了一遍WHERE 条件用 DATE(created_at) 包住列索引失效GROUP BY 里既有城市又有日期排序和分组压力都很大而且 order_info 是一张 5000 万行级别的订单大表。4.2 用 EXPLAIN 定位病根而不是靠猜当查询慢到不可接受不要靠猜。MySQL 里直接在查询前加 EXPLAINSQL Server 里看执行计划都能看见数据库准备怎么执行。我让同事在子查询上跑了 EXPLAIN关键信息大概是这样列名值说明typeALL全表扫描最差的访问类型rows50000000预估要扫 5000 万行extraUsing temporary; Using filesort分组排序产生了临时表和文件排序看到 typeALL 和 rows5000 万基本就能判断问题出在扫描范围太大。DATE 函数包裹索引列导致索引用不上SELECT * 又把整行数据都拖进内存后面还叠加了一次大表的 GROUP BY。每一步都在放大成本。4.3 逐项优化后的效果6 分钟到 8 秒优化步骤按收益从大到小排第一步改写日期过滤区间让索引能用上。把WHERE DATE(created_at) BETWEEN ...换成WHERE created_at 2024-06-01 00:00:00 AND created_at 2024-07-01 00:00:00第二步干掉 SELECT *只保留需要的字段 city_id、user_id、amount、created_at。这一步减少大量 IO。第三步去掉子查询嵌套直接在主查询里过滤。子查询在这里没有任何必要反而让优化器更难做下推。第四步维度表关联要保住。dim_city 本来就不大LEFT JOIN 的开销可以接受但关键是大表先过滤、先聚合之后再关联。改完之后的 SQL 大概是这样的SELECT c.city, DATE_FORMAT(o.created_at, %Y-%m-%d) AS dt, SUM(o.amount) AS gmv, COUNT(DISTINCT o.user_id) AS uv FROM order_info o LEFT JOIN dim_city c ON o.city_id c.city_id WHERE o.created_at 2024-06-01 00:00:00 AND o.created_at 2024-07-01 00:00:00 GROUP BY c.city, DATE_FORMAT(o.created_at, %Y-%m-%d);在同量级数据上跑了一遍耗时从 6 分多钟降到 8 秒左右。没有改任何表结构只是换了一种写法差距接近 45 倍。这就是“查得对”和“查得快”之间的实际距离。但这还没到最优状态。如果 city_id 和 created_at 上有一个(city_id, created_at)联合索引扫描行数还能进一步下降。如果哪天数据量再翻几倍8 秒也会变成 80 秒这时候就要考虑后面的手段。4.4 数据再大一级怎么办中间表、分区与并行数据量到了亿级以上单纯调 SQL 语法已经不够用。我见过最有效的三板斧也是数据分析师应该了解的前瞻方案。第一板斧是预聚合中间表。把明细层预先按城市、日期、渠道等维度汇总成结果表查询直接查汇总表。代价是数据有延迟适合日报周报这类固定分析不适合即席查询。第二板斧是分区表。按日期或按月分区查询条件带上分区字段数据库直接跳过不相关的分区文件扫描量能缩小几个数量级。第三板斧是并行查询和分布式计算。热词里频繁出现的 Spark SQL、并行 SQL 优化本质都是把一个大查询拆成多个子任务分发到多个节点上同时执行。只要 SQL 能表达清楚逻辑底层引擎帮你并行你要关心的只是如何减少 shuffle数据重分布比如避免大表 JOIN 大表、避免用 DISTINCT 对超大结果集去重。一个数据分析师如果能把中间表思维和分区裁剪用好在大数据平台上的效率会明显高于只会对着一张原始明细表硬查的人。这也是为什么现在数据分析师岗位经常要求 Spark SQL 或 Hive SQL 的原因语法大同小异但优化的思维方式一脉相承。5. 面试考察点与工具链差异把力气花在刀刃上5.1 SQL 面试题背后真正想看的东西搜“sql面试题”的人很多面试题的类型也高度集中。最常见的几类我列一下以及它们背后的考察意图去重类表面考 DISTINCT 和 ROW_NUMBER实际看你能不能理解“按业务键保留一条”这类真实需求。TopN 类几乎必考窗口函数 RANK 系实际看你对分组排名的应用是否熟练。连续登录类用日期减去行号分组连续 N 天登录一眼识别实际考的是日期函数和窗口函数组合的灵活度。留存率、漏斗转化类常见做法是按日期分组自关联或 LAG 对比实际考你对业务指标定义是否清晰。同比环比类LAG 函数一步到位实际看你会不会做时间维度的对比分析。拿连续登录来举例核心思路是先算每个用户登录日期减去连续编号的差值差值一致说明日期连续SELECT user_id, DATE_SUB(login_date, INTERVAL rn DAY) AS grp FROM ( SELECT user_id, login_date, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY login_date ) AS rn FROM user_login_log ) t GROUP BY user_id, grp HAVING COUNT(*) 3;这道题的经典之处在于它考察的不是某个孤立的函数而是窗口函数、日期函数和分组聚合的组合应用。面试官真正想看的是你能不能把学过的技能在真实问题里串起来而不是背一个答案。5.2 MySQL、SQL Server、Spark SQL 等工具链不要混为一谈数据分析师日常工作至少会遇到三套 SQL 环境最常见的是 MySQL大量中小互联网公司的分析库和业务库都用它很多传统行业和金融场景用的是 SQL Server语法和 MySQL 有不少细节差异比如分页用 OFFSET FETCH 而不是 LIMIT日期函数用 DATEADD 而不是 DATE_SUB大数据平台则是 Hive SQL 或 Spark SQL更强调分区和并行执行计划。差异点MySQLSQL ServerSpark SQL / Hive分页LIMIT offset, countOFFSET FETCHLIMIT 支持较好日期加减DATE_SUB / DATE_ADDDATEADDDATE_ADD 语法略有不同窗口函数8.0 支持支持完善支持执行计划EXPLAIN图形执行计划Spark UI / Explain 输出工具层面Navicat 这类客户端软件只是一个编辑器加查看器连接数据库后无非是写 SQL、看结果它的价值在于让你更舒服地调试而不是替你掌握 SQL。有人花大量时间研究某个客户端的高级功能我反而建议把这个时间花在理解表结构和业务口径上收益大得多。还要注意一点Navicat 连接 SQL Server 时常遇到的密码过期、实例无法连接等问题多数是服务配置或认证方式问题不要二话不说就去重装数据库先检查网络、端口、服务是否启动这个排查顺序能帮你省下大量时间。关于安全性也要单独说一句写 SQL 时不要用字符串拼接的方式把外部参数塞进查询里这会给 sql 注入留出可乘之机。正确做法是使用参数化查询或预编译语句。数据分析师虽然不直接负责应用安全但写取数逻辑、临时查询脚本时养成参数化的习惯是基本的职业素养。5.3 能力边界哪些不用学哪些必须学我不是劝你把所有时间都砸在 SQL 上。数据分析师还有业务理解、指标体系、可视化、统计建模一堆事要干。SQL 学习要有边界。不用深入的方向包括事务隔离级别、锁机制、主从复制、数据库备份恢复、存储引擎内部实现。这些是 DBA 和内核工程师的主场分析岗位遇到相关问题知道找谁帮忙就行。但有三样必须持续学第一是数据模型思维拿到一个需求先能画出涉及哪些表、表之间的关系是什么第二是口径管理能力同样的指标在不同部门定义不同你要能说清楚自己的口径和边界第三是输出能力和业务翻译能力SQL 跑出来的只是一张表你得把它讲成业务方能听懂的一句话结论。从面试题到实际工作你会发现 SQL 永远在被考察但永远不是终点。真正值钱的是你用 SQL 回答了什么问题而不是你会写多少条花式语句。最后说一点我这些年带人时的真实感受。不少人把 SQL 学得挺深函数背得滚瓜烂熟但一到需求场景就不知道从哪张表开始也有不少人基础一般可他很清楚业务方要什么知道去哪个库、看哪张表、用什么粒度去算反而产出又快又稳。如果你还在纠结“学到什么程度”我的建议很直接先把取数练到不假思索再练窗口函数和基本调优然后带着业务问题去写 SQL。写着写着你会发现能够准确接住业务、把口径对齐、扛住面试的场景追问就是当前阶段最好的程度。
返回列表