CREATE TABLE 定义表结构
SQLite 中所有结构化数据都存放在表里,而每张表都从一条 CREATE TABLE 语句开始。你给它起个名字,列出各列,再按需加上约束。SQLite 会把表结构写进数据库文件,这张表就可以用了。
最小的实用示例:
三列,一个主键,一条 NOT NULL 规则。因为 id 是整数主键,SQLite 自动填好了它的值;而第二行的 email 没有任何规则限制,所以它允许为 NULL。这就是表的全部骨架:名字、列、约束。本页其余内容都是在此基础上的变化。
逐部分讲解语法
列定义的格式是 name TYPE constraint constraint ...。在传统的 SQLite 中,类型是可选的(详见类型亲和性一页),但最好总是写上,阅读代码的人和各种工具都依赖它。
有几点值得指出:
- 约束之间用空格连接:
sku上的NOT NULL UNIQUE表示两条规则同时生效。 in_stock上的DEFAULT 1让INSERT可以省略这一列。- SQLite 用
INTEGER表示布尔值,没有原生的BOOLEAN类型。0表示假,1表示真。 - 最后一列后面多一个逗号是语法错误。在这一点上 SQL 比 JavaScript 严格。
IF NOT EXISTS:重复运行也不报错
如果对已经有这张表的数据库运行 CREATE TABLE,SQLite 会报错:
Error: table users already exists
第一次遇到还好,第一百次就很烦了。IF NOT EXISTS 让这条语句在表已存在时什么也不做:
第二条 CREATE TABLE 什么都不做:不报错,也不改变表结构。在启动代码、迁移脚本以及任何可能多次运行同一段 SQL 的地方,你需要的就是这种写法。
不过要注意:IF NOT EXISTS 只检查“名字”。如果同名的表已经存在但列不同,SQLite 不会动它,也不会替你“修复”或“升级”表结构。那是迁移要做的事。
约束:跟着表结构走的规则
约束能把数据校验放进数据库本身。下面这几个你会经常用到:
PRIMARY KEY:唯一标识一行。详见主键文档。NOT NULL:这一列必须有值。DEFAULT value:当INSERT省略这一列时使用。可以是字面量,也可以是datetime('now')这样的表达式。CHECK (expr):对每一行都必须求值为真。UNIQUE (col, col):表级约束,保证列组合的唯一性。
约束在每次 INSERT 和 UPDATE 时都会检查。违反约束的行会被拒绝,语句执行失败。在数据库中拦下错误数据,比等它扩散到整个应用之后再处理要省事得多。
外键
外键表示“这一列指向另一张表中的某一行”。它让数据保持一致:你不能引用一个不存在的用户,而且(配合合适的选项)删除用户时可以级联删除他的订单。
SQLite 有个值得牢记的坑:外键约束“默认是关闭的”。每个需要检查约束的连接都必须运行 PRAGMA foreign_keys = ON。大多数应用驱动要么会帮你做这件事,要么提供一个设置项;如果你的驱动没有,就在连接后立刻运行这条 pragma。
这里的 ON DELETE CASCADE 表示删除用户时会自动删除他的帖子。其他选项还有 SET NULL、RESTRICT,以及默认的 NO ACTION,它会在存在子行时拒绝删除。
CREATE TABLE AS SELECT
有时你想把查询结果快速复制成一张新表,用作快照、备份,或者分析过程中的草稿表。CREATE TABLE ... AS SELECT 就是做这个的:
新表会复制列名、类型(尽量保留)和数据。它“不会”复制的东西同样重要:没有主键,没有 NOT NULL,没有索引,也没有外键。它只是一个扁平的快照。把它当作临时工作的起点,而不是克隆真实表结构的方法。
如果只想要结构而不要数据,加上 WHERE 0:
你会得到一张列结构相同的空表,很适合用作以后再填充的归档表。
临时表
TEMP 表只在当前数据库连接中存在。关闭连接后它就没了,不需要清理,也不会留下多余的表结构:
适合的用途:为多步查询暂存行数据,保存复杂到不适合用 CTE 的中间结果,在长时间运行的会话中隔离每个连接自己的数据。CREATE TEMP TABLE 和 CREATE TEMPORARY TABLE 的意思完全一样。
它也可以和 AS SELECT 结合使用:CREATE TEMP TABLE snapshot AS SELECT ... 是在分析中途冻结一个结果集的常用写法。
给名字加引号
大多数时候,列名和表名都是不加引号的普通标识符。如果你必须使用保留字或带空格的名字,就用双引号(SQL 标准)或反引号(MySQL 的写法,SQLite 也接受)把它括起来:
这样能用,但每次引用这张表都要多费点事。最好用 orders、selection、user_id 这样的普通名字,完全不用加引号。
一个贴近实际的示例
把前面的内容组合起来:一个任务应用的小型表结构,用了 IF NOT EXISTS,所以每次启动都能运行:
这就是一个可以上线的表结构:创建操作是幂等的,外键约束生效,CHECK 保证 done 的值合法,默认值合理,时间戳也会自动填好。
下一篇:数据类型
CREATE TABLE 让你写下 INTEGER、TEXT、REAL,但 SQLite 在存储这些值时出了名地宽松。下一页会介绍 SQLite 实际使用的五种存储类,以及为什么你写的类型不一定就是你得到的类型。
常见问题
如何在 SQLite 中创建表?
使用 CREATE TABLE name (column1 TYPE, column2 TYPE, ...)。每一列有一个名字和一个可选的类型,你还可以加上 PRIMARY KEY、NOT NULL 或 DEFAULT 等约束。这条语句会立即执行,表会一直保存在数据库文件中。
CREATE TABLE 中的 IF NOT EXISTS 有什么作用?
CREATE TABLE IF NOT EXISTS name (...) 只在同名的表还不存在时才创建表。不加它的话,对已有的数据库再次运行脚本会报 table already exists 错误。它是迁移脚本和应用启动代码中的标准防护写法。
SQLite 能用 SELECT 的结果创建表吗?
能。CREATE TABLE new_name AS SELECT ... 会用查询的结果集建出一张新表。新表会复制列名和数据,但不会复制源表的约束、主键或索引。它适合做快照和临时草稿表,不能代替真正的表结构。
临时表和普通表有什么区别?
CREATE TEMP TABLE(或 CREATE TEMPORARY TABLE)创建的表只在当前数据库连接中存在,连接关闭时就消失了。普通表会持久保存在数据库文件里。临时表适合暂存查询的中间结果,又不会弄乱表结构。