🦏 第9章 列的属性
📄 一句话概括

每一列是「名字、类型、属性」三样,属性写在类型后面,可以写好几个。 七个属性:DEFAULT、 NOT NULL、AUTO_INCREMENT、CHECK、UNIQUE、PRIMARY KEY、FOREIGN KEY, 外加一个只管说明的 COMMENT。 属性有两种写法:列级直接跟在列后面,表级集中写在最后。 复合主键、复合唯一只能写表级,因为一条约束同时管两列。 PRAGMA table_info 只能看见四样:类型、非空、默认值、主键。 CHECK 和 UNIQUE 看不到,得去看原始建表语句。

📝 本章和前面几章的关系

第 8 章建表时用过几个属性,这一章把它们一个一个拆开讲透。 这一章的每一篇都是「一个属性 + 一个坑」: 默认值管不到 update、非空不拦空串、自增写错了不报错、外键默认是关的。 这些坑的共同点是「不报错」——报错的坑不叫坑,叫提示。

一、属性的位置:写在类型后面

create table 表 (列 类型 属性, 列 类型 属性, …)。 Demo 里那张表四列, name 同时带了非空和默认值,age 带了检查,email 带了唯一。 属性可以写好几个,用空格隔开就行,不需要逗号——逗号是用来分隔列的。 这一点新手常错:写成了 name TEXT, NOT NULL,那 NOT NULL 就被当成新的一列了。

二、七个属性,各管一件事

DEFAULT 管「不给值时填什么」,NOT NULL 管「不许是空」, AUTO_INCREMENT 管「自己往上加」,CHECK 管「取值范围」。 UNIQUE 管「不许重复」,PRIMARY KEY 管「一行的身份证」, FOREIGN KEY 管「指向别的表」。 最后一个 COMMENT 管「给人看」。 前七个数据库会真的拦你,最后一个不拦—— 它只是说明文字,而且本沙箱连这个说明都存不好。 第 9.9 篇会单独撞一次。

三、列级写法与表级写法

列级:属性直接跟在列的类型后面,只能管这一列。 大多数属性都这么写。 表级:把所有列写完,再用逗号接约束。 PRIMARY KEY (a, b)、UNIQUE (a, b)、CHECK (…) 都能写成表级。 表级能干列级干不了的事:复合主键、复合唯一必须写表级, 因为一条约束同时管两列,没法挂在某一列后面。 另外给约束起名(CONSTRAINT ck_ab CHECK (…))也只有表级能写。

四、顺序无关,但习惯有讲究

同一个列上几个属性谁先谁后都行, name TEXT NOT NULL DEFAULT '匿名' 和 name TEXT DEFAULT '匿名' NOT NULL 完全等价。 但读代码的人是按顺序理解的,习惯上排成「主键 → 非空 → 默认值 → 检查 → 唯一」, 这样一眼就能看出这列有多严格。

五、属性会互相打架

PRIMARY KEY 已经包含「非空 + 唯一」,再写 NOT NULL 是重复,写 UNIQUE 更是白写。 DEFAULT 和 NOT NULL 是最佳搭档: 非空保证「有值」,默认值保证「不给也有值」。 只写非空不写默认值,插入时漏了这列就直接报错。

六、PRAGMA 里只能看见四样

PRAGMA table_info(表) 给六列: 序号、列名、类型、是否非空(notnull)、默认值(dflt_value)、是否主键(pk)。 CHECK 和 UNIQUE 这两样在这里看不到—— 它们不在 table_info 里,得去看 sqlite_master 里的原始建表语句。 这是查表结构时最容易被漏掉的一处:你以为看全了,其实少看了两条约束。 MySQL 的 DESCRIBE 能多给一个 Key 列,UNI 表示唯一、PRI 表示主键, 比这里多看见一样。 CHECK 两边都看不见,只能翻建表语句。

七、约束最好都起个名字

七个属性写一堆,报错只说「constraint failed」,不告诉你是谁拦的。 加了名字就不一样: CONSTRAINT ck_age CHECK (age >= 0),报错会带上 ck_age。 名字还有个好处:以后想 DROP CONSTRAINT 得有名, 没名字的约束只能靠重建表来删。 这是「现在多打几个字,以后少干半小时」的典型例子。

八、动手 Demo:一张表四个属性,再看 PRAGMA 能看见什么

Demo 五段:建一张带四个属性的表、建一张用表级写法的表、看 PRAGMA 输出、再讲属性能不能互相打架。 第三段那张 PRAGMA 表是重点:它显示的信息比你以为的少。

import _q46 as Q
c = Q.blank()

# 一张表把几个属性都写上
Q.run(c, "CREATE TABLE demo_attr ("
          "id INTEGER PRIMARY KEY, "
          "name TEXT NOT NULL DEFAULT '匿名', "
          "age INTEGER CHECK (age BETWEEN 0 AND 200), "
          "email TEXT UNIQUE)")

# 表级写法:复合主键 + 具名约束
Q.run(c, "CREATE TABLE demo_tl (a TEXT, b TEXT, "
          "PRIMARY KEY (a, b), "
          "CONSTRAINT ck_ab CHECK (a <> b))")

# 属性在 PRAGMA 里能看见几样?
Q.show(c, "PRAGMA table_info(demo_attr)")

实跑输出:

--------------------------------------------------------------------
第四十二课:七个开关,一次说清
--------------------------------------------------------------------

  属性的位置:写在类型后面
  **建表时每一列是「名字、类型、属性」三样。** **属性写在类型的后面,可以写好几个。**
  SQL> CREATE TABLE demo_attr (id INTEGER PRIMARY KEY, name TEXT NOT NULL DEFAULT '匿名',
       age INTEGER CHECK (age BETWEEN 0 AND 200), email TEXT UNIQUE)
  完成

  七个属性,各管一件事
  **`DEFAULT` 管「不给值时填什么」**,**`NOT NULL` 管「不许是空」**,
  **`AUTO_INCREMENT` 管「自己往上加」**,**`CHECK` 管「取值范围」**。
  **`UNIQUE` 管「不许重复」**,**`PRIMARY KEY` 管「一行的身份证」**,
  **`FOREIGN KEY` 管「指向别的表」**。 **最后一个 `COMMENT` 管「给人看」。**
  **前六个数据库会真的拦你,最后一个不拦——它只是说明文字。** **这一点第 50 课会单独撞一次。**

  列级写法与表级写法
  **属性可以直接跟在列后面**(列级),**也可以集中写在最后**(表级)。 上面那张表用的是列级。
  SQL> CREATE TABLE demo_tl (a TEXT, b TEXT, PRIMARY KEY (a, b),
       CONSTRAINT ck_ab CHECK (a <> b))
  完成
  **表级写法能干列级干不了的事**:**复合主键、复合唯一**都要写成表级,
  **因为一条约束同时管两列,没法挂在某一列后面。**

  顺序无关,但习惯有讲究
  **同一个列上几个属性谁先谁后都行**,
  `name TEXT NOT NULL DEFAULT '匿名'` 和 `name TEXT DEFAULT '匿名' NOT NULL` 等价。
  **但读代码的人是按顺序理解的**,**习惯上排成「主键 → 非空 → 默认值 → 检查 → 唯一」**,
  **这样一眼就能看出这列有多严。**

  属性太多会互相打架
  **`PRIMARY KEY` 已经包含「非空 + 唯一」**,再写 `NOT NULL` 是重复,写 `UNIQUE` 更是白写。
  **`DEFAULT` 和 `NOT NULL` 经常一起用**:
  **非空保证有值,默认值保证「不给也有值」。** **只写非空不写默认值,插入时漏了这列就直接报错。**

  看一张表到底带了哪些属性
  SQL> PRAGMA table_info(demo_attr)
  cid     name      type        notnull     dflt_value     pk
  --------------------------------------------------------------------
  0       id        INTEGER     0           NULL           1
  1       name      TEXT        1           '匿名'         0
  2       age       INTEGER     0           NULL           0
  3       email     TEXT        0           NULL           0
  (4 行)

  **六列里能看出四样**:类型、是否非空(`notnull`)、默认值(`dflt_value`)、是否主键(`pk`)。
  **`CHECK` 和 `UNIQUE` 这两样在这里看不到**——
  **它们不在 `table_info` 里,得去看原始建表语句。** **这是查表结构时最容易被漏掉的一处。**

  一条语句里塞太多属性,出问题很难查
  **七个属性写在一列上,报错只说「constraint failed」**,**不告诉你是哪一条拦的。**
  **所以约束最好都起个名字**:`CONSTRAINT ck_age CHECK (age >= 0)`,**报错时就会带上这个名字**,
  一眼知道是谁拦的。

第一段四列四个属性:主键、非空 + 默认值、检查、唯一。 第二段表级写法:复合主键 PRIMARY KEY (a, b) 加具名约束 CONSTRAINT ck_ab CHECK (a <> b)。 第三段 PRAGMA 四行六列:name 的 notnull 是 1、dflt_value 是 '匿名', id 的 pk 是 1。 CHECK 和 UNIQUE 在输出里一条都看不见——这就是「PRAGMA 看不全」的实证。 后面两段没有 SQL,讲属性顺序和「属性会互相打架」。

30 秒实验

  1. name TEXT NOT NULL DEFAULT '匿名' 里,两个属性之间要不要逗号?为什么?
  2. 想给「两列一起」加唯一约束,能写在某一列后面吗?该写在哪?
  3. PRAGMA table_info 的输出里,能不能看出某列有 UNIQUE?那要看哪里?

常见误区

  1. 属性之间加逗号。 逗号是分隔列的,加了以后后面那串会被当成新的一列, 建表可能不报错但结构全错。
  2. 以为 PRAGMA table_info 就是全部结构。 CHECK、UNIQUE、外键它都不显示,得去翻建表语句。
  3. 主键上再写 NOT NULL 或 UNIQUE。 主键本来就含这两条,写上去只是噪音, 还会让人以为约束是分开的两回事。
自测三问(点开对答案) 1. 七个属性各管什么?哪个不拦数据? —— `DEFAULT` 填默认值、`NOT NULL` 不许空、`AUTO_INCREMENT` 自动加、`CHECK` 管范围、 `UNIQUE` 不许重复、`PRIMARY KEY` 当身份证、`FOREIGN KEY` 指别的表。 `COMMENT` 只管说明,不拦。 2. 列级写法和表级写法分别适合什么? —— 列级管单列,多数属性都这么写;表级能写复合主键、复合唯一、具名约束,这些列级做不到。 3. 为什么约束要起名字? —— 报错时会带上名字,一眼知道是哪条拦的;而且不起名就没法单独删除这条约束,只能重建表。

七个属性一个一个来。下一篇 9.2 默认值——它只在「你没提这一列」的时候生效。

相关笔记