学习前提

能建立SQLite表并执行SELECT;理解列类型与WHERE的作用。

本课完成后你能做什么

组合条件,正确处理缺失值和并列排序。

先理解机制

WHERE在返回记录之前筛选行。AND优先于OR;混合条件应使用括号表达业务含义,不要依赖读者记忆优先级。

NULL表示未知或缺失,不是空字符串也不是0。与NULL进行=比较不会得到真;应使用IS NULL或IS NOT NULL。WHERE只保留条件为真的行。

LIMIT取结果片段,必须搭配稳定排序。若只按minutes排序,并列记录顺序可能不确定;加入唯一id作为第二排序键。频繁变化的数据使用OFFSET分页还可能出现跨页漂移。

可运行示例

本系列使用SQLite 3方言。打开一个全新的内存练习数据库,例如终端执行 sqlite3 :memory:,再执行本课整段SQL。每章都自带建表和样例数据,不连接网站业务数据库。

CREATE TABLE lessons(id INTEGER PRIMARY KEY, title TEXT, minutes INTEGER, level TEXT);
INSERT INTO lessons VALUES
 (1,'Variables',20,'beginner'),(2,'Loops',20,'beginner'),
 (3,'Objects',40,'intermediate'),(4,'Review',NULL,'beginner');
SELECT id, title, minutes FROM lessons
WHERE level = 'beginner' AND minutes IS NOT NULL AND minutes <= 30
ORDER BY minutes, id LIMIT 2;

推演与验证

仅第1、2行满足所有条件。第4行minutes缺失,不会被当作0分钟。

常见错误与边界

COALESCE可为显示提供替代值,但用它把未知分钟变成0再统计会改变含义。先定义缺失数据策略。

动手练习

查询分钟数未知的课程,然后查询第2页,每页2条,按id排序。

参考解法与理由

第一问WHERE minutes IS NULL得到Review。第二问ORDER BY id LIMIT 2 OFFSET 2得到Objects和Review;OFFSET是跳过的行数。

深入实验:NULL与分页的隐藏边界

筛选“不是完成状态”时,status <> done不会包含未知状态;SQL比较NULL通常产生UNKNOWN而不是TRUE。下面在新的SQLite内存数据库运行,数据包含同分排序和未知状态,便于同时观察两个容易遗漏的边界。

CREATE TABLE lessons(id INTEGER PRIMARY KEY, score INTEGER, status TEXT);
INSERT INTO lessons VALUES (1, 90, 'todo'), (2, 90, NULL), (3, 80, 'done');
SELECT id FROM lessons WHERE status <> 'done' ORDER BY id;
SELECT id FROM lessons WHERE status <> 'done' OR status IS NULL ORDER BY id;
SELECT id, score FROM lessons ORDER BY score DESC, id ASC LIMIT 2;
SELECT id, score FROM lessons
WHERE score < 90 OR (score = 90 AND id > 2)
ORDER BY score DESC, id ASC LIMIT 2;

预期观察

第一条查询只返回1;第二条返回1和2;第一页为(1,90)、(2,90),游标之后为(3,80)。

原因、取舍与适用边界

WHERE只保留TRUE。必须先决定未知状态应归入待办、排除还是单独展示,再写IS NULL分支。排序加入唯一id作为决胜条件,使相同score也有确定顺序。游标条件必须遵循相同方向:score降序用小于,id升序用大于。这里score可空却只演示非空分值的游标;正式接口应禁止空分值或明确NULL的排序与游标编码,不能直接照搬。

进阶练习

翻页期间分数改变,游标分页是否就能保证一次不重不漏?

解法与推理

不能。记录可以跨过已读取的游标边界。游标减少OFFSET位移问题,但一致快照、固定排序字段或业务可接受的最终一致性仍需单独设计。不要把稳定的排序规则等同于跨请求冻结的数据集。

进一步阅读