SQL 数据分析 · 指南 · 2026年10月30日 · 13 分钟阅读

SQL 速查表(数据分析师版):常用语句和函数,每条都附可运行的例子

书桌上放着一页打印好的 SQL 速查表、一台显示查询语句的笔记本电脑和一杯咖啡

这一页汇总了数据分析师每天要用的 SQL:查询的各个子句、筛选条件、聚合函数、JOIN、CASE、NULL 处理、子查询和 CTE、窗口函数,以及最常用的日期和字符串函数。每一节都有一张简短的速查表和一个附带真实输出的例子,让你看到语法实际返回什么,而不是靠猜。

所有例子都在 Nomto SQL 课程的航空公司数据库上运行:104 个机场、9 种机型、2026 年 7 月 12 日至 9 月 10 日的 33,121 个航班、262,788 笔订单,金额单位为美元,时间为莫斯科时间。语法是 PostgreSQL。任何一个查询都可以原样粘贴到 Nomto 的 SQL 练习场里运行。

下文用到的表

表 一行代表 这里用到的列
airports 一个机场 airport_code、airport_name、city、timezone
aircrafts 一种机型 aircraft_code、model、range(公里)
flights 某一天的一次起飞 flight_id、flight_no、scheduled_departure、actual_departure、departure_airport、arrival_airport、status、aircraft_code
bookings 一笔订单 book_ref、book_date、total_amount
tickets 订单里的一位乘客 ticket_no、book_ref、passenger_name、contact_data(JSON)
ticket_flights 机票上的一个航段 ticket_no、flight_id、fare_conditions、amount

查询骨架:各子句及其执行顺序

子句 作用 执行顺序
SELECT 要返回的列和表达式 第 5
FROM / JOIN 数据从哪里来 第 1
WHERE 保留满足条件的行 第 2
GROUP BY 每组一行 第 3
HAVING 保留满足条件的组 第 4
ORDER BY 对结果排序 第 6
LIMIT 只保留前 N 行 第 7

你按表格第一列的顺序书写,数据库却按最后一列的顺序执行。这就是为什么 SELECT 里定义的别名能在 ORDER BY 里用,却不能在 WHERE 里用。

SELECT model, range
FROM aircrafts
WHERE range >= 5000
ORDER BY range DESC
LIMIT 3;
model range
Boeing 777-300 11100
Boeing 767-300 7900
Airbus A319-100 6700

筛选(WHERE)

运算符 例子 说明
=、<>、<、>= range >= 5000 文本用单引号
AND、OR、NOT a AND (b OR c) AND 优先级高于 OR:记得加括号
IN status IN ('Departed', 'Arrived') 多个 OR 的简写
BETWEEN x BETWEEN 1 AND 10 两端都包含
LIKE / ILIKE city LIKE 'Novo%' % 匹配任意多个字符,_ 匹配一个字符;ILIKE 不区分大小写
IS NULL actual_departure IS NULL 永远不要写 = NULL
SELECT COUNT(*) FILTER (WHERE status IN ('Departed', 'Arrived')) AS flown,
       COUNT(*) FILTER (WHERE scheduled_departure BETWEEN '2026-08-01' AND '2026-08-07 23:59:59') AS first_week_aug,
       COUNT(*) FILTER (WHERE actual_departure IS NULL) AS not_departed
FROM flights;
flown first_week_aug not_departed
16765 3798 16348

FILTER (WHERE ...) 让你在一个查询里放三个不同的筛选条件,每个 COUNT 只看自己的那部分行。日期范围用 >= '2026-08-01' AND < '2026-08-08' 比 BETWEEN 更稳妥,因为你不用记得写 23:59:59。

聚合函数

函数 返回
COUNT(*) 行数
COUNT(col) 非 NULL 值的个数
COUNT(DISTINCT col) 不重复的非 NULL 值的个数
SUM(col)、AVG(col) 总和、平均值(忽略 NULL)
MIN(col)、MAX(col) 最小值、最大值(日期和文本也适用)
SELECT COUNT(*)                                  AS bookings,
       COUNT(DISTINCT DATE_TRUNC('day', book_date)) AS days,
       SUM(total_amount)                         AS revenue,
       ROUND(AVG(total_amount), 2)               AS avg_booking,
       MIN(total_amount)                         AS min_b,
       MAX(total_amount)                         AS max_b
FROM bookings;
bookings days revenue avg_booking min_b max_b
262788 56 207669809.00 790.26 34.00 12045.00

GROUP BY 和 HAVING

SELECT 里的每一列都必须出现在 GROUP BY 中,或者放在聚合函数里。WHERE 在分组前筛选行;HAVING 在分组后筛选组。

SELECT city, COUNT(*) AS airports
FROM airports
GROUP BY city
HAVING COUNT(*) > 1
ORDER BY airports DESC, city;
city airports
Moscow 3
Ulyanovsk 2

两者的区别详见 GROUP BY 和 HAVING 的区别。

JOIN

JOIN 类型 保留
JOIN(INNER JOIN) 只保留两边都能匹配上的行
LEFT JOIN 左表的每一行;右边没有匹配时填 NULL
RIGHT JOIN LEFT JOIN 的镜像(很少用:直接把两张表对调)
FULL JOIN 两边的每一行
CROSS JOIN 所有行的两两组合
LEFT JOIN ... WHERE right.key IS NULL 没有匹配的行(反连接)

从另一张表查一个值:

SELECT f.flight_no, a.model
FROM flights f
JOIN aircrafts a ON a.aircraft_code = f.aircraft_code
WHERE f.flight_id = 1;
flight_no model
PG0405 Airbus A321-200

找出没有对应记录的行,这里是从不执飞的机型:

SELECT a.model
FROM aircrafts a
LEFT JOIN flights f ON f.aircraft_code = a.aircraft_code
WHERE f.flight_id IS NULL;
model
Airbus A320-200

要记住的陷阱:一行匹配到多行时,它会按匹配次数重复。把 bookings 和 tickets JOIN 之后再对 bookings.total_amount 求和,得到的是 343,206,192,而不是正确的 207,669,809,因为有多张机票的订单被重复计算了。SQL JOIN 用法详解 详细讲了这种行数膨胀。

CASE:条件判断

CASE WHEN condition1 THEN value1
     WHEN condition2 THEN value2
     ELSE default
END

条件从上到下依次检查,第一个为真的条件生效,所以把范围最窄的放在最前面。

SELECT CASE WHEN total_amount >= 1000 THEN 'large'
            WHEN total_amount >= 300  THEN 'medium'
            ELSE 'small'
       END AS size,
       COUNT(*) AS bookings
FROM bookings
GROUP BY size
ORDER BY bookings DESC;
size bookings
medium 130009
small 68305
large 64474

SUM(CASE WHEN ... THEN 1 ELSE 0 END) 是按条件计数的通用写法;在 PostgreSQL 里 COUNT(*) FILTER (WHERE ...) 效果相同。

NULL 处理

表达式 作用
col IS NULL / IS NOT NULL 判断 NULL 的唯一正确方式
COALESCE(a, b, ...) 第一个非 NULL 的值
NULLIF(a, b) a = b 时返回 NULL,否则返回 a(常用来避免除以零)
COUNT(col) 跳过 NULL;COUNT(*) 不跳过
SELECT flight_no, scheduled_departure, status,
       COALESCE(CAST(actual_departure AS VARCHAR), 'not departed') AS actual
FROM flights
WHERE flight_no = 'PG0405'
  AND scheduled_departure BETWEEN '2026-08-10' AND '2026-08-13'
ORDER BY scheduled_departure;
flight_no scheduled_departure status actual
PG0405 2026-08-10 09:35:00 Arrived 2026-08-10 09:37:00
PG0405 2026-08-11 09:35:00 Arrived 2026-08-11 09:38:00
PG0405 2026-08-12 09:35:00 On Time not departed

一个安全的百分比写法,表为空时返回 NULL 而不是报错:

SELECT ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'Cancelled') / NULLIF(COUNT(*), 0), 2) AS cancelled_pct
FROM flights;
cancelled_pct
1.25

要写 100.0,不要写 100:两个整数相除时,有些数据库会做整数除法,结果是 0。

子查询和 CTE

子查询就是嵌在另一个查询里的查询。高于平均金额的订单数:

SELECT COUNT(*) AS above_avg
FROM bookings
WHERE total_amount > (SELECT AVG(total_amount) FROM bookings);
above_avg
87224

CTE(WITH)给子查询起一个名字,让查询可以从上往下读。先算每天的航班数,再算这些日航班数的最小值、平均值和最大值:

WITH daily AS (
  SELECT CAST(scheduled_departure AS DATE) AS day, COUNT(*) AS flights
  FROM flights
  GROUP BY day
)
SELECT MIN(flights) AS min_day, ROUND(AVG(flights), 1) AS avg_day, MAX(flights) AS max_day
FROM daily;
min_day avg_day max_day
493 543 568

需要聚合两次(日计数的平均值、占总数的比例),或者查询超过一屏时,就用 CTE。

窗口函数

窗口函数为每一行加上一个值,但不合并行。结构是 function() OVER (PARTITION BY ... ORDER BY ... frame)。

函数 用途
ROW_NUMBER() 分组取前 N、去重
RANK()、DENSE_RANK() 有并列的排名(RANK 会跳号,DENSE_RANK 不跳号)
LAG(col)、LEAD(col) 上一行 / 下一行的值
SUM(col) OVER (ORDER BY ...) 累计求和
AVG(col) OVER (ORDER BY ... ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) 7 行移动平均
SUM(col) OVER () 在每一行旁边放总计(用来算占比)

一个查询里同时做环比(和前一天比较)和累计求和:

SELECT CAST(book_date AS DATE) AS day,
       COUNT(*) AS bookings,
       LAG(COUNT(*)) OVER (ORDER BY CAST(book_date AS DATE)) AS prev_day,
       SUM(COUNT(*)) OVER (ORDER BY CAST(book_date AS DATE)) AS running_total
FROM bookings
GROUP BY day
ORDER BY day
LIMIT 4;
day bookings prev_day running_total
2026-06-17 3 NULL 3
2026-06-18 7 3 10
2026-06-19 20 7 30
2026-06-20 74 20 104

用 ROW_NUMBER 取每组第一名:每种机型起飞最多的机场。

SELECT *
FROM (
  SELECT a.model, f.departure_airport, COUNT(*) AS flights,
         ROW_NUMBER() OVER (PARTITION BY a.model ORDER BY COUNT(*) DESC) AS rn
  FROM flights f
  JOIN aircrafts a USING (aircraft_code)
  GROUP BY a.model, f.departure_airport
) t
WHERE rn = 1
ORDER BY flights DESC
LIMIT 4;
model departure_airport flights rn
Sukhoi Superjet-100 SVO 1011 1
Bombardier CRJ-200 DME 829 1
Airbus A321-200 LED 793 1
Cessna 208 Caravan KJA 488 1

不能在同一个查询的 WHERE 里筛选窗口函数的结果,所以要套一层子查询。完整讲解见 SQL 窗口函数详解 和 累计求和。

日期和时间

表达式 结果
CAST(ts AS DATE) 去掉时间部分
DATE_TRUNC('month', ts) 当月的第一时刻(也可以用 'week'、'day'、'hour')
EXTRACT(MONTH FROM ts) 月份数字;也可以取 YEAR、DAY、HOUR、ISODOW(1 = 星期一)
ts + INTERVAL '30 days' 日期加减
date1 - date2 两个日期相差的天数
EXTRACT(EPOCH FROM ts1 - ts2) / 60 两个时间戳相差的分钟数

每月航班数:

SELECT DATE_TRUNC('month', scheduled_departure) AS month, COUNT(*) AS flights
FROM flights
GROUP BY month
ORDER BY month;
month flights
2026-07-01 00:00:00 10826
2026-08-01 00:00:00 16854
2026-09-01 00:00:00 5441

这里的 7 月和 9 月都不是完整月份(时刻表从 7 月 12 日到 9 月 10 日),比较月份时值得明确说出来。

日期加减:

SELECT book_ref, book_date,
       book_date + INTERVAL '30 days' AS plus_30,
       '2026-08-11'::DATE - CAST(book_date AS DATE) AS days_ago
FROM bookings
ORDER BY book_ref
LIMIT 2;
book_ref book_date plus_30 days_ago
00000F 2026-07-01 03:12:00 2026-07-31 03:12:00 41
000012 2026-07-10 09:02:00 2026-08-09 09:02:00 32

起飞延误最长的航班,以分钟计:

SELECT flight_no, scheduled_departure, actual_departure,
       ROUND(EXTRACT(EPOCH FROM actual_departure - scheduled_departure) / 60) AS delay_min
FROM flights
WHERE status = 'Arrived'
ORDER BY delay_min DESC
LIMIT 3;
flight_no scheduled_departure actual_departure delay_min
PG0589 2026-07-25 15:30:00 2026-07-25 20:07:00 277
PG0164 2026-07-25 15:25:00 2026-07-25 19:53:00 268
PG0364 2026-07-15 11:45:00 2026-07-15 16:12:00 267

完全没有数据的日子(某天零订单,在 GROUP BY 结果里根本不会出现)怎么处理,见 date spine 指南。

字符串

函数 例子 结果
|| 'PG' || '0405' PG0405
UPPER、LOWER LOWER('SVO') svo
LENGTH LENGTH('Vnukovo') 7
TRIM TRIM(' SVO ') SVO
LEFT(s, n)、SUBSTRING(s, start, len) LEFT('Moscow', 3) Mos
POSITION(sub IN s) POSITION('/' IN 'Europe/Moscow') 7
REPLACE(s, from, to) REPLACE('Asia/Ust-Nera', '-', ' ') Asia/Ust Nera
SPLIT_PART(s, sep, n) SPLIT_PART('IVAN PETROV', ' ', 2) PETROV
json ->> 'key' contact_data ->> 'phone' 以文本形式返回的值

乘客姓名以「名 姓」的形式全大写存储,联系方式是 JSON:

SELECT passenger_name,
       LOWER(SPLIT_PART(passenger_name, ' ', 2)) AS last_lower,
       contact_data ->> 'phone' AS phone
FROM tickets
ORDER BY ticket_no
LIMIT 3;
passenger_name last_lower phone
VALERIY TIKHONOV tikhonov +70127117011
EVGENIYA ALEKSEEVA alekseeva +70378089255
ARTUR GERASIMOV gerasimov +70760429203

拼一个标签,再在某个字符处截断字符串:

SELECT airport_name || ' (' || airport_code || ')' AS label,
       SUBSTRING(timezone, 1, POSITION('/' IN timezone) - 1) AS region
FROM airports
WHERE city = 'Moscow'
ORDER BY airport_code;
label region
Domodedovo (DME) Europe
Sheremetyevo (SVO) Europe
Vnukovo (VKO) Europe

更多函数见 SQL 字符串函数。

这张速查表帮你避开的错误

方言差异

这里的一切都是 PostgreSQL 语法。大部分在其他数据库里也一样;常见的差异如下:

任务 PostgreSQL MySQL SQL Server
前 N 行 LIMIT 10 LIMIT 10 TOP 10 / FETCH FIRST 10 ROWS ONLY
拼接 a || b CONCAT(a, b) a + b 或 CONCAT(a, b)
截断日期 DATE_TRUNC('month', d) DATE_FORMAT(d, '%Y-%m-01') DATETRUNC(month, d)(2022 及以上)
不区分大小写的 LIKE ILIKE LIKE(取决于排序规则) LIKE(取决于排序规则)
条件计数 COUNT(*) FILTER (WHERE ...) SUM(CASE WHEN ... THEN 1 ELSE 0 END) SUM(CASE WHEN ... THEN 1 ELSE 0 END)

怎么用这张速查表

工作时把它开着,但别试图背下来。语法是在解决你关心的问题时用熟的。在 Nomto 的 SQL 练习场里,上面每个查询都跑在同一个航空公司数据库上,改一个筛选条件或分组列,看看结果怎么变。如果你刚入门,SQL 基础查询语句 一步步讲解了本页的前半部分,SQL 练习题 则提供了练习后半部分的题目。

常见问题

数据分析师需要掌握哪些 SQL?

要熟练掌握:SELECT、WHERE、GROUP BY、HAVING、JOIN、CASE、NULL 处理、子查询和 CTE,以及日期函数。然后是窗口函数(ROW_NUMBER、LAG、累计求和),大多数数据分析师面试都会考。索引、存储过程和数据库管理对这个岗位来说就没那么重要了。

这张速查表适用于 MySQL 和 SQL Server 吗?

子句、JOIN、聚合函数、CASE、COALESCE、子查询、CTE 和窗口函数的用法都一样。日期函数、字符串拼接和 LIMIT 有差异;上面的方言对照表列出了常见情况。

子查询和 CTE 有什么区别?

两者作用相同。CTE 给子查询起了个名字并把它挪到最前面,让长查询可以分步阅读。哪个好读就用哪个;只要有两步或以上,通常是 CTE 更好读。

不安装数据库能运行这些例子吗?

能。打开 Nomto 的 SQL 练习场,粘贴查询,就能在浏览器里对同一份航空公司数据运行。

其他语言: English, Español, Português (Brasil), Tiếng Việt