SQLite EXCLUDE TIES:排除同分记录时,为什么当前这一行仍然参加求和
做一张按评分展示的汇总表,要求暂时排除与当前记录同分的其他记录,却保留当前记录本身。EXCLUDE TIES可以表达这个关系,但如果把TIES理解为把整组并列项全删掉,就会算出另一份结果。整组排除对应的是EXCLUDE GROUP。
下面有四行,score分别为十、十、二十、三十,amount分别为二、三、五、七,总额十七。窗口明确覆盖整个分区,所以每一行在排除之前都包含全部四行;比较时唯一变化是EXCLUDE规则,避免累计边界掩盖差异。
保存为demo.mjs,用node demo.mjs执行,使用Node.js内置node:sqlite连接内存数据库。本文在Node.js v24.19.0、SQLite 3.53.3实跑,无磁盘数据库。查询同时返回四种规则,最终按id排列便于逐行核算。
AI模型生成的概念插图:当前同色圆点留在框内,其他同色圆点移到框外,异色圆点仍保留;只表示排除关系,不是查询结果截图。
完整可运行程序
import assert from 'node:assert/strict';
import {DatabaseSync} from 'node:sqlite';
const db = new DatabaseSync(':memory:');
try {
const rows = db.prepare(`
WITH sample(id, score, amount) AS (
VALUES (1, 10, 2), (2, 10, 3), (3, 20, 5), (4, 30, 7)
)
SELECT id,
sum(amount) OVER (base
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
EXCLUDE NO OTHERS) AS all_rows,
sum(amount) OVER (base
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
EXCLUDE CURRENT ROW) AS without_current,
sum(amount) OVER (base
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
EXCLUDE GROUP) AS without_group,
sum(amount) OVER (base
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
EXCLUDE TIES) AS without_ties
FROM sample
WINDOW base AS (ORDER BY score)
ORDER BY id
`).all().map(row => Object.values(row));
assert.deepEqual(rows, [
[1, 17, 15, 12, 14], [2, 17, 14, 12, 15],
[3, 17, 12, 12, 17], [4, 17, 10, 10, 17]
]);
console.log('id all current group ties');
for (const row of rows) console.log(row.join(' '));
} finally {
db.close();
}本次实际输出
id all current group ties 1 17 15 12 14 2 17 14 12 15 3 17 12 12 17 4 17 10 10 17
先手算第一行,再对照四列
第一行的四个金额结果依次是十七、十五、十二、十四。NO OTHERS不排除任何行;CURRENT ROW只减掉自己amount为二的这一行,得到十五;GROUP连同另一个同分行一起去掉,减掉二与三,得到十二。
TIES排除其他同序值记录,保留已经处在frame里的当前行,所以第一行只减三,得到十四。第二行反过来保留自己的三、排除另一行的二,得到十五。两行的GROUP结果一样,TIES结果不同,正好揭示了当前行身份仍然重要。
ROWS不会让同伴关系失效
本例frame类型是ROWS,但EXCLUDE判断同伴时仍看窗口ORDER BY的值。score为十的两条记录因此互为同伴;不会因为frame按行描述,就把它们当作完全无关的两个位置。这是与单纯理解行范围不同的一层规则。
score为二十、三十的行没有其他同分同伴,TIES列便都等于十七。GROUP则仍会排除当前行自己,因此第三、四行分别是十二与十。单独检查没有并列值的数据,容易把TIES误认为“这个选项没效果”。
窗口排序也定义了谁算同伴
不要为了让页面显示稳定,就随手把唯一id加入窗口内部的ORDER BY。那会使原先同score的两行不再具有完全相同的排序值,改变同伴分组。本例只在查询最外层按id排序,它控制展示,不改变窗口按score建立的关系。
这里让base命名窗口只含ORDER BY,各个OVER再补完整frame与EXCLUDE。SQLite窗口链不允许base已经带着frame后又被这样继承;如果为了少写几行而把全部frame移入base,再在外面只补EXCLUDE,会遇到语法或窗口继承限制。
EXCLUDE处理的是已选frame里的成员,并不会把原本在frame之外的当前行重新加回来。本文使用全分区frame,当前行天然在里面,才方便用“保留自己”解释TIES。换成仅含前几行的frame时,要先列范围,再应用排除。
排查报表时,按“分区有哪些行、窗口排序如何定义同伴、frame先选哪些行、EXCLUDE再去掉谁”的顺序手算。保留一组同分两行和两条独立分数,检查成员集合,比只比对一个总和更容易定位口径错误。
官方资料核验日期:2026年10月2日。上述输出来自本文完整程序的本地执行,全部断言通过,退出状态为零。


