第五章:表与约束
最后更新: 2024-01-01
作者: PostgreSQL Team
页面目录
第五章:表与约束
5.1 表设计原则
良好的表设计是数据库性能和数据完整性的基础。
┌─────────────────────────────────────────────────────────────┐
│ 表设计核心原则 │
├─────────────────────────────────────────────────────────────┤
│ 1. 规范化设计(避免数据冗余) │
│ 2. 适当反规范化(提升查询性能) │
│ 3. 主键设计(唯一标识记录) │
│ 4. 索引设计(优化查询效率) │
│ 5. 约束设计(保证数据完整性) │
└─────────────────────────────────────────────────────────────┘
5.2 约束类型
PostgreSQL 支持多种约束类型:
| 约束类型 | 说明 |
|---|---|
| NOT NULL | 非空约束 |
| UNIQUE | 唯一约束 |
| PRIMARY KEY | 主键约束 |
| FOREIGN KEY | 外键约束 |
| CHECK | 检查约束 |
| EXCLUDE | 排他约束 |
5.3 NOT NULL 约束
-- 创建表并定义 NOT NULL
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
age INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 添加 NOT NULL 约束
ALTER TABLE users ALTER COLUMN age SET NOT NULL;
-- 移除 NOT NULL 约束
ALTER TABLE users ALTER COLUMN age DROP NOT NULL;
-- 测试约束
INSERT INTO users (username, email) VALUES ('test', 'test@example.com');
-- 成功
INSERT INTO users (username, email) VALUES (NULL, 'test@example.com');
-- 失败: ERROR: null value in column "username" violates not-null constraint
5.4 UNIQUE 约束
-- 单列唯一约束
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE
);
-- 多列唯一约束(复合唯一)
CREATE TABLE user_products (
user_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER DEFAULT 1,
UNIQUE (user_id, product_id) -- 复合唯一键
);
-- 使用表约束语法
CREATE TABLE products (
id SERIAL PRIMARY KEY,
code VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
CONSTRAINT uk_products_code UNIQUE (code)
);
-- 给唯一约束创建索引(自动创建)
\d users -- 会看到唯一的索引名
5.5 PRIMARY KEY 约束
主键是表中每行记录的唯一标识。
-- 单列主键
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
-- 复合主键
CREATE TABLE order_items (
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER DEFAULT 1,
PRIMARY KEY (order_id, product_id)
);
-- 添加主键约束到已有表
ALTER TABLE users ADD PRIMARY KEY (id);
-- 删除主键约束
ALTER TABLE users DROP CONSTRAINT users_pkey;
5.5.1 自增主键
-- SERIAL 方式
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(50)
);
-- IDENTITY 方式(SQL 标准,推荐)
CREATE TABLE users (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(50)
);
-- 指定序列选项
CREATE TABLE users (
id INTEGER GENERATED BY DEFAULT AS IDENTITY (
START WITH 1000
INCREMENT BY 1
MINVALUE 1000
MAXVALUE 999999999
CYCLE
) PRIMARY KEY,
name VARCHAR(50)
);
-- 获取当前序列值
SELECT currval('users_id_seq');
SELECT lastval();
SELECT nextval('users_id_seq');
5.6 FOREIGN KEY 约束
外键约束用于维护表之间的引用完整性。
-- 创建带外键的表
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_amount DECIMAL(10, 2)
);
-- 完整语法
CREATE TABLE order_items (
item_id SERIAL PRIMARY KEY,
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER DEFAULT 1,
price DECIMAL(10, 2),
FOREIGN KEY (order_id) REFERENCES orders(order_id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
ON DELETE RESTRICT
);
-- 使用表约束语法
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_orders_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE RESTRICT
);
5.6.1 外键动作
-- ON DELETE 和 ON UPDATE 选项:
-- NO ACTION - 默认,引用的行存在时阻止删除/更新
-- RESTRICT - 类似于 NO ACTION,但不可延迟
-- CASCADE - 删除/更新时同步删除/更新引用行
-- SET NULL - 删除/更新时设置引用列为 NULL
-- SET DEFAULT - 删除/更新时设置引用列为默认值
-- 示例:CASCADE 删除
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(200),
content TEXT
);
-- 删除用户时,所有帖子自动删除
DELETE FROM users WHERE id = 1;
5.6.2 外键约束验证
-- 检查外键是否有效
SELECT
tc.constraint_name,
tc.table_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_name = 'orders';
-- 查找孤立数据
SELECT * FROM orders WHERE user_id NOT IN (SELECT id FROM users);
5.7 CHECK 约束
CHECK 约束用于限制列中值的范围。
-- 列级 CHECK 约束
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10, 2) CHECK (price > 0),
quantity INTEGER CHECK (quantity >= 0),
discount DECIMAL(3, 2) CHECK (discount >= 0 AND discount <= 1)
);
-- 表级 CHECK 约束
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
subtotal DECIMAL(10, 2),
tax DECIMAL(10, 2),
total DECIMAL(10, 2),
CHECK (tax >= 0),
CHECK (subtotal >= 0),
CHECK (total = subtotal + tax)
);
-- 添加 CHECK 约束
ALTER TABLE products ADD CONSTRAINT chk_products_price
CHECK (price > 0 AND price < 100000);
-- 命名 CHECK 约束(便于管理)
ALTER TABLE products ADD CONSTRAINT chk_products_discount
CHECK (discount >= 0 AND discount <= 1);
-- 删除 CHECK 约束
ALTER TABLE products DROP CONSTRAINT chk_products_price;
5.7.1 CHECK 约束示例
-- 邮箱格式验证
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255),
CONSTRAINT chk_users_email
CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
);
-- 手机号格式验证
CREATE TABLE contacts (
id SERIAL PRIMARY KEY,
phone VARCHAR(20),
CONSTRAINT chk_contacts_phone
CHECK (phone ~ '^\+?[1-9]\d{1,14}$')
);
-- 日期范围验证
CREATE TABLE bookings (
id SERIAL PRIMARY KEY,
check_in DATE,
check_out DATE,
CONSTRAINT chk_bookings_dates
CHECK (check_out > check_in)
);
5.8 EXCLUDE 约束
排他约束确保特定比较运算符下没有重复值。
-- 安装 btree_gist 扩展(支持范围类型排除)
CREATE EXTENSION IF NOT EXISTS btree_gist;
-- 防止同一房间同一时间段被重复预订
CREATE TABLE room_bookings (
id SERIAL PRIMARY KEY,
room_number INTEGER NOT NULL,
booking_period TSTZRANGE NOT NULL,
guest_name VARCHAR(100),
EXCLUDE USING gist (
room_number WITH =,
booking_period WITH &&
)
);
-- 测试排他约束
INSERT INTO room_bookings (room_number, booking_period, guest_name)
VALUES (101, '[2024-06-15 14:00, 2024-06-18 12:00)', '张三');
-- 这个会失败(时间段重叠)
INSERT INTO room_bookings (room_number, booking_period, guest_name)
VALUES (101, '[2024-06-16 14:00, 2024-06-19 12:00)', '李四');
-- ERROR: conflicting key value violates exclusion constraint
5.9 模式 (Schema)
模式是数据库的命名空间,用于组织表和其他对象。
5.9.1 默认模式
PostgreSQL 默认使用 public 模式。
-- 查看所有模式
SELECT schema_name FROM information_schema.schemata;
-- 查看 public 模式中的表
\dt public.*
-- 在 public 模式中创建表
CREATE TABLE users (id SERIAL PRIMARY KEY);
-- 等价于
CREATE TABLE public.users (id SERIAL PRIMARY KEY);
5.9.2 创建模式
-- 创建新模式
CREATE SCHEMA hr;
-- 创建带授权的模式
CREATE SCHEMA analytics AUTHORIZATION db_admin;
-- 创建并指定默认所有者
CREATE SCHEMA production OWNER db_admin;
-- 使用模式前缀创建对象
CREATE TABLE hr.employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
salary DECIMAL(10, 2)
);
5.9.3 模式搜索路径
-- 查看当前搜索路径
SHOW search_path;
SELECT current_schema();
-- 设置搜索路径
SET search_path TO hr, public;
-- 恢复默认路径
SET search_path TO DEFAULT;
-- 在数据库级别设置默认搜索路径
ALTER DATABASE mydb SET search_path TO hr, public;
5.9.4 模式管理
-- 重命名模式
ALTER SCHEMA hr RENAME TO human_resources;
-- 修改模式所有者
ALTER SCHEMA human_resources OWNER TO new_admin;
-- 删除空模式
DROP SCHEMA hr;
-- 删除模式及其所有对象
DROP SCHEMA hr CASCADE;
-- 将对象移动到另一个模式
ALTER TABLE hr.employees SET SCHEMA public;
ALTER TABLE employees SET SCHEMA production;
5.9.5 模式使用示例
-- 多租户数据库设计
CREATE SCHEMA tenant_a;
CREATE SCHEMA tenant_b;
CREATE TABLE tenant_a.users (id SERIAL, name TEXT);
CREATE TABLE tenant_b.users (id SERIAL, name TEXT);
-- 按模式查询
SELECT * FROM tenant_a.users;
SELECT * FROM tenant_b.users;
5.10 表管理
5.10.1 重命名表
ALTER TABLE old_table_name RENAME TO new_table_name;
5.10.2 修改表所有者
ALTER TABLE users OWNER TO new_owner;
5.10.3 清空表
-- 删除所有行(保留表结构,可重置序列)
TRUNCATE TABLE users RESTART IDENTITY;
-- 级联清空多个表
TRUNCATE TABLE users, orders, products RESTART IDENTITY CASCADE;
-- 只清空(不重置序列)
TRUNCATE TABLE users;
5.10.4 复制表
-- 复制表结构
CREATE TABLE users_copy (LIKE users INCLUDING ALL);
-- 复制表结构和数据
CREATE TABLE users_backup AS SELECT * FROM users;
-- 复制带条件的表
CREATE TABLE active_users AS
SELECT * FROM users WHERE status = true;
-- 复制到远程数据库
CREATE TABLE remote_users (LIKE users INCLUDING ALL);
5.10.5 查看表信息
-- 列出当前模式所有表
\dt
-- 列出所有模式中的表
\dt *.*
-- 查看表结构
\d users
-- 查看表详细信息
\d+ users
-- 查看表大小
SELECT pg_size_pretty(pg_total_relation_size('users'));
SELECT pg_size_pretty(pg_relation_size('users'));
-- 查看表占用空间
SELECT
relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROM pg_catalog.pg_statio_user_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(relid) DESC;
5.11 注释管理
-- 给表添加注释
COMMENT ON TABLE users IS '用户信息表';
-- 给列添加注释
COMMENT ON COLUMN users.username IS '用户名,唯一标识';
-- 查看注释
SELECT obj_description('users'::regclass);
SELECT col_description('users'::regclass, 2); -- 2 是列的序号
5.12 本章小结
本章介绍了 PostgreSQL 的表设计和约束管理:
| 主题 | 关键点 |
|---|---|
| 约束类型 | NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY, CHECK |
| 模式管理 | CREATE SCHEMA, search_path, 模式命名空间 |
| 表维护 | RENAME, TRUNCATE, COPY |
| 最佳实践 | 合理使用约束,保证数据完整性 |
📌 下一章预告
下一章将深入介绍 PostgreSQL 的查询语法,包括复杂查询、窗口函数和高级查询技巧。