数据分析 SQL 入门,数据库查询基础教程
目录

数据分析 SQL 入门,数据库查询基础教程 | 九数云-E数通

eshutong 发表于2026年8月20日

数据分析 SQL 入门,数据库查询基础教程

很多 SQL 入门教程的第一章都在讲 SELECT 语法,第二章讲 WHERE,第三章讲 JOIN,看上去结构完整,但到了真实的数据分析现场,大多数人还是写不出一条能直接回答业务问题的查询。

我过去三年带过 40 多个数据分析方向的新人,反复看到一个规律:能快速上手 SQL 的人,都不是先背语法,而是先建立“提问顺序”。

SQL 不是一门需要系统学习的编程语言,它更像一台把业务问题翻译成数据步骤的机器。在分析场景下,90% 的日常取数工作,只需要掌握 8 个语法块,以及一套正确的拆解思路。

一、核心结论:SQL 分析入门不是学语法,而是学提问顺序

1. 90% 的入门教程让你学错了方向

市面上的 SQL 教程绝大多数按“语法清单”组织:SELECT 讲一章、WHERE 讲一章、JOIN 讲一章、字符串函数讲三章。这种结构会让学习者觉得自己在不断“学新东西”,但到了分析场景里,往往连一张订单表的周报都写不顺。

我面试过不少简历上写着“熟练使用 SQL”的人,给一张订单表和一张用户表,让他们算“近 30 天客单价大于 200 元的用户数”,约一半能写对。再追问一句“这个查询在数据库里先执行哪一步”,能答出来的不到四分之一。

这说明问题不在语法量,而在心智模型。分析场景下的 SQL 入门,核心不是“学会更多语法”,而是建立一套把业务问题翻译成查询步骤的思维流程。先搞懂执行顺序,再追求语法数量,是我给所有新人的第一条建议。

2. 分析型查询 90% 以上只需要 8 个语法块

我盘点过团队 2023 年 6 月到 2024 年 6 月之间沉淀下来的 412 条线上分析查询,结果非常集中:

语法块用途在 412 条查询中的出现比例
SELECT / SELECT DISTINCT选择列、去重100%
FROM指定数据表100%
WHERE行级过滤87.6%
JOIN / LEFT JOIN多表关联71.4%
GROUP BY分组聚合65.8%
HAVING分组后过滤22.3%
ORDER BY排序68.2%
LIMIT限制返回行数54.6%
CASE WHEN条件字段与分组口径41.7%

这 9 个语法块加起来,已经覆盖了团队日常分析查询的 92% 以上。我还没有提窗口函数、存储过程、正则函数和复杂索引调优。窗口函数只出现在 38 条查询里,占比 9.2%;涉及存储过程的只有 3 条。

所以我的结论很直接:入门阶段不需要系统啃完一本 600 页的 SQL 教材。把 8 个核心语法块练到不假思索就能写,再花时间搞懂执行顺序,你就已经具备完成大部分分析取数的能力。

3. 我观察到的共同规律:执行顺序是第一道分水岭

如果你关注数据分析类岗位的面试,应该常看到这道题:SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT 哪个先执行?

答案是:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。也就是说,你书写时最先写的 SELECT,实际上是最后才被数据库执行的。

我观察到的规律是:能准确说出执行顺序的人,写 JOIN 时很少漏条件;不理解执行顺序的人,容易写出“SELECT 里起了别名,WHERE 里直接引用”这类低级报错,也会在子查询里做大量无用功。

这不是背诵层面的差异,而是思维方式的差异。SQL 的每一步操作都有明确的输入和输出。当你把查询当作一条流水线,而不是一句“命令”,你才算真正入门。

数据分析 SQL 入门,数据库查询基础教程

二、真实场景:Excel 到 SQL 的分界线在哪里

1. 什么时候你必须离开 Excel

经常有人问我:Excel 用得挺好,为什么还要学 SQL?我的回答是:看你的数据量级和协作方式。

Excel 的实用极限大约在 100 万行左右,超过这个量级,筛选、透视、VLOOKUP 都会明显变卡。更麻烦的是多人协作:一个 2GB 的 CSV 文件,配一堆手工更新的数据透视表,很容易出现口径不一致,最后谁都说不清哪个数字是对的。

我见过最典型的例子:财务同事用 Excel 手工核对线上销售报表,每月文件 500 多 MB,打开一次要 3 分钟,改一个筛选条件要 30 秒。而同一个办公室用 SQL 的数据分析师,同样的数据 10 秒内出结果,还能每天自动刷新。

2. 一次真实的取数现场

2023 年 8 月,一家做电商代运营的客户找到我们,说运营团队每周要花半天时间从后台导出订单明细,再用 Excel 做周报。导出的数据量是 217 万行,Excel 打开后至少卡 5 分钟。

我们的做法很简单:把 217 万行导入本地 MySQL 实例,写一条查询,按渠道、支付方式、省份三个维度汇总订单金额和订单量。第一次跑通后,运营团队把周报从半天压缩到 20 分钟。

这个案例里没有复杂建模,没有机器学习,只有一条 12 行的 SQL。它的价值不在于“快”,而在于数据口径被固定了,所有人都只看同一套数字。

3. 为什么我不推荐一上来就装复杂数据库

新手喜欢挑战“最专业”的工具,但这是入门阶段最大的干扰项。学习 SQL 的目标是把语法和思维方式练熟,而不是调数据库配置。

我的建议按环境分三种:本地零安装用 SQLite,适合纯学习和练习;内网环境装 MySQL 社区版,适合模拟真实业务表;如果已经拿到公司数据库账号,直接在只读权限下跑 SELECT,学习效率最高。

数据分析 SQL 入门,数据库查询基础教程

4. 用只读权限开始:能问问题,就不急着写数据

这里有一个重要原则:入门阶段只需要能读数据,不需要能写数据。公司的生产数据库通常会给分析账号开只读权限,这是常态,不是限制。

只读意味着你只能写 SELECT,不能写 INSERT、UPDATE、DELETE。这反而是好事:它逼你在查询层面解决所有问题,也大幅降低了误操作风险。很多 SQL 事故,都是在“能写”的时候发生的。

三、常见误区:为什么你的 SQL 总是“看起来对,结果错”

1. 误区一:SELECT 写在最前面,就是最先执行的

前面已经讲了执行顺序。很多人写查询时习惯先写 SELECT 的列,再回头想 FROM 哪张表。这种习惯在单表查询时问题不大,一旦涉及 JOIN 和聚合,错误就来了。

举一个真实报错:SELECT 里用别名 total,然后在 WHERE 里写 total > 1000,数据库直接报错。因为 WHERE 执行时,SELECT 的别名根本还不存在。这是数据分析新人最常见的报错之一。

-- 错误写法:WHERE 引用 SELECT 别名
SELECT customer_id, SUM(amount) AS total

FROM orders

WHERE total > 1000

GROUP BY customer_id;

-- 正确写法:聚合结果过滤必须用 HAVING

SELECT customer_id, SUM(amount) AS total

FROM orders

GROUP BY customer_id

HAVING total > 1000;

判断原则很明确:能用 WHERE 过滤的原始行条件,绝不放 HAVING;要引用聚合结果的过滤,必须放 HAVING。

2. 误区二:WHERE 能做过滤,所以 HAVING 是多余的

这两个过滤器的执行时机完全不同:WHERE 在分组之前过滤行,HAVING 在分组之后过滤组。把本应在 WHERE 里做的条件放进 HAVING,数据库会先算出大量无用分组,再把它们扔掉,浪费严重。

反过来,把本应在 HAVING 里的条件硬塞进 WHERE,比如写 WHERE SUM(amount) > 1000,会直接报错,因为 WHERE 阶段聚合函数还没执行。

判断方法很简单:条件里出现 SUM、COUNT、AVG 这类聚合函数,就用 HAVING;只跟原始行字段有关,就用 WHERE。

3. 误区三:JOIN 想怎么连就怎么连

JOIN 是 SQL 里报错率最高、也最容易“看着对”的部分。常见问题有两个:忘记写关联条件,造成笛卡尔积爆炸;JOIN 方向搞反,把 LEFT JOIN 写成 JOIN,导致左表数据被静默删掉。

我给自己团队定过一条标准:写 JOIN 必须写明关联键和关联方向,并且要在注释里说明“这条 JOIN 会不会过滤主表行数”。如果一条查询里有三张表,至少要有两句注释写清楚表之间的关系。

-- LEFT JOIN:把日期过滤写在 ON 里,可以保留左侧全部用户行
SELECT u.user_id, COUNT(o.order_id) AS order_cnt

FROM users u

LEFT JOIN orders o

ON o.user_id = u.user_id

AND o.order_date >= '2024-01-01'

GROUP BY u.user_id;

把过滤条件写在 ON 里而不是 WHERE 里,是保留左表全部用户的关键细节,也是很多教程不会讲的实战差异。

4. 误区四:数据清洗必须一步到位写进 SQL

新手总想把所有清洗逻辑塞进一条 SQL,结果是一条查询又长又难维护,改一个口径要盯十分钟。更合理的做法是分层:第一步限定数据范围和关键字段,第二步做类型转换和缺失值处理,第三步再做聚合分析。

我们团队的习惯是每层用不同命名:原始层、清洗层、聚合层。这不是数据仓库的“范式”,而是一种降低排错成本的工作方式。好处是出问题时能快速定位是哪一层错了。

5. 误区五:学 SQL 就是背命令

最后一个误区影响最大。SQL 语法当然要记忆,但只记忆不练习,两三天就忘光。真正的记忆路径是:带着业务问题写查询,反复把“问题”翻译成“语法”。

我给新人布置的入门作业只有一份:连续 14 天,每天一个取数题。比如“计算每个产品近 7 天销售额并排序”“找出复购用户中购买次数最多的前 10 人”。这些题目的价值不是覆盖多少语法,而是把查询执行顺序练成肌肉记忆。

数据分析 SQL 入门,数据库查询基础教程

四、专业判断逻辑:用“取数四问”代替试错

1. 取数四问:任何查询从四个问题开始

我带人第三年时总结了一套“取数四问”,用来解决“会写 SQL 但不知道写什么”的问题。任何查询动手之前,先把这四个问题答清楚:

(1)指标是什么:金额、数量、占比、排名,还是增长率?

(2)统计粒度是什么:按用户、按订单、按天,还是按产品?

(3)时间窗口是什么:近 7 天、近 30 天,还是自然月?

(4)计算口径是什么:包含退款吗?包含测试订单吗?同一用户多平台下单怎么算?

四个问题里任何一个没答清楚,写出来的查询都可能是错的。而且这四个问题的答案,必须在写 SQL 之前就存在,不能一边写一边猜。

数据分析 SQL 入门,数据库查询基础教程

2. 从问题到 SQL 的映射关系

取数四问的答案确定后,下一步是把答案翻译成 SQL 骨架。我在培训时常用下面这张映射表:

业务问题对应 SQL 阶段关键语法
只要某几个字段SELECT 列SELECT
只要某段时间 / 某类用户行级过滤WHERE
按产品 / 渠道汇总分组统计GROUP BY
保留左表全部记录关联方向LEFT JOIN
只要订单数大于 10 的组分组后过滤HAVING
看最高 / 最低 / TopN排序与截断ORDER BY + LIMIT

这套映射关系熟练之后,你会发现 SQL 更像一种填空题:先把业务问题翻译成关键字,再往骨架里填条件。

3. 表的粒度决定结果的口径

这是我很想强调、但教程里很少讲清楚的问题:一切查询先看表的粒度。订单表一行是一笔订单,用户表一行是一个用户,订单明细表一行可能是订单里的一个商品。

同样的 SUM(amount),在订单表和订单明细表上算,结果可能不同。因为明细表里同一笔订单被拆成多行,直接 SUM 会重复计算。要算出真实订单金额,必须先确认使用的表恰好是一行一单,或者先做一次订单维度去重。

-- 写聚合前,先检查表粒度
SELECT order_id, COUNT(*) AS line_cnt

FROM order_items

GROUP BY order_id

ORDER BY line_cnt DESC

LIMIT 10;

我的建议是:写查询之前,先写出“这张表一行代表什么”,再写出“我要的粒度是什么”。两者不一致时,必须先解决粒度问题,再进入聚合。

4. NULL 与去重:两个被严重低估的坑

SQL 里 NULL 不等于 0,也不等于空字符串。COUNT(具体列) 会跳过 NULL,COUNT(*) 不会;SUM(具体列) 对全为 NULL 的组返回 NULL,而不是 0。这些细节在几百行数据时无感,在两百万行时可能造成几十万的金额偏差。

去重的坑则在于:DISTINCT 不是只对某一列生效,而是对 SELECT 后面的整组列生效。SELECT DISTINCT user_id 统计的是用户数;SELECT DISTINCT user_id, order_id 统计的是“用户-订单组合”数,两者含义完全不同。

如果你在 CASE WHEN 里写 SUM(CASE WHEN status = 'paid' THEN amount END),在没有匹配行时结果也是 NULL。此时需要用 COALESCE 包一层,把它转成 0,否则报表里会出现莫名其妙的空白。

五、具体案例:一条查询从 2 小时优化到 3 分钟

1. 需求与原始表

2024 年 3 月,我们接到一个客户的数据需求:统计 2023 年全年每个渠道、每个省的月度订单金额。数据源是两张表:订单主表 orders 约 217 万行,订单明细表 order_items 约 310 万行。

客户原来的做法是:从后台导出 CSV,再用 Excel 透视表处理,每次耗时约 2 小时。我们接手后,把数据导入 MySQL,开始写第一版查询。

2. 第一版查询:能跑但很慢

SELECT channel, province, DATE_FORMAT(order_date, '%Y-%m') AS month,
SUM(oi.amount) AS total_amount

FROM (

SELECT order_id, order_date, channel, province

FROM orders

) o

LEFT JOIN (

SELECT order_id, amount

FROM order_items

) oi ON o.order_id = oi.order_id

WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31'

GROUP BY channel, province, month;

这条查询能返回正确结果,但第一次跑耗时 2 小时 14 分钟。原因很清楚:两个子查询没有缩小任何数据量,等于先把两张全表搬出来做拼接,之后才执行 WHERE 过滤。三行有效的过滤条件,被放在了整条流水线的最后一步。

3. 三个优化步骤:耗时从 134 分钟降到 3 分钟

第一步是过滤下推。把订单日期的过滤条件写进子查询内部,让 MySQL 尽早缩小订单表的扫描范围,参与关联的数据从 217 万行降到约 34 万行。

第二步是去掉多余子查询,直接 JOIN。两个子查询没有过滤也没有聚合,反而阻止了优化器直接访问基表索引。改成普通 JOIN 后,数据库可以自主选择驱动表和关联顺序。

第三步是增加联合索引。在 orders 表上创建覆盖 order_date、channel、province 三列的索引,让范围过滤和分组维度都能走索引,扫描成本大幅下降。

-- 优化后的最终查询
CREATE INDEX idx_orders_date_channel ON orders(order_date, channel, province);

SELECT o.channel, o.province, DATE_FORMAT(o.order_date, '%Y-%m') AS month,

SUM(oi.amount) AS total_amount

FROM orders o

LEFT JOIN order_items oi

ON oi.order_id = o.order_id

WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31'

GROUP BY o.channel, o.province, month

ORDER BY month, total_amount DESC;

优化后的查询耗时 3 分 12 秒,结果与原查询完全一致。整条优化路径没有用任何炫技语法,只有三步基本功:过滤下推、去除无用子查询、合理使用索引。

数据分析 SQL 入门,数据库查询基础教程

4. 结果验证:SQL 不是万能的,对账是必修课

查询跑完不等于工作结束。我们做了一道关键步骤:把 SQL 结果和 Excel 透视表结果对账。客户只有 2023 年 3 月的数据量能装进 Excel,我们便用 3 月单月做交叉验证。

对账发现 28 处差异,分为四类:渠道字段为空的订单被分组到 NULL,共 12 条,而 Excel 透视表默认隐藏了空值;跨时区订单的日期归属差异 8 条;重复订单 ID 5 条;小数精度误差 3 条。

这件事给我的教训很深刻:SQL 只能保证查询按逻辑执行,不能保证逻辑符合业务流程。哪怕你写出了“正确”的 SQL,也要先和已知口径的子集做一次对账,再对外交付数字。

数据分析 SQL 入门,数据库查询基础教程

六、不同情况下的行动建议:三种人,三种学法

1. 业务运营与产品经理:先学 SELECT 系

如果你做运营或产品,日常工作以查数、看趋势、做周报为主,第一优先级是 SELECT、WHERE、GROUP BY、ORDER BY、LIMIT,最多再加 LEFT JOIN 和 CASE WHEN。

不需要急着学窗口函数。运营类取数里九成可以用过滤加分组解决,剩下的排名需求先用近似手段处理,等遇到真正的复杂分析再回头学,效率更高。

具体路径:第一周做单表取数,第二周做两张表 JOIN,第三周完成一次“拉数,对账,解读”的完整闭环。坚持三周,你就能独立承接大部分日常取数需求。

2. 数据分析师候选人:多表关联和窗口函数必须拿下

如果你的目标是数据分析师,难度要上两个台阶。面试几乎必考的窗口函数,ROW_NUMBER、RANK、LAG、LEAD,本质上是在解决“分组内排名”和“同环比”问题。

我的建议学习顺序:先把 8 个基础语法块练到不看资料就能写,然后专攻窗口函数。理解窗口函数的关键也在于执行顺序:先形成分组,组内排序,再逐行计算。

-- 每个用户最近一笔订单:数据分析师面试经典题
SELECT user_id, order_id, amount, order_date

FROM (

SELECT user_id, order_id, amount, order_date,

ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) AS rn

FROM orders

) t

WHERE rn = 1;

还有一个容易被忽视的考点:SQL 执行流程和索引的基本原理。你不需要会调优,但要知道查询为什么慢,以及加索引为什么能变快。

3. 数据开发与后台研发:执行计划和数据模型优先

如果你的岗位是数据开发或后台研发,SQL 只是工具,真正的重点在数据库机制本身:执行计划、索引选择、事务隔离级别、分区与分库。

这类人我最不建议陷进“写长 SQL”的成就感里。长 SQL 往往意味着可以拆成多层或任务链。能用视图、定时任务、物化表解决的问题,不要留给一条 300 行的巨型查询。

4. 三类人群的学习优先级差异

学习模块业务运营数据分析师数据开发
基础 8 语法块重点重点基础
窗口函数选学重点重点
多表关联重点(LEFT JOIN 为主)重点重点
执行计划与索引不必学了解精通
数据建模与分层不必学了解重点
结果解读与对账重点重点了解

数据分析 SQL 入门,数据库查询基础教程

5. 学习资源的取舍

最后说学习资源。我的建议不是先买课,而是先建立“需求池”:把业务里真实存在、但还没解决的数据问题列成清单,每天用 SQL 解决一个。真实需求比任何练习题都更能暴露你对执行顺序和粒度的理解不足。

教材方面,官方文档永远优先于二手教程。SQLite 和 PostgreSQL 的官方文档都有清晰的语法说明;遇到具体函数,优先查官方手册,不要在一篇博客里猜答案。

七、不同情况下的取舍:性能、成本与可维护性

1. 视图 vs 临时表 vs 子查询

同一个取数需求,经常能用三种方式实现。我把决策标准总结成四个词:复用性、可维护性、性能、隔离性。

视图适合频繁复用的固定口径,缺点是复杂视图的性能不稳定;临时表适合中间结果多、需要分步骤调试的场景,缺点是不适合长期复用;子查询适合一次性查询,缺点是嵌套过深时可读性迅速下降。

-- 视图:固定口径,供多张报表复用
CREATE VIEW v_monthly_sales AS

SELECT channel, DATE_FORMAT(order_date, '%Y-%m') AS month,

SUM(amount) AS total_amount

FROM orders

GROUP BY channel, month;

-- 临时表:分步骤处理中间结果

CREATE TEMPORARY TABLE tmp_valid_orders AS

SELECT order_id, amount

FROM orders

WHERE status = 'paid';

我的取舍原则很简单:会被复用 3 次以上,建视图;中间步骤超过 2 步,用临时表;只跑一次且逻辑简单,直接子查询。

数据分析 SQL 入门,数据库查询基础教程

2. 在数据库里算,还是拉出来算

经常会有人问:“这个分析计算量很大,我是用 SQL 全算完,还是先拉到 Python 或 Excel 再算?”我的答案是:大前提尽量在 SQL 里算,因为数据库天生擅长并行和索引;但也不要把 SQL 当全能计算器。

判断标准有三条:数据量超过 500 万行时,优先在数据库里完成聚合;逻辑里有大量字符串处理、正则、文本相似度匹配时,可以拉出来用 Python;如果是临时的一次性探索,拉回本地也完全可接受。

3. 宽表 vs 范式表

宽表查询快,但维护成本高;范式表节省存储、更新友好,但查询要 JOIN 很多次。在分析场景里,我的经验是适度宽表是最优解:把单次分析最常用的维度提前拼好,比每次现场 JOIN 快得多。

但这不意味着可以随便做宽表。宽表字段一旦超过 50 个,维护口径就是灾难。建议定期清理未被引用的列,用视图提供“准宽表”来替代物理宽表,兼顾查询速度和维护成本。

4. 一次性取数 vs 自动化报表

这是分析岗每天都在做的取舍。一次性取数追求速度和灵活,自动化报表追求稳定和可解释。我的建议是:同一个需求被问到第 3 次,就把它做成固定查询;被问到第 5 次,再考虑定时任务。

过早自动化会带来维护负担:数据源变了、口径调整了、字段改名了,每一样都需要你去维护,而且你未必是最后接手的人。我们的实践是:任何自动化报表上线前,必须有明确负责人和退出机制,否则不如继续手动取数。

八、结语:下一步怎么走

回到开头的问题:SQL 分析入门到底学什么?我的核心观点是,它学的不是语法清单,而是提问顺序。先问清楚指标、粒度、时间窗口和口径,再按 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT 的流水线,把业务问题翻译成查询。

如果你读完这篇文章只能记住三件事:第一,先搞懂执行顺序,再追求语法数量;第二,写查询前用“取数四问”确认需求,而不是直接打开编辑器;第三,输出结果必须对账,SQL 保证的是逻辑正确,不是业务口径正确。

下一步,找一张真实的业务表,订单、日志、用户表都行。从今天最后一个需求开始,写一条能跑通、能对账、能优化的完整查询。14 天之后,你大概率会发现,自己已经能独立回答同事的取数问题了。

常见问题解答(FAQ)

1. SQL 入门时,为什么查询结果总是比预期多,尤其是使用 JOIN 之后?

我刚开始做数据分析时,以为两张表通过用户 ID 连接后,每个用户应该只返回一行。实际查询却出现了重复用户,行数从 12,000 增长到 38,000,我不确定这是 SQL 写错了,还是原始数据本来就存在一对多关系。

这通常不是 JOIN 语法错误,而是你误判了连接键的唯一性。用户表可能是一人一行,但订单表是一人多行;当你把用户表和订单表连接时,一个用户会按照订单数量重复出现。SQL 不会自动替你“去重”,它只会忠实返回满足连接条件的所有组合。

我在排查类似问题时,先不急着加 DISTINCT,而是分别检查两张表的粒度。

可以先执行:

SELECT user_id, COUNT(*) AS row_count FROM orders GROUP BY user_id HAVING COUNT(*) > 1 ORDER BY row_count DESC;

如果结果显示大量用户拥有多条订单,就说明订单表的粒度是“每笔订单一行”,而不是“每个用户一行”。这时直接 JOIN 后,用户重复是合理结果。

如果目标是统计每个用户的订单数,应先聚合再连接,而不是连接后再想办法去重:

SELECT u.user_id, u.user_name, COALESCE(o.order_count, 0) AS order_count FROM users u LEFT JOIN ( SELECT user_id, COUNT(*) AS order_count FROM orders GROUP BY user_id ) o ON u.user_id = o.user_id;

两种写法的含义完全不同:直接连接得到的是“用户,订单明细”,先聚合再连接得到的是“用户,订单汇总”。我的判断标准是:写 JOIN 前先用一句话描述每张表的一行代表什么。如果两边粒度不同,就必须明确是要保留明细、先聚合,还是只判断是否存在记录。

目标推荐方式常见错误 查看每笔订单直接 JOIN 明细表误以为用户不会重复 统计每个用户订单数订单表先 GROUP BY 再 JOINJOIN 后用 DISTINCT 掩盖问题 判断用户是否下过单使用 EXISTS为了判断存在而连接全部订单 尤其要谨慎使用 DISTINCT。

它只能删除完全相同的结果行,不能修复金额重复累加、订单数膨胀或连接条件错误。若销售额从 100 万变成 270 万,优先检查 JOIN 后的行数和表粒度,而不是先加 DISTINCT。

2. SQL 中 NULL 为什么不能直接用等号判断,COUNT(*) 和 COUNT(字段) 又有什么区别?

我在统计用户手机号为空、订单金额缺失时,写了 WHERE phone = NULL,却始终查不到结果。后来又发现 COUNT(*) 和 COUNT(phone) 得到的数字不一样,我想知道 NULL 在查询和统计中到底是怎样被处理的。

NULL 不是一个具体值,而是“未知、缺失或不适用”的状态,所以 NULL = NULL 的结果不是 TRUE,而是 UNKNOWN。WHERE 子句只保留判断结果为 TRUE 的行,因此写成 phone = NULL 时不会返回你想要的空值记录。

判断空值必须使用 IS NULL 或 IS NOT NULL: SELECT * FROM users WHERE phone IS NULL;这是 SQL 入门阶段最容易造成业务误判的地方。例如“没有手机号”可能包括真正缺失、空字符串、只含空格三种情况。

若数据来源不统一,仅写 IS NULL 可能漏掉空字符串:

SELECT COUNT(*) FROM users WHERE phone IS NULL OR TRIM(phone) = '';COUNT 的差异则来自统计对象不同。COUNT(*) 统计行数,不管字段是否为 NULL;

COUNT(phone) 只统计 phone 不为 NULL 的行。假设表中有 10 行,其中 3 行 phone 为 NULL,那么 COUNT(*) 是 10,COUNT(phone) 是 7。

写法统计含义适合场景 COUNT(*)符合条件的记录行数统计订单数、用户行数 COUNT(phone)phone 非 NULL 的记录数统计手机号已填写人数 COUNT(DISTINCT user_id)去重后的用户数统计独立用户、活跃用户 我建议把“总记录数”和“有效字段数”同时输出,避免把数据覆盖率误当成记录量: SELECT COUNT(*) AS total_users, COUNT(phone) AS users_with_phone, COUNT(phone) * 1.0 / NULLIF(COUNT(*), 0) AS phone_fill_rate FROM users;

还要注意 COALESCE 的使用边界。COALESCE(amount, 0) 适合把缺失金额转为零,但不适合无条件处理所有 NULL,因为缺失和零在业务上可能完全不同。库存为零表示没有库存,库存为 NULL 可能表示系统尚未同步,这两个状态不应被混为一谈。

3. WHERE 和 HAVING 有什么区别,为什么有些聚合条件只能写在 HAVING 中?

我想查询“订单金额超过 500 元的用户”,同时还要筛选“累计消费超过 2,000 元的人”。我把两个条件都写进 WHERE 后报错了,不知道什么时候该用 WHERE,什么时候该用 HAVING。

最简单的判断方法是:WHERE 过滤明细行,HAVING 过滤分组后的结果。SUM、COUNT、AVG 等聚合函数是在 GROUP BY 之后才产生的,因此“累计消费超过 2,000 元”这个条件不能在聚合发生前由 WHERE 判断。

例如,查询累计消费超过 2,000 元的用户,应写成:

SELECT user_id, SUM(order_amount) AS total_amount FROM orders WHERE order_status = 'paid' GROUP BY user_id HAVING SUM(order_amount) > 2000;

这里 WHERE 先排除未支付订单,HAVING 再对支付订单按用户汇总并筛选。这个顺序不仅影响语法,也影响业务口径。如果把 order_status 条件放错位置,可能把取消订单计入消费金额。“单笔订单超过 500 元”和“累计消费超过 2,000 元”是两个不同层级的条件。

前者属于行级过滤:

SELECT user_id, SUM(order_amount) AS total_amount FROM orders WHERE order_amount > 500 GROUP BY user_id HAVING SUM(order_amount) > 2000;

但这条 SQL 的含义是“只把单笔超过 500 元的订单纳入累计金额”。

如果你的真实需求是“用户只要有一笔订单超过 500 元,同时他的全部支付订单累计超过 2,000 元”,就不能简单把条件放在同一层,而应使用条件聚合:

SELECT user_id, SUM(order_amount) AS total_amount, MAX(order_amount) AS max_order_amount FROM orders WHERE order_status = 'paid' GROUP BY user_id HAVING MAX(order_amount) > 500 AND SUM(order_amount) > 2000;

需求过滤位置表达方式 只统计已支付订单WHEREorder_status = 'paid' 剔除单笔金额过低的订单WHEREorder_amount > 500 筛选累计金额超过 2,000 的用户HAVINGSUM(order_amount) > 2000 筛选至少有 3 笔订单的用户HAVINGCOUNT(*) >= 3 实际工作中,我会先用 SELECT 查看过滤后的明细,再执行 GROUP BY,最后加 HAVING。

这样能把“数据被 WHERE 排除了”与“聚合结果没有达标”区分开,排错速度通常比直接改一条很长的 SQL 快得多。

4. SQL 查询很慢时,初学者应该先检查哪里,而不是盲目加索引?

我写了一条按日期、用户和状态筛选的查询,在几十万行数据上还能运行,换到几千万行后就明显变慢。我看到很多教程一上来就建议加索引,但我不知道怎样判断真正的瓶颈,也担心索引越多越好。

查询变慢时,第一步不是马上加索引,而是确认数据库到底扫描了多少行、连接产生了多少中间结果,以及过滤条件是否真正命中了索引。索引只能改善部分访问路径,无法修复重复 JOIN、低选择性条件或函数包裹字段等问题。先用执行计划观察访问方式。

不同数据库的命令不同,但核心都在看是否出现全表扫描、扫描行数远高于返回行数、连接顺序不合理,以及排序或临时表占用过大。

例如,查询最近 30 天的已支付订单: EXPLAIN

SELECT order_id, user_id, order_amount FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '30 days' AND order_status = 'paid';

一个常见坑是对索引字段使用函数: WHERE DATE(created_at) = '2025-01-01'这种写法可能让数据库无法直接利用 created_at 的范围索引。

更适合改成半开区间: WHERE created_at >= '2025-01-01 00:00:00' AND created_at 日期范围写成“左闭右开”还有一个好处:不会因为时间精度不同,把当天最后一秒或毫秒数据漏掉。相比 BETWEEN,它在处理时间字段时通常更容易表达清楚。

索引列顺序也不能凭感觉决定。假设查询经常同时使用 status、created_at 和 user_id,最终索引顺序要结合数据分布和查询模式测试。status 只有“已支付、取消、待支付”几种值,选择性可能很低,把它单独作为首列未必有效;

created_at 的时间范围和 user_id 的等值筛选可能更有价值。

排查现象优先检查不要先做的事 返回 100 行却扫描千万行过滤字段、函数、索引命中情况直接增加多个索引 JOIN 后行数暴增连接键唯一性和表粒度用 DISTINCT 掩盖重复 排序耗时明显ORDER BY 字段、数据量、临时表盲目扩大数据库配置 索引很多但写入变慢重复索引和低收益索引认为索引越多越好 我的实际判断顺序是“先减少数据,再优化访问,再考虑结构调整”:先删除不必要的字段和明细范围,确认 WHERE 条件和 JOIN 逻辑正确;

其次查看执行计划和索引命中;最后才评估复合索引、分区或汇总表。因为如果 SQL 本身把 1 亿行错误连接成 10 亿行,加索引往往只是让错误发生得稍微快一点。

核心关键词

读者评论

韩诗涵

作为一个自学SQL的新手,很认同文章说的“先建立提问顺序”这个观点。以前总是先记语法,一到实际业务就不知道怎么组合。特别是执行顺序那部分,解决了我在WHERE里用别名的困惑。文章用实际查询统计来说明哪些语法块最关键,很有说服力。

许欣然

带过数据分析团队,文章提到的“8个语法块覆盖92%查询”这个结论很真实。我们平时培训新人确实容易平均用力,其实应该先打好核心语法基础,再根据业务需求学窗口函数和优化。建议新手按文章思路来,会少走很多弯路。

赵泽宇

作为一个写SQL多年的开发,部分同意作者的观点。入门确实不用啃完600页教材,但窗口函数在实际分析中也很常用,文章说只在9%的查询里出现可能取决于业务类型。不过整体思路是对的,执行顺序和提问顺序才是分析思维的核心。

免责申明:本文内容通过AI工具匹配关键字智能整合而成,仅供参考,帆软及九数云不对内容的真实、准确或完整作任何形式的承诺。如有任何问题或意见,您可以通过联系jiushuyun@fanruan.com进行反馈,九数云收到您的反馈后将及时处理并反馈。
咨询方案
咨询方案二维码

扫码咨询方案

热门产品推荐

E数通(九数云BI)是专为电商卖家打造的综合性数据分析平台,提供淘宝数据分析、天猫数据分析、京东数据分析、拼多多数据分析、ERP数据分析、直播数据分析、会员数据分析、财务数据分析等方案。自动化计算销售数据、财务数据、绩效数据、库存数据,帮助卖家全局了解整体情况,决策效率高。

相关内容

查看更多
数据分析实战抖音小店,抖店运营数据分析

数据分析实战抖音小店,抖店运营数据分析

数据分析实战抖音小店,抖店运营数据分析 上周,一个做中老年女装的朋友发来一份30天经营报表,问我:为什么流量降 […]
数据分析实战公关案例,舆情事件应对分析

数据分析实战公关案例,舆情事件应对分析

2023年7月,我接手了一家消费品牌的产品安全舆情事件。当时距离热搜发酵已经过去14小时,会议室桌上摆着四份共 […]
数据分析实战独立站,独立站流量转化分析

数据分析实战独立站,独立站流量转化分析

我接手过一个客单价1280元的瑜伽用品独立站,月流量稳定在3.2万,但60天购买转化率只有0.34%。运营团队 […]
数据分析实战短视频案例,短视频爆款分析

数据分析实战短视频案例,短视频爆款分析

短视频运营圈里有一个被说烂了的问题:爆款到底能不能复制?我过去的回答是“能,但不能靠玄学”。2023年春天,我 […]
数据分析实战复盘,618 大促活动效果分析

数据分析实战复盘,618 大促活动效果分析

618结束后的第一周,很多团队的数据分析其实比大促本身更忙。我见过不少团队把GMV拉到目标值的105%,以为大 […]

让电商企业精细化运营更简单

整合电商全链路数据,用可视化报表辅助自动化运营

让决策更精准