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 秒实验
- 在 SSMS 里建一张表,只勾一个自增主键和一个普通列,保存后右键「编写表脚本」看它生成了什么。
你会发现自增那一行写的是
IDENTITY(1,1),不是界面上的「标识」两个字。 - 把一张表的「允许 Null」取消勾选,保存时看它报什么。 如果表里有 NULL 值,保存会失败——它不会替你把 NULL 改成空字符串。
- 想一想:为什么
WHERE phone <> ''会漏掉phone IS NULL的行? 因为 NULL 参与任何比较都返回「未知」,未知不是「真」,所以那行不进结果集。
常见误区
- 「允许 Null 勾上就是不能为空」。反过来。
勾上「允许 Null」= 可以为空;取消勾选 = NOT NULL = 必填。中文界面这个措辞坑了很多人,
看生成的 SQL 里的
NOT NULL最靠谱。 - 「NULL 就是空字符串」。完全不同。
NULL 是「不知道」,空字符串是「知道,就是空的」。
COUNT(列)不数 NULL 但数空字符串, 这就是统计对不上的常见原因。 - 「界面能改就都从界面改」。改列类型这种操作,界面会让你「重建表」,
风险比写
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 把这些操作全写成语句。 界面上点不动的那些(改列类型、加约束、删列),语句里都能做——而且语句能存成脚本, 下次换台机器直接跑。
- ☐ (本篇完,下一篇 5.2b 用语句建表、改表与删表)