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

NOT NULL 拦的是「什么都没有」,不拦空字符串。 Demo 里 '' 进去了,LENGTH 是 0。 要拦它得加 CHECK (name <> '')。 非空是列的性质,插入和修改都管。 这一点和默认值正相反——默认值只管插入。 = NULL 永远给 0 行,因为「不知道」等不等于「不知道」的答案是「不知道」。 判断空只能用 IS NULL/IS NOT NULL。

📝 本章和前面几章的关系

上一篇讲默认值「只在没给的时候生效」,这一篇讲「给了空就拦」。 两个属性经常一起用:NOT NULL DEFAULT 0, 非空保证有值、默认值保证不给也有值。 这一篇的坑在「什么叫空」这个判断上。

一、不给值又没默认值,报错

name TEXT NOT NULL,插入时不给 name,直接报错。 报错文字是 NOT NULL constraint failed: demo_nn.name,连表名列名都告诉你了。 MySQL 那边是 Column 'name' cannot be null,意思一样、措辞不同。 报错的措辞不一样,但「哪一列空了」都能看出来。

二、空串能过,NULL 不能过

insert into 表 (id, name) values (2, '') 进去了,LENGTH 是 0。 非空约束只拦「什么都没有」,不拦「空字符串」。 这是一个真实存在的坑:前端提交了空输入框,后端拼成 '' 传进来,非空约束一句不拦, 库里就多了一行「名字是空字符串」的记录。 要拦它得加 CHECK (name <> '')。 注意这个 CHECK 对 NULL 是不拦的 (NULL 参与比较得「不知道」,不算假),所以两个要一起写: name TEXT NOT NULL CHECK (name <> '')。

三、update 成 NULL 一样拦

非空是列的性质,不管你是插入还是修改,它都管。 Demo 里 UPDATE demo_nn SET name = NULL 直接报错。 这一点和默认值正相反: 默认值只管插入,非空插入修改都管。 一个管「进来的时候」,一个管「任何时候」。

四、非空与默认值是最佳搭档

status INTEGER NOT NULL DEFAULT 0—— 非空保证「这列一定有值」,默认值保证「不给也有值」。 两个一起用,业务代码就可以完全不提这一列。 只写非空不写默认值,那每次插入都必须显式给值,漏一次报一次错,代码里到处是 0、 '' 这种填充值,还不如一开始就写在表定义里。

五、NULL 的等号永远不成立

where phone = NULL 永远给 0 行,where phone is null 才给真正没填的那一行。 原因:NULL 表示「不知道」。 「不知道」等不等于「不知道」?数据库的答案是「不知道」,不算成立。 所以任何跟 NULL 的比较都不会是真。 这一条在写校验时天天碰上: 「查所有没填手机号的人」写成 = NULL,结果永远是空的,还不报错。 排查这种 bug 时最费时间,因为语句本身一点毛病没有。 同理 <> NULL、> NULL 也都不成立,COUNT(列) 也不数 NULL 的行。 NULL 会渗透到每一个运算符里,这是 SQL 三值逻辑的代价。

六、动手 Demo:撞非空、过空串、改 NULL,最后看 NULL 的比较

Demo 五段:不给值撞一次、给空串过一次、update 成 NULL 再撞一次,然后验证非空与默认值搭配, 最后看 = NULL 与 IS NULL 的差别。 第二段是这一篇的核心——它会真的把空字符串插进去,让你看清「非空」这个翻译有多容易误导。

import _q46 as Q
c = Q.blank()
Q.run(c, "CREATE TABLE demo_nn (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")

c.execute("INSERT INTO demo_nn (id) VALUES (1)")          # 撞非空
Q.run(c, "INSERT INTO demo_nn (id, name) VALUES (2, '')")  # 空串能过
Q.show(c, "SELECT id, name, LENGTH(name) AS len FROM demo_nn")
c.execute("UPDATE demo_nn SET name = NULL WHERE id = 2")   # 也拦

# NULL 的等号永远不成立
c2 = Q.con()
Q.show(c2, "SELECT COUNT(*) AS with_null FROM students WHERE phone IS NULL")
Q.show(c2, "SELECT COUNT(*) AS wrong FROM students WHERE phone = NULL")

实跑输出:

--------------------------------------------------------------------
第四十四课:非空——空串不是空
--------------------------------------------------------------------

  不给值又没默认值,报错
  SQL> CREATE TABLE demo_nn (id INTEGER PRIMARY KEY, name TEXT NOT NULL)
  完成

  SQL> INSERT INTO demo_nn (id) VALUES (1)
  报错:NOT NULL constraint failed: demo_nn.name
  **报错文字是 `NOT NULL constraint failed: demo_nn.name`**,
  **连表名列名都告诉你了。** **MySQL 那边是 `Column 'name' cannot be null`,意思一样。**

  空串能过,NULL 不能过
  SQL> INSERT INTO demo_nn (id, name) VALUES (2, '')
  影响 1 行
  SQL> SELECT id, name, LENGTH(name) AS len FROM demo_nn
  id     name     len
  ---------------------
  2               0
  (1 行)
  **空串进去了,`LENGTH` 是 0。** **非空约束只拦「什么都没有」,不拦「空字符串」。**
  **这是一个真实存在的坑**:前端提交了空输入框,
  **后端拼成 `''` 传进来,非空约束一句不拦,库里就多了一行「名字是空字符串」的记录。**
  **要拦它得加 `CHECK (name <> '')`。**

  update 成 NULL 一样拦
  SQL> UPDATE demo_nn SET name = NULL WHERE id = 2
  报错:NOT NULL constraint failed: demo_nn.name
  **非空是列的性质,不管你是插入还是修改,它都管。** **这一点和默认值正相反——默认值只管插入。**

  非空与默认值是最佳搭档
  SQL> CREATE TABLE demo_nn2 (id INTEGER PRIMARY KEY, status INTEGER NOT NULL DEFAULT 0)
  完成
  SQL> INSERT INTO demo_nn2 (id) VALUES (1)
  影响 1 行
  SQL> SELECT id, status FROM demo_nn2
  id     status
  --------------
  1      0
  (1 行)
  **非空保证「这列一定有值」,默认值保证「不给也有值」。**
  **两个一起用,业务代码就可以完全不提这一列。**
  **只写非空不写默认值**,那每次插入都必须显式给值,漏一次报一次错。

  顺便说清:NULL 的等号永远不成立
  SQL> SELECT COUNT(*) AS with_null FROM students WHERE phone IS NULL
  with_null
  ----------
  1
  (1 行)
  SQL> SELECT COUNT(*) AS wrong FROM students WHERE phone = NULL
  wrong
  ------
  0
  (1 行)
  **`= NULL` 永远给 0 行**,因为 NULL 表示「不知道」,
  **「不知道」等不等于「不知道」?数据库的答案是「不知道」,不算成立。**
  **要判断空只能用 `IS NULL`/`IS NOT NULL`。** **这一条在写非空校验时天天碰上**——
  **「查所有没填手机号的人」写成 `= NULL`,结果永远是空的,还不报错。**

第一段撞非空,报 NOT NULL constraint failed: demo_nn.name。 第二段空串进去了,LENGTH(name) 是 0——非空不拦空串。 第三段 update 成 NULL,同样被拦,表里还是空串那一行。 第四段非空 + 默认值,只给 id 也有 status = 0。 第五段两个计数:IS NULL 给 1,= NULL 给 0。

30 秒实验

  1. name TEXT NOT NULL 的列里,能存进去几种「看起来是空」的值?
  2. 要同时拦住 NULL 和空字符串,建表时怎么写?
  3. SELECT COUNT(*) FROM students WHERE phone = NULL 为什么给 0?

常见误区

  1. 以为 NOT NULL 就是「不许空」。 空字符串照样能进,要拦得再加 CHECK (列 <> '')。
  2. 用 = NULL 查空值。 永远返回 0 行,还不报错,该用 IS NULL。
  3. 靠程序保证非空。 程序有好几个入口,漏一个就进一行脏数据;数据库约束是最后一道关,不该省。
自测三问(点开对答案) 1. `NOT NULL` 拦得住空字符串吗? —— 拦不住。它只拦 NULL,`''` 是一个有值的字符串,长度为 0。要拦得加 `CHECK (列 <> '')`。 2. 为什么 `= NULL` 永远不成立? —— NULL 表示「不知道」,跟它比较的结果是「不知道」而不是真,所以一行都匹配不上。 3. 非空和默认值分别管什么时候? —— 非空插入修改都管;默认值只管插入,而且只在语句里没提这一列时才生效。

有了非空,还缺「自动编号」。 下一篇 9.4 自动递增——一个写错了也不报错的属性。

相关笔记