创建表(CREATE TABLE)基础
本教程共 50 篇 · 第 8 篇 · 更新于 2026-07-31 · 约 8 分钟阅读
8. 创建表(CREATE TABLE)基础
本节目标:学完你能独立写出 CREATE TABLE 语句,建出带列、类型和约束的表,并查看它的结构。
表是存数据的地方,建表是数据库里最基本的动作。这一章我们把它拆开讲:列怎么定义、类型怎么选、约束是什么,最后用 \d 看结构。
建表的基本语法
CREATE TABLE table_name (
column1 datatype constraints,
column2 datatype constraints,
...
);
一张表由若干「列」组成,每列要说明:名字、数据类型,以及可选的约束。表名在同一模式内不能和别的表、视图等重名。
Note如果担心表已存在会报错,可以加
IF NOT EXISTS:CREATE TABLE IF NOT EXISTS 名 (...)。表已在时它会给个提示而不是报错,写初始化脚本时很实用。
列定义与类型初见
我们拿贯穿全书的 users 表来建。它包含:id、username、email、age、created_at。
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
age INT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
逐列解释一下用到的类型:
INT:整数,存 id、age 这类没有小数的数。VARCHAR(50):变长字符串,括号里是最大长度。存用户名、邮箱。TIMESTAMPTZ:带时区的时间戳。存创建时间。
Tip
VARCHAR(n)的n是上限不是固定占用。和TEXT(不限长字符串)相比,VARCHAR 多了一道长度校验,其余几乎一样。新手记不住就用TEXT,想加限制就用VARCHAR(n)。
类型这一章只初见,后面有专门章节细讲整数、字符、时间等。你现在只要知道「每列必须有个类型」即可。
类型选错的代价
类型选得不合适,后面会很麻烦。举个例:如果你把 age 设成 VARCHAR,那「年龄比较大小」就会变成字符串比较,9 会比 10 大,统计全错。所以数字就用数字类型,时间就用时间类型,别图省事全用 TEXT。
Warning表建好之后改列类型不是不行,但要小心数据转换。比如 TEXT 转 INT,里面若有非数字内容就会报错。建表时把类型想清楚,比事后折腾省事得多。
约束初见
约束(constraint)是对列里数据的规定,用来保证数据靠谱。上面那张表已经用到了几个:
PRIMARY KEY(主键):唯一标识一行。一张表只能有一个主键。这里作用在id上,意味着每行 id 不重复。NOT NULL(非空):这列不能留空。比如用户名必须有。UNIQUE(唯一):这列的值在表里不能重复。比如邮箱不能两人相同。DEFAULT(默认):没填时用的值。created_at默认取now(),即插入时的当前时间。
再看订单表 orders,它用外键关联到 users:
CREATE TABLE orders (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id),
amount NUMERIC(10,2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
这里 user_id INT NOT NULL REFERENCES users(id) 是「外键」的雏形:它要求 orders.user_id 的值必须能在 users.id 里找到,保证订单一定属于某个存在的用户。外键细节后面章节会展开,这里先见个面。
Note主键那个
GENERATED ALWAYS AS IDENTITY负责自增:你插入时不填 id,Postgres 自动按顺序生成。这是 PostgreSQL 18 推荐写法。老的SERIAL写法现在只作为历史兼容保留,新代码别用了。
NUMERIC(10,2) 表示最多 10 位数字、其中 2 位是小数,正好用来存金额(如 99.99)。金额千万别用浮点类型,会有精度误差,后面类型章节细说。
给约束起个名字
约束其实都有名字。你不写,Postgres 会自动起一个(比如 users_email_key)。但自动名不好记,出了错不好定位。推荐显式命名:
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
CONSTRAINT uq_users_email UNIQUE (email)
);
这样万一邮箱重复报错,错误信息里会出现 uq_users_email,一眼就知道是哪条规则。表建多之后,起名是好习惯。
建表后的提示
语句成功会显示:
CREATE TABLE
如果报错,常见原因是语法拼错、括号没配对、或类型名写错。仔细检查逗号、括号和分号。
Tip逗号是列与列之间的分隔符,最后一行列定义后面不要加逗号,这是最常见的语法错误。报错信息会指出出错位置,照着改就行。
查看表结构
建完想确认对不对,用 psql 的 \d 看结构。
看当前库所有表:
\dt
看某张表的详细结构:
\d users
输出类似:
Table "public.users"
Column | Type | Collation | Nullable | Default
------------+--------------------------+-----------+----------+----------------------------------
id | integer | | not null | generated always as identity
username | character varying(50) | | not null |
email | character varying(100) | | not null |
age | integer | | |
created_at | timestamp with time zone | | not null | now()
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_key" UNIQUE CONSTRAINT, btree (email)
你看,约束都列在底下:主键 users_pkey、邮箱唯一 users_email_key。一目了然。
Tip想看更全的信息(比如表大小、描述),用
\d+ users。\d系列命令是排查表结构的利器,建议熟记。
一个常见坑
CREATE TABLE Users (...);
SELECT * FROM users;
Postgres 里未加引号的标识符会自动转小写,所以 Users 和 users 是同一张表,这没问题。但如果你建表时用了双引号 "Users",那它就真成了大写开头,以后查也必须带双引号。所以新手建议一律用小写,别用双引号裹表名,能省很多麻烦。
Warning表名、列名尽量用小写字母、数字、下划线。中文或大小写混用虽然支持,但容易在后续查询里踩坑。比如
"UserName"和username在 Postgres 眼里是两个不同的列。
小结
建表就是列定义 + 类型 + 约束。记住自增用 GENERATED ALWAYS AS IDENTITY、主键用 PRIMARY KEY、非空用 NOT NULL。建完用 \d 表名 检查结构。
临时表(TEMPORARY TABLE)
有些中间结果只想在本次会话用一下,不想落进正式库,可以用临时表:
CREATE TEMP TABLE tmp_stat (
user_id INT,
cnt INT
);
临时表只在当前连接可见,断开后自动销毁。做复杂统计时的「中转站」特别好用,不会污染正式表空间。
Note临时表优先于普通表被找到,即使普通库里有个同名表,会话内写的
tmp_stat也会先命中临时那个。用完断开连接它就消失了,别指望下次还能看到。
用 LIKE 复制表结构
想建一张和现有表结构一样的空表(不要数据),用 LIKE:
CREATE TABLE users_bak (LIKE users INCLUDING ALL);
INCLUDING ALL 会把列、约束、索引一起复制过来,只是没有数据。做表备份骨架、或新建一张结构相同的表时很方便。
建表前先看是否存在
前面说过 IF NOT EXISTS。完整写法配上约束长这样:
CREATE TABLE IF NOT EXISTS users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username VARCHAR(50) NOT NULL
);
表已有时,它只提示 NOTICE: relation "users" already exists,不报错。写初始化脚本(可能重复跑)时必加,避免第二次跑就崩。
表建在指定模式里
结合上一章,建表时带上模式前缀就落进对应模式:
CREATE TABLE app.users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL
);
前提是 app 模式已存在,且你有在那建表的权限。不写前缀就按 search_path 落库,默认是 public。
写完建表语句后该做什么
建完表别急着插数据,先两步自检:
\d 表名看结构对不对,列类型、约束是否如预期。- 想得过的,先插一两行试试约束会不会误伤正常数据,再批量导入。
Tip我习惯建完表立刻
\d+ 表名,把输出截图或记下来,作为「设计契约」。后面代码对着这个契约写,字段名、类型对不上就一眼能发现。
下一章我们会讲怎么修改已经建好的表。