SQLite STRICT 表的 ANY:同样写 ANY,为什么一张表保住 0007,另一张只剩 7
需要保留导入值的原始表示,于是把列声明为 ANY,带前导零的字符串却变成整数。SQLite中的ANY要结合表选项来看:普通表会尝试把看起来像数字的文本转成数值,STRICT表的ANY则保留接收到的值与存储类型。
下面在内存数据库里建立三张表,使用相同绑定值对照。保存为 demo.mjs,用 Node.js 24 运行 node demo.mjs。STRICT表要求SQLite 3.37.0或更新版本;这里不读取或更新任何现有数据库文件。
AI模型生成概念示意:一种存储路径转换数字表示,另一种完整保留原始形态;是类型规则的抽象图,不是数据库截图。
import assert from 'node:assert/strict';
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:');
try {
db.exec(`
CREATE TABLE ordinary(value ANY);
CREATE TABLE preserved(value ANY) STRICT;
CREATE TABLE integers(value INTEGER) STRICT;
`);
const input = ['0007', '7.50', 'widget'];
for (const value of input) {
db.prepare('INSERT INTO ordinary VALUES (?)').run(value);
db.prepare('INSERT INTO preserved VALUES (?)').run(value);
}
for (const table of ['ordinary', 'preserved']) {
const rows = db.prepare(
`SELECT typeof(value) AS kind, quote(value) AS saved
FROM ${table} ORDER BY rowid`
).all().map(row => Object.values(row));
const expected = table === 'ordinary'
? [['integer', '7'], ['real', '7.5'], ['text', "'widget'"]]
: [['text', "'0007'"], ['text', "'7.50'"], ['text', "'widget'"]];
assert.deepEqual(rows, expected);
console.log(table + ': ' + JSON.stringify(rows));
}
db.prepare('INSERT INTO integers VALUES (?)').run('0007');
assert.equal(db.prepare('SELECT typeof(value) AS t FROM integers').get().t, 'integer');
console.log('STRICT INTEGER accepts numeric text: integer');
assert.throws(() => db.prepare('INSERT INTO integers VALUES (?)').run('widget'),
error => error.errcode === 3091);
assert.equal(db.prepare('SELECT count(*) AS n FROM integers').get().n, 1);
console.log('STRICT INTEGER rejects widget: SQLITE_CONSTRAINT_DATATYPE');
} finally {
db.close();
}看保存类型,也看保留下来的文字
ordinary 一行依次显示 integer 与 7、real 与 7.5、text 与带引号的 widget。输入0007的前导零和7.50末尾的零都没有保住,因为保存下来的已经是数值。第三个词无法转成数值,所以仍然是文本。
preserved 一行则把三项全部保留为text,quote结果分别是带引号的0007、7.50与widget。typeof检验存储类别,quote展示SQL形式的表示,二者一起使用可以避免驱动打印时把字符串与数字都显示得很相似。
表名来自代码中写死的两项列表,值则通过问号参数绑定。动态SQL中的标识符与数据参数不是同一个入口;如果将这段代码扩展成通用导入器,应继续限制目标表集合,不能直接把外部表名拼进查询。
STRICT 不等于拒绝所有类型转换
第三张表声明为 STRICT INTEGER。插入字符串0007仍然成功,输出 STRICT INTEGER accepts numeric text: integer,因为这个值能够按该列规则无损转换成整数。严格表承诺最终列类型符合要求,并不要求所有入口参数一开始就具有完全相同的宿主语言类型。
widget不能完成这种转换,断言捕获扩展错误码3091,即SQLITE_CONSTRAINT_DATATYPE。随后再次计数确认表里仍然只有先前成功的那一行。这里同时验证拒绝原因与拒绝后的数据状态,避免仅看到异常便忽略到底留下了什么。
原始表示与业务类型需要分别建模
如果字段永远是编号文字,明确的TEXT列通常更容易解释;若一列确实允许文本、整数与二进制等多种输入,同时又要保留每次输入类型,STRICT ANY才符合这个目标。选择ANY不等于已经验证内容格式、单位或业务范围,这些条件仍需单独表达。
ANY也不会自动排除NULL,需要非空时仍应声明NOT NULL。迁移已有表之前,应先检查历史存储类型,并在隔离副本里测试导入行为。已经被普通列转换掉的前导零,不能只靠把新表改成ANY就恢复;需要回到仍保留原始文字的数据来源。
资料核对日期:2026年10月2日。代码在 Node.js v24.19.0、内置SQLite 3.53.3 实际运行,类型、表示、错误码与行数断言全部通过。


