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

MOD函数在SQL中如何正确计算奇偶行
SQL里的MOD函数(或%操作符)本身不直接作用于“行号”,它只对数值列做取模运算。想实现奇偶行交替筛选,必须先生成行号——而不同数据库生成行号的方式差异极大,这是最容易出错的第一步。
常见错误是直接写 WHERE MOD(id, 2) = 1,这其实是在按id字段奇偶筛选,不是“结果集的第1、3、5行”,两者语义完全不同。如果id有缺失、被删除或非连续,结果就不可靠。
- MySQL 8.0+ 和 PostgreSQL 支持
ROW_NUMBER() OVER(),这是最稳妥的起点 - SQLite 从 3.25.0 开始支持窗口函数;旧版只能靠自关联或子查询模拟,性能差且易错
- SQL Server 用
ROW_NUMBER(),但注意ORDER BY必须明确,否则行号无意义 - Oracle 早期版本用
ROWNUM,但它在WHERE中不可用(执行顺序问题),必须嵌套子查询
MySQL 8.0+ 实现奇数行筛选的完整写法
以获取结果集中第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表示奇数行;若要偶数行,改用= 0。关键点在于:ORDER BY 决定行号顺序,没有它,ROW_NUMBER() 的结果不可预测;MOD 必须作用在生成的rn上,不能直接套在原表字段上。
- 别名
rn必须在子查询里定义,外部WHERE才能引用 - 如果只需要偶数行且想跳过第一行(比如分页式交替),可加偏移:
MOD(t.rn - 1, 2) = 0 - MySQL 中
MOD(a,b)和a % b等价,但%在某些ORM里可能被转义,显式用MOD更安全
PostgreSQL 和 SQL Server 的等效写法差异
PostgreSQL 完全兼容上述 MySQL 写法,但需注意:它的MOD函数对负数返回负余数(如MOD(-1,2)得-1),而行号不会为负,所以不影响使用。SQL Server 则不支持MOD函数,必须用%操作符:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY updated_at) AS rn FROM products ) t WHERE t.rn % 2 = 1;
SQL Server 的%与MOD行为一致,但语法强制要求用符号。另外,SQL Server 的ORDER BY子句中若含GETDATE()这类非确定性函数,会导致ROW_NUMBER()报错,必须换为确定性排序依据。
- PostgreSQL 中
MOD(NULL, 2)返回NULL,若rn可能为空(极罕见),需加IS NOT NULL判断 - SQL Server 的
TOP+ORDER BY不能和ROW_NUMBER()混用在同一层级,会引发语法错误 - 所有数据库中,
ORDER BY字段若有重复值,应添加唯一键(如id)作为第二排序条件,避免行号不稳定
为什么不用 WHERE id % 2 == 1 就不行
因为id是业务主键,不是执行时的物理行序。典型反例:一张用户表删掉id=2和id=4后,剩余id为1,3,5,6,7…,此时WHERE id % 2 = 1返回5行,但“结果集第1、3、5行”只有3行——二者根本不在同一维度。
- 前端分页、表格隔行变色、导出奇数批次等场景,依赖的是查询结果的**呈现顺序位置**,而非原始数据的ID分布
- 即使
id连续,若查询带WHERE status = 'active'过滤,id奇偶性也和过滤后行序脱钩 - 用
OFFSET/LIMIT模拟奇偶筛选(如OFFSET 0 ROWS FETCH FIRST 1 ROW ONLY)极其低效,无法一次性拿到全部奇数行
真正可靠的奇偶行筛选,永远始于明确的行号生成,止于对该行号的取模判断。中间少任何一环,结果都只是巧合。

















