SQL 基础知识体系概览
什么是 SQL?
SQL(Structured Query Language,结构化查询语言) 是与关系型数据库"对话"的标准语言。对于数据分析师来说,SQL 是最重要的技能之一——它是你从海量数据中提取洞察的第一步工具。
你可以把 SQL 想象成一种"向数据库提问的语言":
- "上个月销售额最高的商品是什么?"→ 一条 SELECT 语句
- "最近 30 天新注册了多少用户?"→ 一条 COUNT 语句
- "每个城市的平均客单价是多少?"→ 一条 GROUP BY 语句
SQL 诞生于 1970 年代 IBM 的研究实验室,最初叫 SEQUEL。如今它已成为 ISO 国际标准,几乎所有主流数据库(MySQL、PostgreSQL、Oracle、SQL Server、Hive、Spark SQL)都支持它,语法大同小异。
为什么数据分析师必须精通 SQL?
| 原因 | 说明 |
|---|---|
| 日常工具 | 数据分析工作中 60%~80% 的时间在写 SQL |
| 面试必考 | 几乎所有数据分析岗都会考 SQL 手写题 |
| 通用性强 | MySQL、PostgreSQL、Hive、Spark SQL 语法大同小异 |
| 效率极高 | 用 SQL 处理数据比 Python/Excel 快得多,几行代码处理百万行数据 |
| 职场通用语 | 和数据工程师、产品经理、业务方沟通,SQL 是最通用的语言 |
面试中 SQL 题目通常涉及:
SQL 基础(本章)→ SQL 分析进阶(窗口函数、CTE)→ 具体数据库特性
数据库与表的基本概念
在学习 SQL 之前,先理解几个核心概念:数据库、表、行、列。
概念层次关系
具体示例:电商数据库
假设我们有一个电商平台的数据库,其中有一张用户表(users):
| user_id | name | age | city | register_date |
|---|---|---|---|---|
| 1 | 张三 | 28 | 北京 | 2023-01-15 |
| 2 | 李四 | 35 | 上海 | 2023-02-20 |
| 3 | 王五 | 22 | 广州 | 2023-03-10 |
| 4 | 赵六 | 31 | 北京 | 2023-04-05 |
在这张表中:
- 数据库(Database):整个电商系统的数据仓库,包含
users、orders、products等多张表 - 表(Table):
users就是一张表,专门存储用户信息 - 行(Row)/ 记录(Record):每一行代表一个用户,如第一行是"张三"的所有信息
- 列(Column)/ 字段(Field):每一列代表一种属性,如
city列存储所有用户的城市信息 - 主键(Primary Key):
user_id是主键,唯一标识每一行,不能重复、不能为空
NULL 表示"缺失值"或"未知值",不等于空字符串 '',也不等于 0。在 SQL 中判断 NULL 必须用 IS NULL 或 IS NOT NULL,而不能用 = NULL。
SQL 语言分类(DDL / DML / DQL / DCL)
SQL 语句按功能分为四大类,面试中有时会被问到这些概念:
| 类别 | 全称 | 中文 | 代表命令 | 说明 |
|---|---|---|---|---|
| DDL | Data Definition Language | 数据定义语言 | CREATE、DROP、ALTER | 定义数据库结构(建表、删表、改字段) |
| DML | Data Manipulation Language | 数据操作语言 | INSERT、UPDATE、DELETE | 操作表中的数据(增、改、删) |
| DQL | Data Query Language | 数据查询语言 | SELECT | 查询数据(数据分析师用得最多) |
| DCL | Data Control Language | 数据控制语言 | GRANT、REVOKE | 权限管理(通常由 DBA 负责) |
DQL(SELECT) 是工作中用得最多的,占 90% 以上。DML 的 INSERT/UPDATE/DELETE 偶尔用到。DDL 和 DCL 通常由数据工程师或 DBA 负责,了解即可。
各类别代表示例
-- DDL:创建一张用户表
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT,
city VARCHAR(50),
register_date DATE
);
-- DML:插入一条用户记录
INSERT INTO users (name, age, city, register_date)
VALUES ('张三', 28, '北京', '2023-01-15');
-- DQL:查询北京用户
SELECT name, age
FROM users
WHERE city = '北京'
ORDER BY age DESC;
-- DML:更新用户信息
UPDATE users SET city = '深圳' WHERE user_id = 1;
-- DML:删除用户
DELETE FROM users WHERE user_id = 1;
SQL 核心执行模型
很多初学者觉得 SQL "写起来顺序和执行顺序不一样,很迷惑"。掌握 SQL 的逻辑执行顺序是写出正确查询的关键。
SQL 的书写顺序 vs 执行顺序
为什么执行顺序重要?
这个顺序解释了很多常见的"为什么报错"问题:
-- ❌ 错误:WHERE 中不能用 SELECT 里定义的别名
-- 因为 WHERE 比 SELECT 先执行,别名还不存在
SELECT price * quantity AS total_amount
FROM orders
WHERE total_amount > 1000; -- 报错!total_amount 此时尚未定义
-- ✅ 正确:改用 HAVING,或者重写表达式
SELECT price * quantity AS total_amount
FROM orders
HAVING total_amount > 1000; -- HAVING 在 SELECT 之后执行,可以用别名
-- ✅ 或者直接用原始表达式
SELECT price * quantity AS total_amount
FROM orders
WHERE price * quantity > 1000; -- WHERE 重写原始表达式
- WHERE:在分组之前过滤行,不能使用聚合函数
- HAVING:在分组之后过滤组,可以使用聚合函数(如
COUNT(*) > 5)
一个完整查询的执行流程
以"统计每个城市下单金额超过 10000 元的用户数"为例:
SELECT
city,
COUNT(DISTINCT user_id) AS user_count -- 5. 计算每组的用户数
FROM orders
WHERE order_date >= '2024-01-01' -- 2. 先过滤出今年的订单
GROUP BY city -- 3. 按城市分组
HAVING SUM(amount) > 10000 -- 4. 过滤掉总金额不足的城市
ORDER BY user_count DESC -- 6. 按用户数降序排列
LIMIT 10; -- 7. 只取前 10 个城市
初学者常犯的 5 个错误
错误 1:混淆 = 和 IS NULL
-- ❌ 错误:找不到任何结果,NULL 不能用 = 比较
SELECT * FROM users WHERE city = NULL;
-- ✅ 正确
SELECT * FROM users WHERE city IS NULL;
错误 2:GROUP BY 遗漏列
-- ❌ 错误:SELECT 中出现了没有被聚合、也没有在 GROUP BY 中的列
SELECT city, name, COUNT(*) AS cnt -- name 没有在 GROUP BY 里!
FROM users
GROUP BY city;
-- ✅ 正确:要么加入 GROUP BY,要么用聚合函数
SELECT city, COUNT(*) AS cnt
FROM users
GROUP BY city;
错误 3:在 WHERE 中使用聚合函数
-- ❌ 错误:WHERE 不能用聚合函数(执行顺序问题)
SELECT city, COUNT(*) AS cnt
FROM users
WHERE COUNT(*) > 5 -- 报错!
GROUP BY city;
-- ✅ 正确:改用 HAVING
SELECT city, COUNT(*) AS cnt
FROM users
GROUP BY city
HAVING COUNT(*) > 5;
错误 4:忽视 NULL 对聚合函数的影响
-- 假设 age 列有 NULL 值
-- COUNT(*) 统计所有行(包含 NULL)
-- COUNT(age) 只统计 age 非 NULL 的行
-- AVG(age) 只计算非 NULL 的平均值
SELECT
COUNT(*) AS total_rows, -- 所有行数(含 NULL)
COUNT(age) AS non_null_age, -- age 非 NULL 的行数
AVG(age) AS avg_age -- 只对非 NULL 求平均
FROM users;
错误 5:过早使用 DISTINCT,忽视性能
-- ⚠️ 危险:数据量大时 DISTINCT 很耗性能,先想清楚为什么会重复
SELECT DISTINCT user_id FROM orders;
-- ✅ 更好:先理解重复的原因,可能是 JOIN 引起的,从根源解决
-- 而不是盲目加 DISTINCT 掩盖问题
核心知识导航
| 主题 | 说明 | 面试重要度 |
|---|---|---|
| SELECT 查询基础 | WHERE、ORDER BY、LIMIT、DISTINCT——数据查询的基本功 | ⭐⭐⭐⭐⭐ |
| JOIN 连接查询 | 多表关联查询——面试最高频考点之一 | ⭐⭐⭐⭐⭐ |
| 聚合函数与 GROUP BY | COUNT、SUM、AVG + 分组统计——数据分析的核心 | ⭐⭐⭐⭐⭐ |
| 子查询 | 嵌套查询、EXISTS、IN——解决复杂问题的利器 | ⭐⭐⭐⭐ |
| 数据操作 DML | INSERT、UPDATE、DELETE——数据的增删改 | ⭐⭐⭐ |
| SQL 常用函数 | 字符串函数、日期函数、数值函数、类型转换 | ⭐⭐⭐ |
学习路径与时间建议
根据你的目标,推荐以下学习路径:
路径一:快速入门(求职导向,2~3 周)
路径二:系统学习(全面掌握,6~8 周)
| 阶段 | 时间 | 内容 | 目标 |
|---|---|---|---|
| 第一阶段 | 1~2 周 | SELECT / WHERE / JOIN / GROUP BY | 能独立写出基础分析查询 |
| 第二阶段 | 1~2 周 | 子查询 / 常用函数 / DML | 能处理中等复杂度的业务需求 |
| 第三阶段 | 2~3 周 | 窗口函数 / CTE / 行列转换 | 能完成留存、漏斗、RFM 等分析 |
| 第四阶段 | 1~2 周 | 性能优化 / 特定数据库特性 | 写出高效 SQL,理解执行计划 |
边学边练是学 SQL 最快的方式。推荐同步刷 LeetCode SQL,每学一个知识点就找对应的练习题。50 道免费题刷完,基础 SQL 面试基本无忧。
已有编程基础的人如何快速上手?
如果你已经掌握 Python 或其他编程语言,SQL 的核心思维转换如下:
| SQL 操作 | 对应的 Python Pandas 操作 |
|---|---|
SELECT col FROM t | df['col'] |
WHERE condition | df[df['col'] > x] |
JOIN | pd.merge(df1, df2, on='key') |
GROUP BY + COUNT | df.groupby('col').count() |
ORDER BY | df.sort_values('col') |
LIMIT 10 | df.head(10) |
SQL 方言说明
不同数据库的 SQL 语法略有差异(称为"方言")。本文档以 MySQL 和 PostgreSQL 为主,同时标注差异:
| 功能 | MySQL | PostgreSQL | Hive/Spark SQL |
|---|---|---|---|
| 字符串连接 | CONCAT() | || 或 CONCAT() | CONCAT() |
| 限制行数 | LIMIT n | LIMIT n | LIMIT n |
| 当前时间 | NOW() | NOW() | current_timestamp() |
| 自增主键 | AUTO_INCREMENT | SERIAL | 不支持 |
| 类型转换 | CAST() | CAST() 或 :: | CAST() |
| 字符串截取 | SUBSTRING() | SUBSTRING() 或 SUBSTR() | SUBSTR() |
| 日期格式化 | DATE_FORMAT() | TO_CHAR() | DATE_FORMAT() |
| 条件表达式 | IF(cond, a, b) | 不支持 IF(),用 CASE WHEN | IF() |
面试中如果不确定用哪种方言,可以直接问面试官:"请问是用 MySQL 还是 PostgreSQL 的语法?"这不丢分,反而体现你对方言差异有认知。
一个完整的分析案例
在正式开始学习各章节之前,先看一个完整的 SQL 分析查询,感受 SQL 的表达能力:
业务问题:找出 2024 年每个城市下单金额 Top 3 的用户,并显示他们的总下单金额和下单次数。
-- 使用窗口函数(进阶内容,先感受一下 SQL 的表达能力)
WITH user_stats AS (
-- 第一步:按城市和用户统计下单信息
SELECT
u.city,
u.name,
COUNT(o.order_id) AS order_count,
SUM(o.amount) AS total_amount
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE o.order_date >= '2024-01-01'
AND o.order_date < '2025-01-01'
GROUP BY u.city, u.name
),
ranked AS (
-- 第二步:在每个城市内按金额排名
SELECT
city,
name,
order_count,
total_amount,
ROW_NUMBER() OVER (
PARTITION BY city
ORDER BY total_amount DESC
) AS rank_in_city
FROM user_stats
)
-- 第三步:只取每个城市 Top 3
SELECT city, name, order_count, total_amount
FROM ranked
WHERE rank_in_city <= 3
ORDER BY city, rank_in_city;
看不懂也没关系,学完本章节和 SQL 分析进阶 后,这段代码就会变得非常清晰。