Skip to content
知识

/knowledge/sql-querying-data

SQL 与数据查询

几乎每一次分析都以同样的方式开始:从数据库里取出对的那些行。SQL 是那门已有五十年历史、却仍然做得最好的语言,而把熟练与笨拙区分开来的那少数几个想法,值得好好弄懂。

学于
SQL 与数据查询基础 · 每日工具
时间
数据科学 · 墨尔本大学
应用于
每一次分析都从这里开始
阅读 / 复习
约 15 分钟阅读2026-06-26

大多数数据分析都始于一个看似简单的问题:我该如何从数据库里恰好取出我需要的那些行?这个答案, 五十年来且还在继续,是 SQL——结构化查询语言。它活得比无数更时髦的工具都长,因为它 在某件根本性的事情上做对了;而对一名分析师而言,它是所有技能里用得最多的:在你建模、可视化、 或报告任何东西之前,你都得先查询它。

这一页是干活的分析师的 SQL——不是一份语法参考,而是把一个与语言搏斗的人、和一个对它流利的人 区分开来的那少数几个想法:一个查询实际上如何执行、连接到底做什么、窗口函数这一现代 超能力,以及那个至少会逮到每个人一次的陷阱(NULL)。它是数据库系统页的实用伴侣——那一页是理论,这一页是技能。

01

为什么 SQL 历久不衰

SQL 的长寿来自一个设计选择:它是声明式的。你描述你想要什么,而非如何得到它。你不写遍历行的循环,也不指定该用哪个索引——你陈述你想要的结果,而数据库的查询优化器会找出产生它的最高效方式。

那是一种深刻的关注点分离。你的查询保持为一个清晰的意图陈述,而引擎处理那些杂乱的机制;同样的 查询,在数据从数千行长到数十亿行时仍然能用。这也是为什么 SQL 读起来几乎像英语——一个长处,它 藏起了初学者会弄错的那一件事,也就是下一节。

02

核心动词

一个查询的主干,是一小组子句,每一个做一件事。一个把它们全用上的查询:

SELECT   department, COUNT(*) AS staff
FROM     employees
WHERE    start_date >= '2020-01-01'
GROUP BY department
HAVING   COUNT(*) > 5
ORDER BY staff DESC;
  • SELECT——要返回哪些列(以及计算出来的值)。
  • FROM——从哪个(些)表里拉取。
  • WHERE——在分组之前过滤单独的行。
  • GROUP BY——把行折叠成组以做聚合(COUNT、SUM、AVG)。
  • HAVING——过滤那些(在聚合之后)——它与 WHERE 的区别,不断地把人 绊倒。
  • ORDER BY——对最终结果排序。

03

它真正的运行顺序

这是关于 SQL 最能让人豁然开朗的一个事实:它并不按你书写的顺序执行。你先写 SELECT,但数据库几乎最后才运行它。逻辑执行顺序是:

FROM1WHERE2GROUP BY3HAVING4SELECT5ORDER BY6
SQL 的逻辑执行顺序。你先写 SELECT,但它第六个才运行——FROM 与 WHERE 先挑出并过滤行,然后分组,然后 SELECT 计算它的列,然后 ORDER BY 排序。这个顺序,解释了那些否则显得任意的规则。

这个顺序不是冷知识——它解释了那些否则显得任意的规则。为什么你不能在 WHERE 里用一个 SELECT 别名?因为 WHERE 在 SELECT 存在之前就运行了。为什么 WHERE 过滤行、而 HAVING 过滤组?因为 WHERE 在分组之前运行、HAVING 在 之后。为什么一个窗口函数不能放进 WHERE?同样的原因——它在 SELECT 处才被计算, 太晚了,没法据以过滤(把它包进一个 CTE 里,在外面过滤)。把这个顺序内化,一打 「坑」就变得显而易见。

04

连接:合并表

数据住在分开的表里(顾客在这边,订单在那边),而连接按一个共享的键,把它们重新 缝合起来。你需要的四种:

  • INNER JOIN——只要在两个表里都匹配的行。
  • LEFT JOIN——左表的每一行,加上右表的匹配(没有匹配处填 NULL)。分析师的 主力——「所有顾客,连同他们的订单(如果有)」。
  • RIGHT JOIN——镜像(很少需要;把表对调一下即可)。
  • FULL OUTER JOIN——两个表的每一行,能匹配处就匹配。

05

窗口函数:现代的超能力

窗口函数是把 SQL 从一门检索语言变成一门分析语言的那个特性。一个普通的聚合(GROUP BY)把行折叠成一个汇总行。一个窗口函数则跨一组相关的行进行计算,同时保留每一行——于是你可以把一个累计总和、一个排名、或者「与上个月相比」就放在每一 条记录的旁边。

SELECT  month, revenue,
        SUM(revenue) OVER (ORDER BY month)          AS running_total,
        revenue - LAG(revenue) OVER (ORDER BY month) AS change_vs_prev,
        RANK() OVER (ORDER BY revenue DESC)         AS rank
FROM    monthly_sales;

OVER (...) 子句定义了要在其上计算的那个行的「窗口」——PARTITION BY 切成若干组,ORDER BY 在组内排序。累计总和、组内排名、环比变化、移动平均、「每类别 前 N 名」——所有那些过去需要别扭的自连接的问题,都变成干净的一行。它们在 SELECT 这一步才被计算,而这正是为什么你不能直接据以过滤(又回到了执行顺序)。

06

CTE:可读的、分层的逻辑

真实的问题需要好几步,而写它们的错误方式,是一座要从里往外读的嵌套子查询的金字塔。一个公用表表达式WITH 子句)给每一步命名,好让查询像一份食谱那样从上 读到下:

WITH recent AS (
    SELECT * FROM orders WHERE order_date >= '2026-01-01'
),
by_customer AS (
    SELECT customer_id, SUM(amount) AS total
    FROM recent
    GROUP BY customer_id
)
SELECT * FROM by_customer WHERE total > 1000;

每个 CTE 都是一个有名字的、可复用的构件。查询于是变成一连串清晰的阶段,而非一团乱麻——更容易 读、调试、并交给别人。对一名查询必须能被别人理解并 重跑的分析师来说,这种可读性不是奢侈品。

07

NULL 陷阱

那个最终会逮到每个人的 bug:在 SQL 里,NULL 不是指零或空——它是指未知。 而因为「未知」会感染任何比较,SQL 运行在三值逻辑之上:TRUE、FALSE,以及 UNKNOWN。

08

它在我工作中的体现

09

60 秒回顾

逻辑执行顺序的取景、窗口函数的位置,以及 NULL 三值逻辑的告诫,反映了当前的 SQL 参考文献以及 课程。