ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SQL窗口函数速查表:三类函数、OVER框架与易错点复习

SQL窗口函数速查表:三类函数、OVER框架与易错点复习

简介:《SQL窗口函数速查表》PDF面向数据库管理员、数据分析师、开发人员及SQL进阶学习者,帮助解决窗口函数概念抽象、语法参数易混、实际查询中难以快速选用的问题。资源包仅含1个PDF文件,约841KB,以单份速查表集中整理核心内容,便于按需检索和快速浏览。内容覆盖窗口函数基本概念、OVER子句、PARTITION BY、ORDER BY与frame子句的语法结构,分类说明ROW_NUMBER、RANK、DENSE_RANK、LEAD、LAG及SUM、AVG、COUNT等函数的差异,并配有示例代码和返回结果说明。结合报表生成、数据探索、异常值识别与教学研究等场景,速查表给出应用思路,读者可定位排名计算、前后行取值和窗口聚合操作。已有255人学习,适合作为日常查询构建与SQL进阶复习的案头参考。

1. 知识地图:窗口函数改的是「行数」,不是「分组」

「SQL窗口函数速查表」这类资料最容易踩的坑,是把二十来个函数平铺成一张对照表,扫一眼觉得全懂,合上就写不出来。真正值得先立起来的地图只有一条主线:GROUP BY 把多行压成一行,窗口函数把多行算成一个值、再原样贴回每一行——行数不变,这是它唯一的分水岭。沿着这条线,整块知识裂成三瓣:谁在排序(排名函数)、谁在跨行取数(位移函数)、谁在框内聚合(聚合函数开窗);而 OVER() 里的分组、排序、框架三层积木,决定这三瓣最终落在哪几行上。这份笔记适合已经会写 JOIN 和 GROUP BY、但一遇到「每组取最新一条」「算累计占比」就卡壳的人,也适合考前一晚想把碎片拼成整图的人。

2. 函数盘点:排名、位移、聚合三类的分工与边界

记函数名有一个很省力的线索:名字里带 RANK 的管名次,带 LEAD / LAG / FIRST / LAST / NTH 的管跨行取值,剩下的 SUM、AVG、COUNT、MIN、MAX 本来就是聚合函数,加个 OVER 就变成窗口版本。三类函数的差别不在语法,而在「它们对窗口内其他行的依赖方式」——排名函数依赖排序,位移函数依赖位置,聚合函数依赖框架。搞混这一点,写出来的语句不报错,但答案会悄悄偏掉,这种错误在复习阶段最难被自查出来,因为结果看起来总是「挺合理的」。

2.1 排名函数三兄弟:并列时谁让位、谁跳号

排名函数是速查表里最先被翻到的一页,也是最容易被当成「随便挑一个都行」的一页。三者的差别只在并列值上体现,而并列恰恰是真实数据里最常见的状态——销售额相同、分数相同、时间戳撞在同一秒。

函数并列时的行为名次序列示例适用判断
ROW_NUMBER()强行给出唯一序号,并列也分出先后1, 2, 3, 4只需要「挑出一条」,不关心名次语义
RANK()并列同名次,后续跳号1, 1, 3, 4需要「第几名」,允许并列占据同一名次
DENSE_RANK()并列同名次,后续不跳号1, 1, 2, 3需要「第几档 / 第几类」
NTILE(n)按顺序均分成 n 个桶1, 1, 2, 2分位数、分层抽样、等频分箱
-- 同一部门内按销售额降序,一次性看清三种排名的差异 SELECT emp_id, dept_id, sales, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY sales DESC) AS rn, RANK() OVER (PARTITION BY dept_id ORDER BY sales DESC) AS rk, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY sales DESC) AS drk FROM emp_sales;

这段语句的逻辑很直白:PARTITION BY dept_id 先把数据按部门切成互不干扰的窗口,每个部门内部独立编号;ORDER BY sales DESC 决定谁排第一。参数上要注意两点,一是 PARTITION BY 省略时整张结果集就是一个窗口,全表一起排名;二是 ORDER BY 里如果存在并列值,ROW_NUMBER 给出的具体序号由执行计划决定,不同次运行可能不一样。这个不确定性在「取最新一条」的场景里会直接翻车,后面第 5 章会单独拎出来讲。

NTILE 常被漏掉,但它在做分位数分析时非常顺手。它的参数是桶数而不是名次,不能整除时前面的桶会多分到一行,比如 10 行分 3 桶会得到 4、3、3 的分布,这一点在写分层抽样的时候必须记住,否则样本比例会对不上。

2.2 位移函数:LEAD / LAG 的偏移与默认值

位移函数解决的是「把上一行的值搬到这一行来比较」这个需求,环比、同比、连续登录天数、页面跳转路径都靠它。语法上它有第三个参数,这是速查表里最容易被跳过的一格。

-- 环比与下一期预取:注意第三个参数是越界时的兜底值 SELECT month_id, amount, LAG(amount, 1, 0) OVER (PARTITION BY region ORDER BY month_id) AS prev_amount, amount - LAG(amount, 1, 0) OVER (PARTITION BY region ORDER BY month_id) AS diff, LEAD(amount, 1, 0) OVER (PARTITION BY region ORDER BY month_id) AS next_amount FROM monthly_sales;

参数含义逐个数:第一个参数是要取值的表达式,可以是列,也可以是列参与的计算;第二个参数是偏移量,LAG 往「前」数第 n 行,LEAD 往「后」数第 n 行,方向记反是新手最常见的错;第三个参数是越界时的默认值,不写就默认 NULL。把默认值写成 0 以后,首行的 diff 会等于当月全值,看上去像「凭空多出一笔」,所以做差值的场景里更稳妥的做法是保留 NULL,让下游自行处理,或者干脆在展示层用 COALESCE 兜底。

还有一个限制值得单独记:第三个参数必须是常量或常量表达式,不能引用当前行的列。想做「缺失月份用上月值填充」这种逻辑,位移函数本身做不到,得先补齐时间维表再开窗,这是两个步骤的事。

2.3 聚合函数开窗:有没有 ORDER BY 是两种东西

同样是 SUM(...) OVER (...),里面有没有 ORDER BY,得到的是完全不同的结果。这是整份速查表里最值得画粗线的一条,因为它不报错、不警告,只是答案变了。

-- 不带 ORDER BY:整个分区算一个总量,用来做占比的分母 SELECT dept_id, emp_id, salary, SUM(salary) OVER (PARTITION BY dept_id) AS dept_total, ROUND(salary * 1.0 / SUM(salary) OVER (PARTITION BY dept_id), 4) AS ratio FROM emp_salary;
-- 带 ORDER BY:默认框架收缩为「分区起点到当前行」,自然形成累计 SELECT dept_id, emp_id, salary, SUM(salary) OVER (PARTITION BY dept_id ORDER BY emp_id) AS running_total FROM emp_salary;

第一段里,SUM 的窗口是整个部门,每一行拿到的分母都相同,除法算出来就是占比。第二段里加了 ORDER BY,框架的默认值从「整个分区」变成了「起点到当前行」,同一个 SUM 就变成了累计求和。参数上唯一变化的就是一个 ORDER BY 子句,语义却从「分组总量」跳到了「滚动累计」,这也是很多人写占比时莫名算出递增序列的原因。

COUNT 开窗还有一个高频用法:COUNT(*) OVER () 不写 PARTITION BY,直接拿到结果集总行数,一行代码就能算「占全表比例」。但如果想算 COUNT(DISTINCT 某列) OVER (),多数数据库会直接拒绝,替代方案是先在外面用 DISTINCT 去重,再套一层窗口,多一层子查询虽然啰嗦,但结果是对的。

3. 语法骨架:OVER 里的三层积木与执行顺序

窗口函数的语法外壳只有一对括号,但括号里的三段积木可以组合出七八种语义密度。复习的时候与其背语法图,不如记住三种典型密度:空 OVER() 是「全表一个窗口」,只有 PARTITION BY 是「分组内全量」,PARTITION BY 加 ORDER BY 是「分组内滚动」,三段全写才是「精确到行的滑动窗口」。绝大多数业务查询落在前三种,第四种主要集中在移动平均和滑动去重上。

3.1 三段积木:谁决定切分、谁决定顺序、谁决定范围

PARTITION BY 决定「窗口怎么切」,切完之后各窗口互不可见,这是窗口函数能做分组内排名的基础。ORDER BY 决定「窗口内怎么排」,它同时承担两个职责:给排名函数提供排序依据,以及在没有显式框架时充当默认框架的分界线。FRAME 决定「参与计算的到底是哪几行」,它是最精细也最少被用到的一层。

-- 三段积木齐全的完整骨架 SELECT dept_id, emp_id, salary, SUM(salary) OVER ( PARTITION BY dept_id -- 切分:每个部门一个窗口 ORDER BY emp_id -- 排序:窗口内按工号排 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW -- 范围:当前行及前两行 ) AS sum_last_3 FROM emp_salary;

这段里 SUM 的求和范围被限定在三行以内,得到的不是部门累计,而是一个滚动三行的局部和。参数上要留意,PARTITION BY 后面可以跟多个列,切分粒度就是这几列的组合,粒度选错会导致「同一组被拆成好几段」或者「不同组被并成一段」,两种错法都会让排名从 1 重新开始或跨组串号。

3.2 FRAME 子句:ROWS 与 RANGE 的差别在哪里

框架的完整写法是{ROWS | RANGE} BETWEEN 起点 AND 终点,起点可用的值有 UNBOUNDED PRECEDING、n PRECEDING、CURRENT ROW,终点可用的值有 CURRENT ROW、n FOLLOWING、UNBOUNDED FOLLOWING。有两组合法性限制要记:起点不能是 UNBOUNDED FOLLOWING,终点不能是 UNBOUNDED PRECEDING,写了会直接语法报错。

ROWS 和 RANGE 的差别不在关键字本身,而在「排序键有重复值时怎么算」。ROWS 按物理行数数,RANGE 按排序键的值数。举个具体例子:如果按日期排序,某天有两行记录,ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW走到第一行时只包含它自己,走到第二行时才把同一天的两行都算进去;而RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW在第一行时就已经把同一天的所有行全部包含进来,因为它们的排序键值相同。

OVER 中的内容默认框架结果形态
无 ORDER BY整个分区分区聚合值,每行相同
有 ORDER BY,无 FRAMERANGE UNBOUNDED PRECEDING TO CURRENT ROW累计,且把排序键相同的行一起纳入
显式 ROWS 框架按物理行数计算精确的滑动窗口

移动平均是最能体现框架价值的场景:

-- 7 行移动平均:当前行加前 6 行,共 7 行 SELECT dt, amount, AVG(amount) OVER (ORDER BY dt ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma_7 FROM daily_sales;

这里必须写 ROWS 而不是 RANGE,因为业务语义是「最近 7 条记录」,不是「最近 7 个不同的日期值」。如果 dt 有重复,RANGE 会把重复日期的所有行都卷进来,算出来的平均值跟预期差很远。参数上 6 PRECEDING 表示往前数 6 行,加上当前行正好 7 行,这个 off-by-one 是最常见的算术失误。

3.3 执行顺序与命名窗口:为什么它写不进 WHERE

窗口函数的求值时机在 GROUP BY 和 HAVING 之后、SELECT 列表求值阶段、ORDER BY 之前。这条执行顺序解释了一大堆「为什么不能这么写」:窗口函数不能出现在 WHERE 里,因为 WHERE 执行时窗口还没算出来;不能出现在 GROUP BY 和 HAVING 里,同理;想先过滤再开窗,只能把窗口计算包进子查询或 CTE,在外层筛。

-- 先开窗再筛选:直接写 WHERE rn = 1 会报错 WITH ranked AS ( SELECT user_id, order_id, amount, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders ) SELECT user_id, order_id, amount FROM ranked WHERE rn = 1;

CTE 里先把行号算出来,外层再按行号过滤,这是最通用的写法。部分较新的数据库提供了 QUALIFY 子句,可以直接在窗口计算之后过滤,省掉一层嵌套,但换一个环境就不可用,做跨库迁移的时候还是 CTE 更稳。

命名窗口是另一个常被忽略的语法糖,它把重复出现的 OVER 定义抽出来,改一处就能全部生效:

-- WINDOW 子句复用同一份窗口定义 SELECT dept_id, emp_id, salary, SUM(salary) OVER w AS running_total, AVG(salary) OVER w AS running_avg, RANK() OVER w AS rk FROM emp_salary WINDOW w AS (PARTITION BY dept_id ORDER BY emp_id);

参数上要注意,命名窗口定义在 FROM 之后、ORDER BY 之前,多个窗口用逗号分隔;引用已命名的窗口时一般不能再覆盖其中的 PARTITION BY,只能追加 ORDER BY 之类的子句,而且不同数据库对此的宽容度不一样,跨库时不要依赖这种追加写法。

4. 场景落地:从累计求和到分组 TopN 的四类模板

速查表的价值不在函数列表,而在「什么场景该翻哪一页」。把高频需求归成四类之后,基本能覆盖日常八成的窗口函数写法:滚动累计与移动平均、同比环比、分组 TopN 与去重取最新、占比与分母。这四类各自对应一组固定的函数组合,记住组合比记住函数更有用,因为真实需求往往是「在分组内、按时间排、取前 N 条、还要带上该组的占比」这种复合句。

4.1 累计求和与移动平均

累计求和是窗口函数最经典的入门场景,也是区分「会不会用」和「用得对不对」的分水岭。

-- 部门内按日期累计,同时给出占比与移动平均 SELECT dept_id, dt, amount, SUM(amount) OVER (PARTITION BY dept_id ORDER BY dt) AS running_total, AVG(amount) OVER (PARTITION BY dept_id ORDER BY dt ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS ma_3, ROUND(amount * 1.0 / SUM(amount) OVER (PARTITION BY dept_id), 4) AS ratio FROM dept_daily;

这段里三个窗口函数共用了同一个 PARTITION BY,但 ORDER BY 和 FRAME 各不相同:累计求和依赖默认框架,移动平均显式写了 3 行窗口,占比则完全不要 ORDER BY。参数上一个容易忽略的细节是乘法里的* 1.0,如果 amount 和 SUM 都是整型,直接相除会做整数截断,占比全变成 0 或 1,这种错误在排查时特别费时间,因为结果不为 NULL、也不报错,只是看起来「怪」。

4.2 同比环比与月份缺失

环比用 LAG 偏移 1 期,同比偏移 12 期,写法几乎一样,坑也都一样:数据里缺月份时,位移拿到的是「上一条存在的记录」而不是「上一个月」,跨年缺月时误差会翻倍。

-- 环比与同比,缺月时要先补时间维表再开窗 SELECT region, month_id, amount, LAG(amount, 1) OVER (PARTITION BY region ORDER BY month_id) AS prev_month, LAG(amount, 12) OVER (PARTITION BY region ORDER BY month_id) AS prev_year, ROUND((amount - LAG(amount, 1) OVER (PARTITION BY region ORDER BY month_id)) * 1.0 / LAG(amount, 1) OVER (PARTITION BY region ORDER BY month_id), 4) AS mom_rate FROM monthly_sales;

参数含义上,第二个参数 1 和 12 分别代表偏移几行,注意这里是「行」不是「月」,所以必须先保证时间序列连续,否则偏移 12 行不等于偏移 12 个月。补洞的做法是先用时间维表左连接事实表,把缺失月份的 amount 补成 0 或 NULL,再开窗,这样偏移才对齐。另一个细节是除零:首行 LAG 返回 NULL,整列 mom_rate 会变成 NULL,如果需要显示 0 或空字符串,得在展示层兜底。

4.3 分组 TopN 与去重取最新

「每个分类取销量前三」和「每个用户取最新一条订单」是同一类问题的两个变体,前者用 RANK 或 DENSE_RANK,后者用 ROW_NUMBER。

-- 分组 TopN:允许并列时用 DENSE_RANK,严格取 3 条用 ROW_NUMBER SELECT category, product, sales, rk FROM ( SELECT category, product, sales, DENSE_RANK() OVER (PARTITION BY category ORDER BY sales DESC) AS rk FROM product_sales ) t WHERE rk <= 3;
-- 去重取最新:ORDER BY 必须带上唯一键兜底 SELECT user_id, order_id, created_at FROM ( SELECT user_id, order_id, created_at, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, order_id DESC) AS rn FROM orders ) t WHERE rn = 1;

两段的关键差别在排序键的唯一性。TopN 场景里 sales 有并列很正常,用 DENSE_RANK 能保证「并列第三」的两条都被选进来;去重场景里如果只按 created_at 排序,同一秒创建的两条订单谁被选中是随机的,每次跑出来的结果可能不同,加上 order_id DESC 作为第二排序键才能稳定。参数上顺序很重要,主排序键在前、兜底键在后,顺序反了就等于没加。

4.4 占比与分母:三种求分母的写法

占比的本质是「当前行的值除以某个范围的总量」,分母的范围决定了用哪种写法。分区内占比用 PARTITION BY 版本的 SUM 开窗;全表占比用空的 OVER ();分组小计占大组比例则要嵌套一层聚合再开窗。

-- 三级分母:组内占比、全表占比、组占全表比例 SELECT dept_id, emp_id, salary, ROUND(salary * 1.0 / SUM(salary) OVER (PARTITION BY dept_id), 4) AS ratio_in_dept, ROUND(salary * 1.0 / SUM(salary) OVER (), 4) AS ratio_in_all, ROUND(SUM(salary) OVER (PARTITION BY dept_id) * 1.0 / SUM(salary) OVER (), 4) AS dept_share FROM emp_salary;

第三列dept_share是这一节的要点:它在同一个 SELECT 里既做了聚合又做了开窗,分母是全表的 SUM,分子是部门 SUM 再开窗。参数上不用 PARTITION BY 的 OVER () 表示「整个结果集一个窗口」,这个写法在算总占比时非常省事,但要确认结果集没有被外层 WHERE 提前裁剪,否则分母会跟着变小。

5. 易错点:结果悄悄偏掉的五种写法与补救

窗口函数的错误分两类,一类是直接报错,改起来快;另一类是不报错但答案错,这类才是复习时真正要盯住的。按经验排下来,最耽误时间的错误集中在「框架默认值」「排序键唯一性」「过滤位置」三处,它们共同的特点是语法完全合法,只是语义和直觉不一致。下面五条按「错在哪、为什么会漏、怎么补」逐条过一遍,每一条都能在速查表里找到对应的正确写法。

5.1 三条不会报错、但答案已经错了的写法

第一条:加了 ORDER BY 却以为还是分组总量。易漏点在于默认框架的隐式切换——OVER 括号里一旦出现 ORDER BY,框架就从「整个分区」收缩成「起点到当前行」。为什么漏:速查表的语法图通常只画 PARTITION BY 和 ORDER BY 两个框,框架那一行常常被省略成「可选」,读的时候容易当成不影响结果的装饰。怎么补:写聚合开窗前先问一句「我要的是分区总量还是累计」,要总量就别写 ORDER BY,要累计就显式写上ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,把默认值写死,别人读代码也不用猜。

第二条:PARTITION BY 的粒度跟业务粒度对不上。易漏点在于分组列选少了会跨组串号,选多了会组内重开。比如按「部门 + 月份」分区去算部门排名,实际得到的是每月部门内排名,每个月的第一名都会被重新算成 1。为什么漏:语句能跑通,排名序列每次都从 1 开始,看起来「挺规整」。怎么补:把 PARTITION BY 里的列逐个念一遍,对照需求里的「每组」到底指什么,粒度不确定时先用SELECT 分区列, COUNT(*) GROUP BY 分区列验证一下分出来多少组。

第三条:LAG 首行返回 NULL,参与算术后整列变 NULL。易漏点在于位移函数的边界行为,以及 NULL 在算术运算里的传染性。为什么漏:导数、比率这类计算经常写在一行里,首行结果为空时容易被误认为「本来就没有上期」。怎么补:要么在 LAG 的第三个参数里给默认值,要么在展示层用 COALESCE 兜底,并明确业务上首期该显示 0 还是空。做比率时还要额外防一手除零,分母为 NULL 或 0 都会让整列失真。

5.2 两条直接报错、或结果根本不可复现的写法

第四条:把窗口函数写进 WHERE 或 HAVING。易漏点在于执行顺序——窗口计算发生在 WHERE、GROUP BY、HAVING 之后。为什么漏:从需求到 SQL 的翻译几乎是逐句直译的,「取排名第一的」很自然就写成WHERE rn = 1,写完才发现报错。怎么补:包一层 CTE 或子查询,先算行号再过滤;用 QUALIFY 的写法虽然简洁,但跨库时不通用,复习阶段建议先掌握 CTE 版本。

-- 错误写法:窗口别名不能在 WHERE 中引用 -- SELECT * FROM t WHERE ROW_NUMBER() OVER (ORDER BY x) = 1; -- 正确写法:先开窗,再过滤 SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (PARTITION BY grp ORDER BY x) AS rn FROM t ) s WHERE rn = 1;

第五条:用 ROW_NUMBER 取最新,但排序键不唯一。易漏点在于并列时行号的分配由执行计划决定。为什么漏:单次运行结果看起来永远是对的,只有在数据量变大、并行度变化或者换了执行环境之后,同一份查询才会给出不同的行。怎么补:在 ORDER BY 末尾追加一个唯一键(主键、业务流水号都行)作为兜底排序,让名次分配有确定依据;同时把created_at这类可能存在同秒重复的时间列与唯一键组合使用。

提示:这五条里,第一条和第五条最值得在复习时默写一遍——它们不报错,只能靠刻意检查发现。

6. 复习清单:速查表的三遍过法与自测

速查表这种资料,第一遍通读基本留不下什么,真正起作用的是带着问题回查。我自己后来固定成三遍:第一遍只认函数名和分类,花二十分钟把三类函数的归属默写一遍,写不出来的就是盲区;第二遍抠 OVER 括号里的三段积木,专门练「同一段 SQL 改一个子句,结果会变成什么」,这一遍最能暴露默认框架的问题;第三遍套场景,把累计、环比、TopN、占比四个模板各手写一遍,不看速查表写完再对照。

复习轮次目标自查方式常见暴露点
第一遍:认函数三类函数各有哪些、差别在哪默写分类表并标注并列行为RANK 与 DENSE_RANK 混淆
第二遍:抠框架三层积木的语义与默认值同一查询改一处子句,预测结果加 ORDER BY 后误判为总量
第三遍:套场景四个模板能独立写出遮住答案手写,再逐行比对排序键不唯一、除零与 NULL

自测题比通读有效得多,下面这五道能覆盖大部分漏洞:分组内累计求和,但不加 ORDER BY 会得到什么;用 LAG 算同比,缺月时结果会偏成什么样;分组取前三条,并列第三该怎么处理;分组取最新一条,排序键要不要加唯一键;占比的分母用空 OVER () 和带 PARTITION BY 的 SUM 有什么区别。每道题的答案都能在对应的章节里找到,答不上来说明那一块还没真正过手。

-- 一份可以直接跑的自测脚本骨架,跑一遍比看十遍强 SELECT grp, id, val, SUM(val) OVER (PARTITION BY grp ORDER BY id) AS running_total, -- 累计 SUM(val) OVER (PARTITION BY grp) AS grp_total, -- 分组总量 LAG(val, 1) OVER (PARTITION BY grp ORDER BY id) AS prev_val, -- 位移 RANK() OVER (PARTITION BY grp ORDER BY val DESC) AS rk -- 排名 FROM demo_metrics;

这段脚本的价值在于它把四种语义放在同一个结果集里,一眼就能看出running_total和grp_total的差别,也能看出prev_val首行为什么一定是空。跑完再试着删掉 ORDER BY,观察累计是怎么退化成总量的,这个动作做过一次,以后写占比就不太会写错了。

有个血泪教训值得单独说:早些年我在做一份分组排名报表时,图省事只按时间戳排序取最新一条,测试环境数据量小、从来没出过问题,上线后数据一多就开始出现「同一条记录今天有明天没」的怪现象,排查了大半天才定位到排序键不唯一。从那以后,只要写 ROW_NUMBER 取首行,我每次都会强制检查一遍 ORDER BY 里有没有唯一键兜底,没有就补上。这个习惯看着啰嗦,但省下的排查时间远比写它的时间多。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表