必须先用ROW_NUMBER()生成逻辑行号再对行号取模,才能准确筛选查询结果集的奇偶行;直接对id或ROWNUM取模仅按字段值奇偶性过滤,无法保证按输出顺序的第1、3、5…行筛选。

MOD函数在SQL中如何判断奇偶行
SQL里没有“行号”这个天然概念,MOD本身只对数值取模,直接写 MOD(ROWNUM, 2) 或 MOD(id, 2) 看似简单,但结果取决于你用的是哪一列——用主键 id 可能跳号,用 ROWNUM(Oracle)或 ROW_NUMBER()(通用)才真正对应“第几行”。别默认表里 id 是连续的,生产环境几乎从不连续。
常见错误现象:SELECT * FROM t WHERE MOD(id, 2) = 1 筛出来的“奇数行”其实是 id 为 1、3、5… 的记录,不是查询结果集里的第1、3、5行。如果 id 缺失(比如删过数据),就会漏行甚至全空。
- 真正需要奇偶交替时,必须先生成逻辑行号,再套
MOD - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持
ROW_NUMBER() OVER(),这是跨库最稳的做法 - SQLite 不支持窗口函数,得靠子查询模拟行号(性能差,慎用于大表)
用ROW_NUMBER + MOD实现稳定交替筛选
核心思路:先用 ROW_NUMBER() 按指定顺序给结果编号,再用 MOD 判断奇偶。顺序必须显式声明,否则行号无意义——没 ORDER BY 的 ROW_NUMBER() 行为未定义。
示例(查奇数行,即第1、3、5…条):
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY created_at DESC) AS rn FROM orders ) t WHERE MOD(t.rn, 2) = 1;
注意点:
-
MOD(t.rn, 2) = 1→ 奇数行;MOD(t.rn, 2) = 0→ 偶数行 - Oracle 用户注意:
ROWNUM是执行时分配的伪列,不能在WHERE中直接和MOD连用(会报错或逻辑错),必须套子查询或 CTE - PostgreSQL/MySQL 8.0+ 支持 CTE,可写成
WITH t AS (SELECT *, ROW_NUMBER()...) SELECT * FROM t WHERE MOD(rn, 2) = 1 - 如果只要前 N 行的奇偶,记得把
LIMIT/TOP放在内层,否则外层MOD会作用于截断后的结果
不同数据库的MOD兼容性细节
MOD 函数名和行为基本一致,但边界情况有差异:
- MySQL 和 PostgreSQL:支持
MOD(a, b)和a % b两种写法,结果相同 - SQL Server:只有
%运算符,不支持MOD()函数;写MOD(5,2)会报错 - Oracle:
MOD()函数可用,但对负数处理和%不同(比如MOD(-5,3)返回 1,而某些语言返回 -2),不过行号不会是负数,这点可忽略 - SQLite:没有
MOD()函数,但支持%运算符;注意它不支持窗口函数,所以得用关联子查询模拟行号,性能明显下降
为求最大兼容,建议统一用 % 运算符(除 SQL Server 外都认),SQL Server 用户直接换 % 即可。
为什么不能依赖表的物理顺序
有人试过 SELECT * FROM t WHERE MOD((SELECT COUNT(*) FROM t t2 WHERE t2.id 这种自关联模拟行号,逻辑上可行但极慢——O(n²) 复杂度,万级数据就卡住。更隐蔽的问题是:即使加了 <code>ORDER BY,不显式用 ROW_NUMBER(),优化器仍可能改变执行顺序,导致“奇偶”每次查询结果不一致。
真实场景中,交替筛选常用于分页展示、报表隔行变色、AB测试分流。这些场景对“顺序”有强语义要求——比如按时间倒序取最新订单的奇数行,就不能接受随机抖动。所以,ROW_NUMBER() OVER (ORDER BY ...) 不是可选项,是必选项。
别省那几行代码,也别信“我这表 id 连续,不用 ROW_NUMBER”,上线后数据一变动,bug 就跟着来。

















