很多数据库技术文章都有一个共同问题:
SQL 是静态的。
作者贴出一段代码,读者只能“看”,却很难真正理解 SQL 在执行时发生了什么。
而 SQL 本质上是一种行为型语言:
只有执行、修改、对比结果,才能真正理解它的语义。
这篇文章尝试用一种更实用的方法:
把 Oracle SQL 放到 FreeSQL 可运行环境中,让读者可以直接执行、修改并观察结果变化。
我们用一个非常经典但经常被误解的主题作为案例:
Top-N 查询与窗口函数。
为什么可交互 SQL 比静态示例更有教学价值
在数据库教学中,很多概念其实很难靠“阅读代码”理解。
例如下面这几种典型场景:
语义变化必须通过执行观察
例如:
FETCH FIRST 10 ROWS ONLY
如果改成:
FETCH FIRST 5 ROWS ONLY
语法没有变化,但结果集立即变化。
只有实际运行,读者才能真正理解 SQL 语义与结果之间的关系。
窗口函数必须通过结果理解
例如:
ROW_NUMBER()
SUM() OVER()
很多文章会解释语法规则,但读者依然会困惑:
- 为什么需要 PARTITION BY
- 排名是如何生成的
- 累计值为什么会变化
这些问题只有 跑出结果之后对比,才会真正理解。
技术文章的可信度更高
可执行 SQL 的另一个优势是:
读者可以复现。
不是“作者说可以运行”,而是:
我已经执行过,并且给出了结果摘要。
这对于数据库技术文章来说非常重要。
场景拆分:全局 Top-N vs 分组 Top-N
在业务系统中,经常会遇到两种类似但完全不同的问题:
问题一
全公司薪资最高的前 N 人。
问题二
每个部门薪资最高的前 N 人。
这两个问题的 SQL 写法完全不同。
语句一:全局 Top-N
查询薪资最高的前 10 名员工。
SELECT employee_id, last_name, salary
FROM hr.employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
这里的逻辑非常直接:
- 全表排序
- 取前 10 行
回答的问题是:
整个公司谁的薪资最高
语句二:每部门 Top-3
如果需求变成:
每个部门薪资最高的 3 个人
就不能简单使用 FETCH FIRST,而需要使用 窗口函数。
WITH ranked_data AS (
SELECT
department_id,
employee_id,
last_name,
salary,
ROW_NUMBER() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS rn
FROM hr.employees
WHERE department_id IS NOT NULL
)
SELECT department_id, employee_id, last_name, salary, rn
FROM ranked_data
WHERE rn <= 3
ORDER BY department_id, rn;
这里发生了三件事情:
PARTITION BY department_id
把数据按部门划分
ORDER BY salary DESC
每个部门内部排序
ROW_NUMBER()
生成部门内部排名
最后只保留 排名 ≤ 3 的员工。
语句三:部门薪资概览
有时候我们并不关心个人排名,而是希望从管理视角观察部门结构。
SELECT
department_id,
COUNT(*) AS emp_count,
ROUND(AVG(salary), 2) AS avg_salary,
MAX(salary) AS max_salary
FROM hr.employees
GROUP BY department_id
ORDER BY avg_salary DESC;
这个查询可以快速回答三个问题:
- 部门人数
- 平均薪资
- 最高薪资
适合用于 部门横向对比分析。
语句四:窗口累计分析
窗口函数还可以用于观察 高薪群体对薪资总额的贡献。
SELECT
employee_id,
last_name,
salary,
SUM(salary) OVER (ORDER BY salary DESC) AS running_total
FROM hr.employees
ORDER BY salary DESC
FETCH FIRST 15 ROWS ONLY;
这里的 SUM() OVER() 表示:
按照薪资排序进行累计计算。
例如:
24000
24000 + 17000
24000 + 17000 + 17000
可以直观看到 高薪员工对薪资总额的累计贡献。
实测验证结果(已执行)
为了保证 SQL 的可复现性,以上语句已经在 FreeSQL 23ai 环境中执行验证。
Q1 连通性验证
SELECT 'Hello FreeSQL' AS msg,
TO_CHAR(SYSDATE,'YYYY-MM-DD HH24:MI:SS') AS run_time
FROM dual;
结果:
MSG = Hello FreeSQL
RUN_TIME = 2026-03-09 13:30:56
Q2 全局 Top-N
返回 10 行,前几行为:
(100, King, 24000)
(101, Yang, 17000)
(102, Garcia, 17000)
(145, Singh, 14000)
(146, Partners, 13500)
Q3 每部门 Top-3
返回 25 行,示例:
部门 90
King / Yang / Garcia
部门 80
Singh / Partners / Errazuriz
Q4 部门聚合
返回 12 个部门统计结果,均薪最高的几个部门:
部门 90 19333.33
部门 110 10154
部门 70 10000
Q5 窗口累计
返回 15 行:
(100, King, 24000, 24000)
(101, Yang, 17000, 58000)
(102, Garcia, 17000, 58000)
(205, Higgins, 12008, 122516)
(108, Gruenberg, 12008, 122516)
写在最后
很多数据库教学内容的问题不在 SQL 本身,而在 表达方式。
静态代码块只能展示:
SQL 是怎么写的。
而可执行 SQL 展示的是:
SQL 执行后到底发生了什么。
对于 Top-N、窗口函数、排名分析 这类主题来说,可交互环境的教学价值明显更高。
技术文章如果能做到 可运行、可验证、可复现,读者的理解成本会大幅降低。
从数据库教育的角度看,这比单纯贴代码更有意义。




