SQLite lag 的窗口边界:frame 只剩当前行,为什么仍然能读到上一行
为了让查询只处理当前记录,把窗口框架写成 CURRENT ROW 到 CURRENT ROW,lag 仍然返回上一条内容。这里需要先看函数本身的取行规则:SQLite 的 lag 按分区中的相对位置取值,忽略frame;first_value 则会在frame里取第一行。
下面使用 Node.js 的内置 node:sqlite 连接内存数据库。保存为 demo.mjs,运行 node demo.mjs;建议使用 Node.js 24。SQL只构造四条固定记录,不访问磁盘数据库,编号唯一且显式排序。
AI模型生成概念示意:当前行被窄框选中,前一行的值仍可送到当前位置;图只说明取值关系,不是数据库截图。
import assert from 'node:assert/strict';
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:');
try {
const rows = db.prepare(`
WITH sample(seq, value) AS (
VALUES (1, 'A'), (2, NULL), (3, 'C'), (4, 'D')
)
SELECT seq,
lag(value, 1, 'none') OVER narrow AS previous,
first_value(value) OVER narrow AS first_in_frame
FROM sample
WINDOW narrow AS (
ORDER BY seq ROWS BETWEEN CURRENT ROW AND CURRENT ROW
)
ORDER BY seq
`).all().map(row => Object.values(row));
assert.deepEqual(rows, [
[1, 'none', 'A'], [2, 'A', null],
[3, null, 'C'], [4, 'C', 'D'],
]);
console.log('seq previous first_in_frame');
for (const row of rows) console.log(JSON.stringify(row));
} finally {
db.close();
}同一个窗口定义,不代表函数使用同一范围
四条结果依次是 [1,"none","A"]、[2,"A",null]、[3,null,"C"]、[4,"C","D"]。第二列始终尝试向前移动一行,第三列始终读取当前行。第三、四行尤其清楚:当前框架只有自己,lag 仍能取到框架外的前一行。
WINDOW narrow 让两个函数共享同一份 ORDER BY 与frame文字,排除了手误写成两种窗口的可能。对 first_value 来说,当前行就是这个单行框架里的第一行;对 lag 来说,重要的是当前行在整个分区中的位置,以及第二个参数指定的偏移一。
备用值只处理没有那一行
第一行没有前一条,因此 lag 返回第三个参数 none。第三行明明有前一条,只是前一条的 value 为 NULL,所以结果保留 NULL,没有替换成 none。这个样本能防止把lag的备用值误写成“任何空值都补齐”的业务说明。
如果要把确实存在的 NULL 也替换,应另外设计空值规则,例如在查询外层明确处理。这样做之前要确认“没有前一条”和“前一条没有填写内容”能否合并,否则报表会丢失有用区别。默认值本身也应符合接口约定的类型,本例文本列使用文本标记。
限制可见行要改变输入或表达条件
给lag缩小frame并不能阻止它跨过业务边界。若每个设备只比较自己的前一条,应使用 PARTITION BY 设备字段;若只允许相隔不超过一定时间的记录,应同时取得前一时间并检验间隔。选哪条记录与那条记录是否符合业务条件,需要分别写清。
WHERE会改变进入窗口计算的输入,最终 ORDER BY 只控制结果展示。本例两处都按 seq 排序,但实际查询应先决定过滤发生在哪一层。还要为排序加入能打破并列的稳定键,否则“上一条”在同键记录之间没有唯一含义。
资料核对日期:2026年10月2日。代码在 Node.js v24.19.0、内置 SQLite 3.53.3 实际运行并通过全部断言;SQL需要支持窗口函数的SQLite版本。


