这是一篇关于数据分析方向系统学习 SQL 的完整教程,语言清晰、结构完整,全文超过 6000 字,包含环境搭建、SQL 核心语法、数据清洗实战、项目案例、性能优化、常见面试题等模块,可直接用于 CSDN 技术博客发布。
1. 为什么数据分析师要系统学习 SQL?
在数据分析这条路上,很多人存在一个误区:觉得 SQL 只是“查数据”的工具,会用SELECT、WHERE、JOIN就够了。但实际上,SQL 是数据分析师接触数据、理解业务、交付结论的核心桥梁。不管是做报表、搭看板、写取数脚本,还是做数据清洗、特征提取、AB 实验分析,SQL 都贯穿始终。
你可能会问:现在 Python、Pandas、Excel 这么强大,为什么还要学 SQL?
因为无论在互联网公司还是传统企业,数据通常不会以 Excel 文件形式躺在你电脑里。它们存放在数据库系统中(MySQL、Oracle、SQL Server、PostgreSQL 等),数据分析师需要先用 SQL 把数据从库里取出来、处理好,然后再交给 Python 或 BI 工具做进一步分析。如果 SQL 不熟练,你连数据都拿不到,后续分析能力再强也派不上用场。
另外,SQL 的语法相对简单,但它的执行逻辑、性能优化、数据清洗能力,远比你想象的复杂。一个能写好 SQL 的分析师,和一个只会“select *”的分析师,工作效率可以相差数倍。这也是为什么数据分析面试中,SQL 永远是不可或缺的考察环节。
本文是一套从零基础到项目落地的 SQL 学习路线,围绕“数据分析”这个真实用途展开。你会掌握:
- 如何搭建本地 SQL 练习环境;
- 建库建表与数据导入;
- 查询、过滤、分组、排序、连接、子查询等核心语法;
- 用 SQL 做数据清洗(去重、空值处理、字段转换);
- 用 SQL 完成一个完整的数据分析项目;
- SQL 性能优化基础与常见面试题;
- 数据分析师学习 SQL 的避坑建议。
无论你是转行数据分析的零基础新手,还是已经在做数据相关工作但 SQL 不够系统的开发者,这篇文章都值得收藏。
2. 环境搭建:本地 SQL 练习环境准备
SQL 是一门需要大量动手实践的技能。只看不练、光学不写,很难真正掌握。所以我们第一步先把本地环境搭好。
2.1 选择数据库:MySQL 8.0
数据分析工作中,MySQL 是使用率最高的数据库之一,学习资料多、安装简单、生态完善。本文所有案例都基于 MySQL 8.0 编写,你可以按下面方式安装:
- Windows 用户:下载 MySQL Installer for Windows,选择 Server only 或 Developer Default,安装完成后设置 root 密码。
- macOS 用户:可以使用 Homebrew 安装,命令为
brew install mysql。 - Linux 用户:以 Ubuntu 为例,命令为
sudo apt install mysql-server。
如果你不想在本机安装,也可以使用 Docker 快速启动一个 MySQL 实例:
docker run --name mysql-sql-study \ -e MYSQL_ROOT_PASSWORD=123456 \ -p 3306:3306 \ -d mysql:8.0注意:如果你使用的是 SQL Server、Oracle 或 PostgreSQL,语法上会有少量差异,但核心逻辑和思路是相通的。本文以 MySQL 为教学环境,重点讲通用分析思路。
2.2 安装可视化工具:DBeaver 或 Navicat
数据分析师每天都要和数据库打交道,纯命令行操作效率偏低。推荐安装一款数据库可视化工具:
- DBeaver:免费开源,支持 MySQL、PostgreSQL、Oracle、SQL Server 等多种数据库,社区版足够日常使用。
- Navicat:界面美观,功能强大,但需要付费。
- DataGrip:JetBrains 出品,适合熟悉 IntelliJ 生态的开发者。
本文示例使用 DBeaver。安装后新建 MySQL 连接,填写主机、端口、用户名、密码,测试连接成功后即可开始练习。
2.3 准备学习数据集
为了让你能跟着文章实操,我们直接使用一套电商订单数据集,包含三张表:
users:用户表orders:订单表products:商品表
这套数据是模拟的,但结构与真实业务非常接近,覆盖了用户、订单、商品三个核心维度。
-- 创建数据库 CREATE DATABASE IF NOT EXISTS sql_analysis DEFAULT CHARACTER SET utf8mb4; USE sql_analysis; -- 用户表 CREATE TABLE users ( user_id INT PRIMARY KEY, user_name VARCHAR(50), gender VARCHAR(10), age INT, city VARCHAR(50), register_date DATE ); -- 商品表 CREATE TABLE products ( product_id INT PRIMARY KEY, product_name VARCHAR(100), category VARCHAR(50), price DECIMAL(10,2) ); -- 订单表 CREATE TABLE orders ( order_id INT PRIMARY KEY, user_id INT, product_id INT, order_date DATE, quantity INT, amount DECIMAL(10,2), status VARCHAR(20) );建表完成后,我们往三张表中插入一些模拟数据:
INSERT INTO users VALUES (1, '张三', '男', 25, '北京', '2023-01-15'), (2, '李四', '女', 30, '上海', '2023-02-20'), (3, '王五', '男', 28, '广州', '2023-03-10'), (4, '赵六', '女', 22, '深圳', '2023-04-05'), (5, '孙七', '男', 35, '杭州', '2023-05-12'); INSERT INTO products VALUES (101, '手机', '数码', 2999.00), (102, '笔记本电脑', '数码', 5999.00), (103, 'T恤', '服饰', 99.00), (104, '运动鞋', '服饰', 399.00), (105, '面包机', '家电', 199.00); INSERT INTO orders VALUES (1001, 1, 101, '2023-06-01', 1, 2999.00, '已完成'), (1002, 1, 103, '2023-06-05', 2, 198.00, '已完成'), (1003, 2, 102, '2023-06-08', 1, 5999.00, '已完成'), (1004, 3, 104, '2023-06-10', 1, 399.00, '已完成'), (1005, 4, 101, '2023-06-12', 1, 2999.00, '已完成'), (1006, 5, 105, '2023-06-15', 1, 199.00, '已完成'), (1007, 2, 104, '2023-06-18', 2, 798.00, '已完成'), (1008, 3, 103, '2023-06-20', 3, 297.00, '已完成'), (1009, 1, 102, '2023-06-22', 1, 5999.00, '已完成'), (1010, 4, 105, '2023-06-25', 2, 398.00, '已完成'), (1011, 5, 101, '2023-06-28', 1, 2999.00, '已完成'), (1012, 2, 103, '2023-07-01', 1, 99.00, '已完成');3. SQL 核心语法:从入门到熟练
环境准备好之后,我们从最基础的查询语法开始,逐步过渡到复杂分析场景。
3.1 SELECT 基础查询
SELECT是 SQL 中使用频率最高的语句,用于从表中查询数据。
-- 查询所有用户 SELECT * FROM users; -- 查询指定字段 SELECT user_id, user_name, city FROM users; -- 查询并去重 SELECT DISTINCT city FROM users; -- 查询并排序 SELECT user_id, user_name, age FROM users ORDER BY age DESC;这里需要注意,SELECT *在开发调试时很方便,但在生产环境或数据量很大的表中,尽量指定需要的字段,避免不必要的 IO 开销。
3.2 WHERE 条件过滤
数据分析中,我们很少会一次性查询全表数据,更多是通过WHERE条件筛选出目标数据。
-- 查询年龄大于 25 的用户 SELECT user_id, user_name, age FROM users WHERE age > 25; -- 查询城市为北京或上海的用户 SELECT user_id, user_name, city FROM users WHERE city IN ('北京', '上海'); -- 查询 2023 年 6 月 1 日之后的订单 SELECT order_id, order_date, amount FROM orders WHERE order_date >= '2023-06-01'; -- 查询金额在 100 到 1000 之间的订单 SELECT order_id, amount FROM orders WHERE amount BETWEEN 100 AND 1000;WHERE的常用操作符包括:
- 比较运算符:
=,>,<,>=,<=,<> - 逻辑运算符:
AND,OR,NOT - 范围判断:
BETWEEN ... AND ... - 集合判断:
IN (...) - 模糊匹配:
LIKE
3.3 聚合函数与 GROUP BY 分组统计
分组聚合是数据分析中最核心的操作。比如,我们想看每个城市的用户数量、每个商品类别的总销售额,都需要用到GROUP BY。
-- 统计每个城市的用户数 SELECT city, COUNT(*) AS user_cnt FROM users GROUP BY city; -- 统计每个用户的订单总金额 SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id; -- 统计每个商品类别的订单数量和平均金额 SELECT p.category, COUNT(o.order_id) AS order_cnt, AVG(o.amount) AS avg_amount FROM orders o JOIN products p ON o.product_id = p.product_id GROUP BY p.category;常用的聚合函数有:
| 函数 | 作用 |
|---|---|
| COUNT(*) | 统计行数 |
| SUM(字段) | 求和 |
| AVG(字段) | 求平均值 |
| MAX(字段) | 求最大值 |
| MIN(字段) | 求最小值 |
使用GROUP BY时有几个易错点:
SELECT中出现的非聚合字段,必须包含在GROUP BY中;WHERE过滤在分组之前执行,分组之后的过滤要用HAVING。
-- 过滤订单总额大于 5000 的用户 SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id HAVING SUM(amount) > 5000;3.4 JOIN 多表连接
数据分析中,数据很少存在于一张表中,通常需要把用户表、订单表、商品表等连接起来。SQL 提供了几种 JOIN 类型:
INNER JOIN:只返回两边都匹配的记录LEFT JOIN:返回左表全部记录,右表无匹配则为 NULLRIGHT JOIN:返回右表全部记录,左表无匹配则为 NULLFULL JOIN:返回两边全部记录(MySQL 不直接支持,可用 UNION 模拟)
-- 订单表和用户表连接,查询每个订单的用户信息 SELECT o.order_id, u.user_name, u.city, o.amount FROM orders o INNER JOIN users u ON o.user_id = u.user_id; -- 查询每个用户及其订单信息(列出所有用户) SELECT u.user_id, u.user_name, o.order_id, o.amount FROM users u LEFT JOIN orders o ON u.user_id = o.user_id;实际分析中有一个常见需求:找出“没有下过单的用户”。用LEFT JOIN加IS NULL判断就能实现。
SELECT u.user_id, u.user_name FROM users u LEFT JOIN orders o ON u.user_id = o.user_id WHERE o.order_id IS NULL;3.5 子查询与临时表
子查询是指嵌套在SELECT、WHERE、FROM中的查询语句,适用于需要多步计算的分析场景。
-- 查询订单金额高于平均金额的订单 SELECT order_id, amount FROM orders WHERE amount > (SELECT AVG(amount) FROM orders); -- 查询每个用户最近一次下单日期 SELECT user_id, MAX(order_date) AS last_order_date FROM orders GROUP BY user_id;子查询也可以放在FROM中,作为一个“临时表”继续参与计算:
-- 查询用户订单总金额排名前 3 的用户 SELECT user_id, total_amount FROM ( SELECT user_id, SUM(amount) AS total_amount FROM orders GROUP BY user_id ) t ORDER BY total_amount DESC LIMIT 3;3.6 CASE WHEN 条件逻辑
数据分析中经常需要对数据进行分类、打标。比如判断用户是否高价值用户、把金额分成不同区间、把日期转成星期几等,CASE WHEN是最常用的条件表达式。
SELECT order_id, amount, CASE WHEN amount >= 5000 THEN '高金额' WHEN amount >= 1000 THEN '中金额' ELSE '低金额' END AS amount_level FROM orders;在分组统计中使用CASE WHEN可以做到“行转列”效果:
-- 统计每个城市的男女用户数 SELECT city, SUM(CASE WHEN gender = '男' THEN 1 ELSE 0 END) AS male_cnt, SUM(CASE WHEN gender = '女' THEN 1 ELSE 0 END) AS female_cnt FROM users GROUP BY city;4. 用 SQL 做数据分析:七大数据清洗场景实战
数据分析中有一句话叫“Garbage in, garbage out”。数据清洗通常占据分析工作 60% 以上的时间,而 SQL 是完成数据清洗最高效的语言之一。下面我们来看七个最常用的清洗场景。
4.1 去重:DISTINCT 与 ROW_NUMBER
数据重复是脏数据中最常见的问题。比如用户重复注册、订单重复记录、明细表重复关联等。
最简单的去重方式是使用DISTINCT:
SELECT DISTINCT user_id, product_id FROM orders;但DISTINCT只能去“完全重复”的行。如果我们要保留重复数据中的一条(比如每个用户保留最新一条记录),就需要窗口函数ROW_NUMBER()。
-- 为每个用户按订单日期排序,并保留最新一条 SELECT order_id, user_id, order_date FROM ( SELECT order_id, user_id, order_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) AS rn FROM orders ) t WHERE rn = 1;注意:MySQL 8.0 及以上才支持窗口函数。如果使用 MySQL 5.7,可以用关联子查询或分组取最大日期后再关联实现。
4.2 空值处理:IS NULL 与 COALESCE
空值在数据分析中很常见。用户未填写性别、订单没有商品分类、金额字段缺失等,都会导致空值。处理空值前,先分析空值的含义:是“没有”还是“未知”,决定是删除还是填充。
-- 查询性别为空或年龄为空的数据 SELECT * FROM users WHERE gender IS NULL OR age IS NULL; -- 用 COALESCE 将空值填充为默认值 SELECT order_id, COALESCE(amount, 0) AS amount_filled FROM orders; -- 统计非空用户数 SELECT COUNT(*) AS total_cnt, COUNT(user_name) AS name_cnt, COUNT(DISTINCT user_id) AS user_cnt FROM users;这里有一个非常重要的区别:
COUNT(*)统计所有行数;COUNT(字段)只统计该字段非空的行数;COUNT(DISTINCT 字段)统计该字段非空的去重行数。
用COUNT的时候,很多新手会把三种用法混在一起,导致统计结果和预期不一致。
4.3 字段转换与格式化
业务数据库中,字段格式往往不适合直接用于分析。例如日期是字符串类型、性别编码是 0/1、金额单位是分等。
-- 字符串转日期 SELECT order_id, STR_TO_DATE(order_date, '%Y-%m-%d') AS order_date_parsed FROM orders; -- 日期格式化 SELECT order_id, DATE_FORMAT(order_date, '%Y-%m') AS order_month FROM orders; -- 字符串拼接 SELECT CONCAT(user_name, '(', city, ')') AS user_desc FROM users;对于性别等编码字段,建议在查询时就转成可读性更强的内容:
SELECT user_id, CASE WHEN gender = 'M' THEN '男' WHEN gender = 'F' THEN '女' ELSE '未知' END AS gender_text FROM users;4.4 字符串清洗:去空格、截取、替换
字符串清洗包括去除前后空格、去掉换行符、替换无效字符、截取子串等。
-- 去除字符串前后空格 SELECT TRIM(user_name) FROM users; -- 替换字符串 SELECT REPLACE(city, '市', '') AS city_short FROM users; -- 截取子串:取用户名的前 1 个字符 SELECT SUBSTRING(user_name, 1, 1) AS first_char FROM users; -- 判断字符串是否包含某个值 SELECT user_name FROM users WHERE user_name LIKE '%三%';4.5 日期与时间处理
按时间维度分析是数据分析中最常见的需求。SQL 提供了丰富的日期函数:
-- 提取年、月、日 SELECT order_id, YEAR(order_date) AS order_year, MONTH(order_date) AS order_month, DAY(order_date) AS order_day FROM orders; -- 计算两个日期之间的天数差 SELECT order_id, DATEDIFF('2023-12-31', order_date) AS days_to_end FROM orders; -- 日期加减 SELECT order_id, DATE_ADD(order_date, INTERVAL 7 DAY) AS plus_7_days FROM orders;按月份统计订单量,是数据分析报表中非常基础的操作:
SELECT DATE_FORMAT(order_date, '%Y-%m') AS order_month, COUNT(*) AS order_cnt, SUM(amount) AS total_amount FROM orders GROUP BY DATE_FORMAT(order_date, '%Y-%m') ORDER BY order_month;4.6 排名与 Top N
数据分析场景中,经常需要找出销售额最高的商品、下单次数最多的用户、访问量最大的页面。这类问题在 SQL 中有两种常见解法。
方法一:使用ORDER BY配合LIMIT:
-- 找出订单金额最高的前 3 个订单 SELECT order_id, amount FROM orders ORDER BY amount DESC LIMIT 3;方法二:使用窗口函数RANK()、DENSE_RANK()、ROW_NUMBER()。这三者的区别是:
ROW_NUMBER():不管排名是否相同,都按顺序编号 1、2、3、4;RANK():排名相同时会跳过后续排名,例如 1、1、3;DENSE_RANK():排名相同时不会跳过,例如 1、1、2。
-- 每个商品类别下,销售额最高的商品 SELECT p.category, p.product_name, RANK() OVER (PARTITION BY p.category ORDER BY o.amount DESC) AS rk FROM orders o JOIN products p ON o.product_id = p.product_id;4.7 分组 Top N:每个组的最大值
“每个城市订单金额最高的用户是谁”“每个商品类别中最受欢迎的商品是哪几个”,这类问题在数据分析面试中频繁出现。核心思路是先将组内排名算出来,再取前 N 条。
-- 每个用户订单金额最高的前 2 个订单 SELECT * FROM ( SELECT user_id, order_id, amount, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rn FROM orders ) t WHERE rn <= 2;5. SQL 数据分析项目实战:从需求到结论
掌握了基础语法和数据清洗能力后,我们来看一个完整的数据分析项目,巩固前面所有知识点。
5.1 项目需求
假设你是某电商平台的数据分析师,老板给了几个问题:
- 2023 年 6 月和 7 月,每个月的订单总数和总销售额是多少?
- 哪个商品类别贡献的销售额最高?
- 哪个城市的用户下单金额最高?
- 销售额排名前 3 的商品是什么?
- 每个商品类别下,销售额最高的商品分别是什么?
5.2 逐步分析
第一个问题:按月统计订单数和销售额。
SELECT DATE_FORMAT(order_date, '%Y-%m') AS order_month, COUNT(*) AS order_cnt, SUM(amount) AS total_amount FROM orders GROUP BY DATE_FORMAT(order_date, '%Y-%m') ORDER BY order_month;第二个问题:按商品类别统计销售额。
SELECT p.category, SUM(o.amount) AS total_sales FROM orders o JOIN products p ON o.product_id = p.product_id GROUP BY p.category ORDER BY total_sales DESC;第三个问题:按城市统计用户下单金额。
SELECT u.city, SUM(o.amount) AS total_amount FROM orders o JOIN users u ON o.user_id = u.user_id GROUP BY u.city ORDER BY total_amount DESC;第四个问题:销售额排名前 3 的商品。
SELECT p.product_name, SUM(o.amount) AS total_sales FROM orders o JOIN products p ON o.product_id = p.product_id GROUP BY p.product_name ORDER BY total_sales DESC LIMIT 3;第五个问题:每个商品类别下销售额最高的商品。
SELECT category, product_name, total_sales FROM ( SELECT p.category, p.product_name, SUM(o.amount) AS total_sales, ROW_NUMBER() OVER (PARTITION BY p.category ORDER BY SUM(o.amount) DESC) AS rn FROM orders o JOIN products p ON o.product_id = p.product_id GROUP BY p.category, p.product_name ) t WHERE rn = 1;5.3 完整分析结果模板
在实际工作中,我们通常需要把 SQL 查询结果整理成一份分析报告。推荐输出格式包含:
- 分析结论:一句话说清核心发现;
- 数据支撑:用表格或图表展示关键指标;
- 业务建议:基于数据给出可落地建议;
- 风险提示:说明数据口径、样本量、异常情况。
比如针对上述结果,可以写:
“2023 年 6 月至 7 月总订单数为 12 单,数码类商品贡献销售额最高,其中手机和笔记本电脑为主要来源;北京地区用户下单金额领先;建议后续重点运营数码品类,同时在上海开展用户拉新活动。”
6. SQL 性能优化与安全注意事项
当数据量从几百行增长到几百万行、几千万行时,SQL 查询性能就会成为大问题。一条糟糕的 SQL 可能会把数据库拖垮。数据分析师写好“正确”的 SQL 之后,还要追求“高效”的 SQL。
6.1 慢 SQL 优化思路
常见的慢 SQL 原因及解决方向:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 查询非常慢 | 全表扫描 | 为 WHERE、JOIN、ORDER BY 涉及的字段添加索引 |
| 子查询很慢 | 子查询在大表上执行 | 改写为 JOIN 或使用临时表 |
| COUNT 很慢 | 对大表做精确 COUNT | 使用近似统计或缓存计数 |
| 排序很慢 | ORDER BY 字段无索引 | 添加索引或减少排序字段 |
| 分页很深时很慢 | LIMIT 100000, 20 | 使用游标分页或基于主键定位 |
一个简单的优化示例:给orders表的user_id添加索引。
CREATE INDEX idx_orders_user_id ON orders(user_id);添加索引后,WHERE user_id = 1的查询速度会明显提升。但要注意,索引不是越多越好,因为每次插入和更新数据时都需要维护索引,会有额外开销。
6.2 SQL 注入风险
SQL 注入是最常见的数据库安全威胁之一,攻击者通过在输入框或请求参数中拼接恶意 SQL,达到绕过认证、窃取数据、删除数据的目的。
反例(不要这样拼接 SQL):
String sql = "SELECT * FROM users WHERE user_name = '" + userName + "'";如果userName传入的是' OR '1'='1,最终 SQL 就变成了:
SELECT * FROM users WHERE user_name = '' OR '1'='1';这条语句会返回全表数据,账号信息直接泄露。
推荐做法是使用参数化查询,例如在 Java 的 JDBC 中使用PreparedStatement:
String sql = "SELECT * FROM users WHERE user_name = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, userName); ResultSet rs = pstmt.executeQuery();在 Python 中,禁止直接拼接 SQL 字符串,使用参数化方式:
cursor.execute("SELECT * FROM users WHERE user_name = %s", (user_name,))在涉及数据库操作时,务必确保你拥有合法的授权,并且在测试环境验证通过后再执行。生产环境的数据变更、删除、更新操作,必须先备份数据,遵循最小权限原则。
6.3 生产环境 SQL 操作注意事项
在数据分析工作中,你可能会接触到生产环境的数据库。遇到需要执行 DELETE、UPDATE、DROP 等操作时,必须特别谨慎:
- 先确认是否连接的是生产库,避免误操作;
- 对于删除和更新操作,先用
SELECT验证条件; - 重要操作前备份数据;
- 尽量在低峰期执行;
- 遵循公司规范和权限审批流程。
比如下面的删除操作,必须先注释掉 DELETE,先执行 SELECT 确认影响范围:
SELECT COUNT(*) FROM orders WHERE status = '已取消'; -- DELETE FROM orders WHERE status = '已取消';7. 数据分析面试中的 SQL 常见题型
如果你正在准备数据分析岗位面试,SQL 是必考环节。下面这些题型出现的频率非常高,建议逐一练习。
7.1 留存率计算
留存率是互联网数据分析面试的经典问题。比如:计算每个注册日期的新用户在次日、第 3 日、第 7 日的留存率。
SELECT register_date, COUNT(DISTINCT u.user_id) AS new_user_cnt, COUNT(DISTINCT CASE WHEN ORDER BY order_date = DATE_ADD(register_date, INTERVAL 1 DAY) THEN u.user_id END) AS kept_user_cnt FROM users u LEFT JOIN orders o ON u.user_id = o.user_id GROUP BY register_date;7.2 连续登录天数
连续登录天数问题也频繁出现在数据分析面试中。核心思路是先获取每个用户的登录日期,用ROW_NUMBER()或日期差值的连续性来判断。
WITH user_login AS ( SELECT DISTINCT user_id, order_date AS login_date FROM orders ), login_with_rn AS ( SELECT user_id, login_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn FROM user_login ) SELECT user_id, MIN(login_date) AS start_date, MAX(login_date) AS end_date, COUNT(*) AS continuous_days FROM login_with_rn GROUP BY user_id, DATE_SUB(login_date, INTERVAL rn DAY);7.3 同比与环比
同比是和去年同期比较,环比是和上一期比较。SQL 中通常用LAG()窗口函数实现:
SELECT order_month, total_amount, LAG(total_amount, 1) OVER (ORDER BY order_month) AS prev_month_amount, ROUND((total_amount - LAG(total_amount, 1) OVER (ORDER BY order_month)) / LAG(total_amount, 1) OVER (ORDER BY order_month) * 100, 2) AS mom_ratio FROM ( SELECT DATE_FORMAT(order_date, '%Y-%m') AS order_month, SUM(amount) AS total_amount FROM orders GROUP BY DATE_FORMAT(order_date, '%Y-%m') ) t;7.4 累计求和
累计求和计算每个用户的累计消费金额,可以用窗口函数或自连接实现:
SELECT user_id, order_id, amount, SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS cumulative_amount FROM orders;8. 学习路线总结与建议
SQL 的学习路径可以归纳为“基础查询 → 数据清洗 → 业务分析 → 性能优化 → 项目实战”五个阶段。每个阶段的重心不同:
- 第 1 阶段:熟练掌握
SELECT、WHERE、GROUP BY、ORDER BY、JOIN、子查询等核心语法; - 第 2 阶段:掌握去重、空值处理、字段转换、日期处理等清洗能力;
- 第 3 阶段:能够基于业务需求构造分析指标,如留存率、复购率、Top N、同比环比;
- 第 4 阶段:理解索引、执行计划、慢查询优化等性能知识;
- 第 5 阶段:独立完成从取数、清洗、分析到输出报告的完整项目。
最后强调三个实际工作中最值得注意的地方:
第一,不要只背语法,一定要动手建表、造数据、写分析。SQL 的熟练度全部来自实践量。
第二,在真实业务场景中,数据的质量、口径、业务含义比语法更重要。写 SQL 之前先搞清楚字段定义和业务规则,不要拿到表就直接开查。
第三,生产环境操作必须谨慎。删除、更新、批量变更数据之前,先备份、再验证、最后执行,尽量减少对线上数据的影响。
如果本文对你有帮助,可以收藏备用。后续我也会继续整理数据分析项目中更复杂的场景,比如漏斗分析、RFM 模型、A/B 实验效果评估等,欢迎持续关注。