🪟 第5章 数据表操作
📄 一句话概括

SSMS 里点一圈,最后生成的就是一条 CREATE TABLE;看懂那条语句才算会建表。 NULL 和空字符串不是一回事:前者是「不知道」,后者是「知道,就是空的」。 保存时报「不允许保存更改」,是因为 SSMS 默认禁止「建新表搬数据删旧表」这种改法。

📝 本章和前面几章的关系

教材这一节全是截图:右键表 → 新建表 → 填列名类型 → 保存。操作本身五分钟能学会。 这一节真正要带走的是两件事: 一是列属性里那三个勾选项(非空、默认值、标识)各管什么, 二是保存时那两个检查为什么会拦你。 演示里把「界面点完生成的 SQL」原样贴出来,再用 SQLite 演一遍默认值和非空列的实际效果。

一、界面点完,生成的是这条语句

在 SSMS 里建一张学生表,保存之后右键 → 编写表脚本 → CREATE 到,会得到这样一段:

CREATE TABLE [dbo].[tb_student](
    [sno] [char](8) NOT NULL,
    [sname] [nvarchar](20) NULL,
    [sex] [char](2) NULL,
    [birthday] [date] NULL,
    [class_id] [int] NULL,
 CONSTRAINT [PK_tb_student] PRIMARY KEY CLUSTERED ([sno] ASC)
) ON [PRIMARY]

界面里填的每一格,最后都变成这行 SQL。 所以「会写 CREATE TABLE」比「会点界面」值钱—— 界面里改不动的(比如把已有列改成自增),语句里能做,虽然要绕一下。 方括号到处都是,因为 SSMS 生成脚本时一律加。 它不知道你的名字会不会撞关键字, 干脆全加——这样最安全,但看着很吵。手写的时候只在需要的地方加。 ON [PRIMARY] 指定的是文件组。 就是 4.1 讲的那个「把数据放在哪个文件上」。 不写这个子句,默认也是主文件组。

二、列属性三个勾选项

属性 勾上之后 不勾的话
允许 Null 这列可以不填 插入时必须给值
默认值 没给值时填这个 没给值就是 NULL
标识(自增) 自动递增,不用给 必须自己给值

「允许 Null」这个词是反的,容易看错。 勾上「允许 Null」等于「这列可以不填」; 取消勾选等于「NOT NULL」,也就是必填。看脚本里的 NOT NULL 更直观。 默认值和「允许 Null」是两回事。 一列可以是 NOT NULL 又有 DEFAULT 0——意思是「必须有个值, 但你不给我就填 0」。这种组合用得最多。 自增列(IDENTITY)不能自己插值。 想强行插,得先 SET IDENTITY_INSERT 表名 ON,插完再关掉。 这个开关一次只能开一张表,而且要求当前会话有权限。

三、NULL 和空字符串的区别

这是数据库入门最经典的一个问题,但它不是学术问题,是实际会出错的地方。

值 含义 IS NULL 成立吗
NULL 不知道、没填 成立
空字符串 知道,就是空的 不成立
空格 知道,值是一个空格 不成立

判断 NULL 只能写 IS NULL,不能写 = NULL。 因为 NULL 表示「不知道」, 「不知道等于什么」这件事本身无法判断,所以 = NULL 永远返回未知,永远查不到行。 这个坑的典型后果是「统计出来的人数不对」。 WHERE phone <> '' 会把 phone IS NULL 的行排除掉,因为 NULL 参与任何比较都返回未知。 要写 WHERE phone IS NOT NULL AND phone <> ''。 演示里会看到:只插 name 一列,sex 自动变成 '未知'(有默认值), age 变成 NULL(没默认值也没非空)。 这两种「没填」的结果完全不同。

四、保存时的两种检查

情况 SSMS 的提示 实际原因
改列类型 不允许保存更改 要删表重建,默认关闭
表被锁着 超时已过期 有事务没提交
加非空列不给默认 必须填默认值 已有行没法填

第一条最出名。 改列类型这种事,SQL Server 的做法是「建新表、搬数据、 删旧表」——中间任何一个环节出错,数据就丢了。所以 SSMS 默认禁止,让你去写 ALTER TABLE。 开关在「工具 → 选项 → 设计器 → 表设计器和数据库设计器」里。 勾上「阻止保存要求重新创建表的更改」取消掉,就能存了。但风险自负。 第二条「超时已过期」跟表结构没关系。 是有人在另一个窗口开了事务没提交,把表锁住了。 查 sys.dm_tran_locks 或者直接问「谁刚在改这张表」。 第三条是最常见的一次性操作失误。 给一张有数据的表加 NOT NULL 列, 已有的行不知道该填什么——所以必须给 DEFAULT,否则直接拒绝。

五、动手 Demo:默认值、自增与非空列

这一段先贴「界面点完生成的 SQL」,再真建一张带自增、默认值、CHECK 的表, 只插一列看其它列被填成什么,最后试一次「给已有数据的表加非空列」。

import sqlite3

c = sqlite3.connect(u":memory:")
c.execute(u"""CREATE TABLE tb (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    sex TEXT DEFAULT '未知',
    age INT CHECK (age >= 0 AND age <= 150))""")
c.execute(u"INSERT INTO tb (name) VALUES ('张三')")
r = c.execute(u"SELECT id, name, sex, age FROM tb").fetchone()
print(u"只插 name,其它列:id=%s sex=%s age=%s" % (r[0], r[2], r[3]))

# 给已有数据的表加非空列,会怎样
c.execute(u"CREATE TABLE tb2 (id INTEGER PRIMARY KEY, name TEXT)")
c.execute(u"INSERT INTO tb2 (name) VALUES ('甲'), ('乙')")
c.execute(u"ALTER TABLE tb2 ADD COLUMN memo TEXT")
print(c.execute(u"SELECT id, memo FROM tb2").fetchall())
try:
    c.execute(u"ALTER TABLE tb2 ADD COLUMN code TEXT NOT NULL")
except sqlite3.OperationalError as e:
    print(u"加非空列失败:", e)

实跑输出:

--------------------------------------------------------------------
界面建表背后发生了什么
--------------------------------------------------------------------


  ① SSMS 里点一圈,生成的是这条语句
  --------------------------------------------------------------------

  [教材原码 · 本沙箱不执行]
  | CREATE TABLE [dbo].[tb_student](
  |     [sno] [char](8) NOT NULL,
  |     [sname] [nvarchar](20) NULL,
  |     [sex] [char](2) NULL,
  |     [birthday] [date] NULL,
  |     [class_id] [int] NULL,
  |  CONSTRAINT [PK_tb_student] PRIMARY KEY CLUSTERED ([sno] ASC)
  | ) ON [PRIMARY]

  界面里填的每一格,最后都变成这行 SQL。所以「会写 CREATE TABLE」比「会点界面」值钱——
  界面里改不动的(比如把已有列改成自增),语句里能做,虽然要绕一下。
  教材建议新手先用界面,是为了让你有直观印象;但真正落到脚本里的,一定是这条语句。

  ② 列属性:NULL、默认值、自增
  --------------------------------------------------------------------
  只插了 name,其它列自动填成:id=1 sex=未知 age=None
  id 是自增,sex 用了默认值 '未知',age 没默认值也没 NOT NULL,所以是 NULL。
  注意「NULL」和「空字符串」不是一回事:NULL 表示「不知道」,空字符串表示「知道,就是空的」。
  SQL Server 里判断要写 IS NULL,不能写 = NULL——后者永远不成立。

  ③ 保存时的两种检查
  --------------------------------------------------------------------
| 情况 | SSMS 的提示 | 实际原因 |
|---|---|---|
| 改列类型 | 保存不允许保存更改 | 要删表重建,默认关闭 |
| 表被别人锁着 | 超时已过期 | 有事务没提交 |
| 加非空列不设默认 | 必须填默认值 | 已有行没法填 |

  第一条最出名:SSMS 默认禁止「保存时重建表」。
  因为改列类型这种事,SQL Server 的做法是「建新表、搬数据、删旧表」,中间可能丢数据。
  选项在「工具 → 选项 → 设计器 → 表设计器和数据库设计器」里,勾上就能存,但风险自负。

  ④ 给已有数据的表加非空列
  --------------------------------------------------------------------
  加了 memo 列之后,原有的三行:
    id=1 name=甲 memo=None
    id=2 name=乙 memo=None
    id=3 name=丙 memo=None
  新列对老行只能是 NULL——它不知道老行的 memo 该填什么。
  所以要加 NOT NULL 列,必须同时给 DEFAULT,否则 SQL Server 直接拒绝。
  试着加不带默认值的非空列:
    Cannot add a NOT NULL column with default value NULL
  SQLite 的报错是 cannot add a NOT NULL column with default value NULL。
  SQL Server 的说法是「ALTER TABLE 只允许添加可以包含空值的列」。两种说法一个意思。

第一段只有一行数据,但把三种「没填」的结果都演出来了: id 自增填了 1,sex 用了默认值「未知」,age 变成 NULL。 age 是 NULL 而不是 0,这一条最容易误解。 默认值只在「有 DEFAULT 声明」时才生效; 没声明就是 NULL,不管这一列允不允许为空。 第二段加了 memo 列,原有的两行 memo 全是 None——新列对老行只能是 NULL, 它不知道老行的备注该填什么。 第三段就是 SSMS 那个「必须填默认值」的提示的来源。 代码里报的是Cannot add a NOT NULL column with default value NULL, SQL Server 的说法是「ALTER TABLE 只允许添加可以包含空值的列」——一个意思。

30 秒实验

  1. 在 SSMS 里建一张表,只勾一个自增主键和一个普通列,保存后右键「编写表脚本」看它生成了什么。 你会发现自增那一行写的是 IDENTITY(1,1),不是界面上的「标识」两个字。
  2. 把一张表的「允许 Null」取消勾选,保存时看它报什么。 如果表里有 NULL 值,保存会失败——它不会替你把 NULL 改成空字符串。
  3. 想一想:为什么 WHERE phone <> '' 会漏掉 phone IS NULL 的行? 因为 NULL 参与任何比较都返回「未知」,未知不是「真」,所以那行不进结果集。

常见误区

  1. 「允许 Null 勾上就是不能为空」。反过来。 勾上「允许 Null」= 可以为空;取消勾选 = NOT NULL = 必填。中文界面这个措辞坑了很多人, 看生成的 SQL 里的 NOT NULL 最靠谱。
  2. 「NULL 就是空字符串」。完全不同。 NULL 是「不知道」,空字符串是「知道,就是空的」。COUNT(列) 不数 NULL 但数空字符串, 这就是统计对不上的常见原因。
  3. 「界面能改就都从界面改」。改列类型这种操作,界面会让你「重建表」, 风险比写 ALTER TABLE 大。复杂改动一律写脚本,先在测试库上跑一遍。
自测三问(点开对答案) 1. 一列既是 `NOT NULL` 又有 `DEFAULT 0`,插入时不给值会怎样? —— **填 0。** `NOT NULL` 管「不许是 NULL」,`DEFAULT` 管「没给值时填什么」,两者不冲突。 如果只 `NOT NULL` 没有 `DEFAULT`,插入时必须显式给值,否则报错。 2. 为什么判断 NULL 不能写 `= NULL`? —— **因为 NULL 是「不知道」。** 「不知道等于什么」这件事无法判断,所以 `= NULL` 返回未知, 不返回真。**必须用 `IS NULL` / `IS NOT NULL`**,这是 SQL 里唯一正确的写法。 3. 给一张有 10 万行的表加 `NOT NULL` 列,为什么会被拒绝? —— **因为那 10 万行的新列没值可填。** 加上 `DEFAULT` 就能过——SQL Server 会拿默认值把老行填上。 不加默认值又要求非空,逻辑上就是矛盾的。

下一篇 5.2b 把这些操作全写成语句。 界面上点不动的那些(改列类型、加约束、删列),语句里都能做——而且语句能存成脚本, 下次换台机器直接跑。

相关笔记