PostgreSQL: PostgreSQL数据表创建与修改
最后更新:2026-08-26
数据表是数据库中最核心的对象——它是存储数据的"电子表格",每一行是一条记录,每一列是一个字段。
1. 你将学到
- CREATE TABLE 创建表(列定义、约束、默认值)
- PostgreSQL 常用数据类型速览
- 主键 / 外键 / 唯一 / 非空 / CHECK 约束
- ALTER TABLE 修改表结构
- DROP TABLE 删除表
- psql \d 查看表结构
2. 一个电商团队的真实故事
(1) 痛点:三张核心表怎么设计
Alice 的团队接到新需求,要给电商系统创建 3 张核心表:users(用户)、products(商品)、orders(订单)。需求如下:
- 用户表:邮箱唯一、密码不能为空、注册时间自动记录
- 商品表:价格必须大于 0、库存不能为负数
- 订单表:关联用户和商品、状态只能是固定几种
Alice 不确定:约束该加在哪里?数据类型怎么选?外键级联删除怎么设置?
(2) CREATE TABLE + 约束的解法
PostgreSQL 支持在建表时直接声明所有约束,让数据库帮你保证数据质量:
SQL
-- Create users table with email uniqueness and auto-timestamp
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
password_hash CHAR(60) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create products table with price > 0 check
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price DECIMAL(10, 2) CHECK (price > 0),
stock INTEGER DEFAULT 0 CHECK (stock >= 0),
attributes JSONB DEFAULT '{}'::jsonb
);
-- Create orders table with foreign keys and status check
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
product_id INTEGER REFERENCES products(id),
quantity INTEGER NOT NULL CHECK (quantity > 0),
status VARCHAR(20) DEFAULT 'pending'
CHECK (status IN ('pending', 'paid', 'shipped', 'delivered', 'cancelled')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
(3) 收益
- 数据库层面保证数据质量(不可能插入价格 < 0 的商品)
- 外键级联删除(删除用户自动删除其所有订单)
- 默认值减少应用层代码(created_at 自动填充)
3. CREATE TABLE 语法
(1) 完整语法结构
SQL
CREATE TABLE [IF NOT EXISTS] table_name (
column_name data_type [column_constraint ...],
...
[, table_constraint ...]
);
(2) 列级约束
| 约束 | 语法 | 说明 |
|---|---|---|
| PRIMARY KEY | column_name type PRIMARY KEY |
主键(唯一 + 非空) |
| NOT NULL | column_name type NOT NULL |
不允许 NULL |
| UNIQUE | column_name type UNIQUE |
值唯一 |
| DEFAULT | column_name type DEFAULT value |
默认值 |
| CHECK | column_name type CHECK (condition) |
条件检查 |
| REFERENCES | column_name type REFERENCES table(col) |
外键引用 |
(3) 表级约束
SQL
-- Composite primary key
CREATE TABLE order_items (
order_id INTEGER,
product_id INTEGER,
quantity INTEGER,
PRIMARY KEY (order_id, product_id)
);
-- Named foreign key with cascade
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
user_id INTEGER,
content TEXT,
CONSTRAINT fk_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE SET NULL
);
4. 数据类型速览
PostgreSQL 支持超过 40 种数据类型,以下是最常用的:
| 类别 | 类型 | 说明 | 示例 |
|---|---|---|---|
| 整数 | SMALLINT |
2 字节,-32768 ~ 32767 | 年龄、数量小值 |
INTEGER (INT) |
4 字节,-21 亿 ~ 21 亿 | 常用整数(推荐默认) | |
BIGINT |
8 字节,±9.2 万亿 | 大 ID、金额(分) | |
| 自增 | SERIAL |
自增 INTEGER | 主键 ID(推荐) |
BIGSERIAL |
自增 BIGINT | 大表主键 | |
IDENTITY |
SQL 标准自增(PG 10+) | 新项目推荐 | |
| 浮点 | REAL |
4 字节,6 位精度 | 科学计算 |
DOUBLE PRECISION |
8 字节,15 位精度 | 科学计算 | |
| 定点 | DECIMAL(p,s) |
精确小数 | 价格、金额(推荐) |
NUMERIC(p,s) |
同 DECIMAL | 同上 | |
| 字符串 | VARCHAR(n) |
变长,有上限 | 名称、邮箱 |
TEXT |
变长,无上限 | 描述、内容 | |
CHAR(n) |
定长 | 哈希值、编码 | |
| 布尔 | BOOLEAN |
true / false / null | 标志位 |
| 日期 | DATE |
日期 | 生日 |
TIMESTAMP |
日期+时间(无时区) | 本地时间 | |
TIMESTAMPTZ |
日期+时间+时区 | 全球应用(推荐) | |
INTERVAL |
时间间隔 | 时长计算 | |
| UUID | UUID |
128-bit UUID | 分布式 ID |
| JSON | JSON |
文本 JSON | 兼容性好 |
JSONB |
二进制 JSON(推荐) | 索引查询快 |
💡 提示: PostgreSQL 中 VARCHAR 和 TEXT 的性能没有差异(与 MySQL 不同)。大多数场景推荐 VARCHAR(有长度限制时) 或 TEXT(无限制时)。
5. 约束详解
(1) 主键 PRIMARY KEY
graph TB
PK[PRIMARY KEY] --> U[UNIQUE<br/>No duplicate values]
PK --> NN[NOT NULL<br/>Cannot be empty]
PK --> IDX[Auto Index<br/>B-Tree index created automatically]
▶ 示例:单列和复合主键
SQL
-- Single-column primary key (most common)
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
-- Composite primary key (for junction tables)
CREATE TABLE product_tags (
product_id INTEGER REFERENCES products(id),
tag_id INTEGER REFERENCES tags(id),
PRIMARY KEY (product_id, tag_id)
);
输出:
TEXT
📖 仅展示
CREATE TABLE
(2) 外键 FOREIGN KEY
| 级联操作 | ON DELETE 行为 | ON UPDATE 行为 |
|---|---|---|
CASCADE |
级联删除(删用户→删其订单) | 级联更新 |
SET NULL |
设为 NULL | 设为 NULL |
SET DEFAULT |
设为默认值 | 设为默认值 |
RESTRICT |
拒绝删除(默认行为) | 拒绝更新 |
NO ACTION |
同 RESTRICT(SQL 标准) | 同 RESTRICT |
▶ 示例:外键与级联操作
SQL
-- Order items: delete order -> delete all its items (CASCADE)
-- Product: delete product -> set product_id to NULL (SET NULL)
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES orders(id) ON DELETE CASCADE,
product_id INTEGER REFERENCES products(id) ON DELETE SET NULL,
quantity INTEGER NOT NULL DEFAULT 1
);
输出:
TEXT
📖 仅展示
CREATE TABLE
(3) CHECK 约束
▶ 示例:CHECK 约束保护数据质量
SQL
-- Price must be positive, discount cannot exceed price
CREATE TABLE promotions (
id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES products(id),
discount_price DECIMAL(10, 2) CHECK (discount_price > 0),
start_date DATE NOT NULL,
end_date DATE NOT NULL,
-- PG CHECK can reference other columns in the same row!
CHECK (end_date > start_date)
-- 注意:CHECK 不能跨表引用子查询,跨表校验需用触发器
);
输出:
TEXT
📖 仅展示
CREATE TABLE
6. ALTER TABLE
(1) 常用修改操作
| 操作 | 语法 | 说明 |
|---|---|---|
| 添加列 | ALTER TABLE t ADD COLUMN col type |
新增一列 |
| 删除列 | ALTER TABLE t DROP COLUMN col |
删除一列 |
| 重命名列 | ALTER TABLE t RENAME COLUMN old TO new |
列改名 |
| 修改类型 | ALTER TABLE t ALTER COLUMN col TYPE new_type |
修改数据类型 |
| 设默认值 | ALTER TABLE t ALTER COLUMN col SET DEFAULT val |
设置默认值 |
| 删默认值 | ALTER TABLE t ALTER COLUMN col DROP DEFAULT |
删除默认值 |
| 设 NOT NULL | ALTER TABLE t ALTER COLUMN col SET NOT NULL |
设非空 |
| 删 NOT NULL | ALTER TABLE t ALTER COLUMN col DROP NOT NULL |
取消非空 |
| 添加约束 | ALTER TABLE t ADD CONSTRAINT name CHECK (...) |
添加 CHECK |
| 重命名表 | ALTER TABLE old_name RENAME TO new_name |
表改名 |
▶ 示例:修改表结构
SQL
-- Add a new column
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Add with default value
ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT true;
-- Change column type (requires USING for incompatible types)
ALTER TABLE users ALTER COLUMN phone TYPE TEXT;
-- Rename column
ALTER TABLE users RENAME COLUMN phone TO phone_number;
-- Set default value
ALTER TABLE users ALTER COLUMN is_active SET DEFAULT true;
-- Add CHECK constraint
ALTER TABLE products ADD CONSTRAINT price_reasonable
CHECK (price < 100000);
-- Drop a column
ALTER TABLE users DROP COLUMN IF EXISTS phone_number;
-- Rename table
ALTER TABLE users RENAME TO customers;
输出:
TEXT
📖 仅展示
-- SQL 语句执行成功
⚠️ 注意: 修改列类型可能需要重写整个表(如 VARCHAR(50)→VARCHAR(100) 不需要,但 INTEGER→TEXT 需要)。大表操作时用
ALTER TABLE ... ALTER COLUMN ... TYPE ... USING ... 并确保 USING 子句正确转换。
7. DROP TABLE
▶ 示例:删除表
SQL
-- Safe delete (no error if table doesn't exist)
DROP TABLE IF EXISTS test_table;
-- Cascade delete (also drops dependent objects like views)
DROP TABLE IF EXISTS users CASCADE;
输出:
TEXT
📖 仅展示
-- SQL 语句执行成功
| 选项 | 说明 |
|---|---|
IF EXISTS |
表不存在时不报错 |
CASCADE |
同时删除依赖该表的对象(视图、外键引用等) |
RESTRICT |
有依赖对象时拒绝删除(默认行为) |
🔥 易错: DROP TABLE 是不可逆操作!所有数据永久丢失。生产环境务必先
pg_dump 备份。
8. psql 查看表结构
| 命令 | 功能 | 示例 |
|---|---|---|
\dt |
列出所有表 | \dt |
\dt+ |
列出表(含大小信息) | \dt+ |
\d tablename |
查看表结构 | \d users |
\d+ tablename |
查看表结构(详细) | \d+ users |
▶ 示例:查看表结构
BASH
# List all tables in current database
\dt
# Show users table structure
\d users
输出:
TEXT
📖 仅展示
Table "public.users"
Column | Type | Collation | Nullable | Default
-------------+------------------------+-----------+----------+-----------------------------------
id | integer | | not null | nextval('users_id_seq'::regclass)
email | character varying(255) | | not null |
name | character varying(100) | | not null |
password_hash | character(60) | | not null |
created_at | timestamp with time zone | | | now()
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_email_key" UNIQUE CONSTRAINT, btree (email)
9. 完整示例:电商核心三表
SQL
-- ============================================
-- Complete example: e-commerce core tables
-- Users, Products, Orders with proper constraints
-- ============================================
-- 1. Users table
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
password_hash CHAR(60) NOT NULL,
role VARCHAR(20) DEFAULT 'customer'
CHECK (role IN ('customer', 'admin', 'manager')),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 2. Products table
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL CHECK (price > 0),
stock INTEGER DEFAULT 0 CHECK (stock >= 0),
category VARCHAR(50),
attributes JSONB DEFAULT '{}'::jsonb,
is_available BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 3. Orders table with foreign keys
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(20) DEFAULT 'pending'
CHECK (status IN ('pending', 'paid', 'shipped', 'delivered', 'cancelled')),
total_amount DECIMAL(12, 2) DEFAULT 0 CHECK (total_amount >= 0),
shipping_address TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 4. Order items table (junction table)
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price DECIMAL(10, 2) NOT NULL CHECK (unit_price > 0),
subtotal DECIMAL(12, 2) GENERATED ALWAYS AS (quantity * unit_price) STORED
);
-- 5. Insert sample data
INSERT INTO users (email, name, password_hash) VALUES
('alice@example.com', 'Alice', '$2a$12$dummyhashforalice1234567890abcdefghijklmnopqr'),
('bob@example.com', 'Bob', '$2a$12$dummyhashforbob1234567890abcdefghijklmnopqrstuv');
INSERT INTO products (name, price, stock, category, attributes) VALUES
('Running Shoes', 89.99, 150, 'Footwear', '{"color": "red", "size": 42}'::jsonb),
('Laptop Backpack', 49.99, 300, 'Accessories', '{"color": "black", "material": "nylon"}'::jsonb);
-- 6. Verify table structure
\d users
\d products
\d orders
\d order_items
❓ 常见问题
Q SERIAL 和 IDENTITY 有什么区别?该用哪个?
A SERIAL 是 PG 专有语法,内部创建一个序列+设默认值。IDENTITY 是 SQL 标准语法(PG 10+),行为更严格(不能手动插入值除非 OVERRIDING SYSTEM VALUE)。新项目推荐用 IDENTITY。
Q VARCHAR 和 TEXT 该选哪个?
A PG 中 VARCHAR 和 TEXT 性能完全相同。VARCHAR(n) 有长度限制,TEXT 无限制。建议:有明确长度限制用 VARCHAR(如邮箱 VARCHAR(255)),无限制用 TEXT(如文章内容)。不要用 VARCHAR 不加长度——那等于 TEXT。
Q TIMESTAMP 和 TIMESTAMPTZ 该选哪个?
A 推荐始终用 TIMESTAMPTZ(带时区)。它存储 UTC 时间,显示时自动转换为客户端时区。TIMESTAMP 不带时区,跨时区应用会产生混乱。
Q 外键级联删除 CASCADE 安全吗?
A CASCADE 在开发环境很方便(删用户→删订单→删订单项,三级级联),但在生产环境要谨慎——误删一个用户可能连带删除大量数据。建议:关键业务表用 RESTRICT,日志/临时表用 CASCADE。
Q ALTER TABLE 修改大表会锁表吗?
A 简单操作(ADD COLUMN 设置 DEFAULT、改 VARCHAR 长度增大)不锁表。但改数据类型、加 NOT NULL 约束等需要全表扫描,会锁表。大表操作建议用
CREATE INDEX CONCURRENTLY(后续课程讲解)或维护窗口执行。Q 一个表最多能有多少列?
A PG 单表最多 1600 列(实际受行大小 8KB 限制影响可能更少)。但超过 50 列通常是设计问题——考虑拆表或使用 JSONB 存储稀疏字段。
📖 小节
- CREATE TABLE 定义表结构,列定义包含数据类型 + 约束 + 默认值
- 数据类型选择:整数用 INTEGER/SERIAL,金额用 DECIMAL,时间用 TIMESTAMPTZ,灵活结构用 JSONB
- 五大约束:PRIMARY KEY / FOREIGN KEY / UNIQUE / NOT NULL / CHECK
- 外键级联:CASCADE(级联)/ SET NULL / RESTRICT(默认),根据业务需求选择
- ALTER TABLE 可添加/删除/重命名列,修改类型和约束
- DROP TABLE 不可逆,IF EXISTS 防报错,CASCADE 删除依赖对象
- psql \d 查看表结构,\dt 列出所有表
📝 作业
-
基础题(难度⭐):创建一个
categories表(id、name、description、created_at),其中 name 不能为空且唯一。插入 3 条测试数据后,用\d categories查看表结构。 -
进阶题(难度⭐⭐):在本课创建的电商三表基础上,用 ALTER TABLE 给 users 表添加
last_login_at TIMESTAMPTZ列和login_count INTEGER DEFAULT 0列。然后添加一个 CHECK 约束确保 login_count 不能为负数。 -
挑战题(难度⭐⭐⭐):创建一个
product_reviews表,包含:id(主键)、product_id(外键引用 products)、user_id(外键引用 users)、rating(1-5 分,CHECK 约束)、title、content、created_at。要求:删除用户时保留评论但 user_id 设为 NULL,删除商品时级联删除其所有评论。