Appearance
修改表结构 (ALTER TABLE)
在实际项目开发中,很少有表结构在设计之初就是完美的。随着业务需求的变化,我们需要:
- 📊 添加新功能字段 - 比如给商品表添加描述字段
- 🗑️ 删除无用字段 - 清理不再使用的冗余列
- 🔒 增强数据约束 - 添加检查条件保证数据质量
- 🔄 调整数据类型 - 适应新的业务需求
- 📝 重命名优化 - 让字段名更符合规范
⚠️ 重要提醒
与修改表中的数据不同,ALTER TABLE 是修改表的结构定义。在生产环境中执行这些操作前,务必做好数据备份!
1. 添加列 (ADD COLUMN)
基本语法
sql
ALTER TABLE 表名 ADD COLUMN 列名 数据类型 [约束条件];
实战示例
示例 1:添加基本列
问题场景:电商系统的商品表需要增加商品描述字段
sql
-- 原始表结构
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL
);
-- 添加描述列
ALTER TABLE products ADD COLUMN description TEXT;
-- 查看表结构
\d products
执行结果:
txt
Table "public.products"
Column | Type | Collation | Nullable | Default
--------------+------------------------+-----------+----------+---------------------------------------
product_id | integer | | not null | nextval('products_product_id_seq'::regclass)
product_name | character varying(100) | | not null |
price | numeric(10,2) | | not null |
description | text | | |
示例 2:添加带默认值的列
sql
-- 添加状态列,默认为激活状态
ALTER TABLE products ADD COLUMN status VARCHAR(20) DEFAULT 'active';
-- 验证现有数据
SELECT product_id, product_name, status FROM products;
输出示例:
product_id | product_name | status
------------+--------------+--------
1 | iPhone 15 | active
2 | MacBook Pro | active
💡 性能优化提示从 PostgreSQL 11 开始,添加带常量默认值的列非常快速!系统不会立即更新所有行,而是在访问时动态返回默认值。
示例 3:添加带约束的列
sql
-- 添加评分列,限制范围1-5
ALTER TABLE products ADD COLUMN rating INTEGER
CHECK (rating >= 1 AND rating <= 5);
-- 添加创建时间列,不允许为空
ALTER TABLE products ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
注意事项对比表
默认值类型 | 性能影响 | 适用场景 | 示例 |
---|---|---|---|
常量值 | ⚡ 极快 | 固定默认值 | DEFAULT 'active' |
函数调用 | ⚠️ 较慢 | 动态值 | DEFAULT CURRENT_TIMESTAMP |
无默认值 | ⚡ 极快 | 后续手动填充 | 不加 DEFAULT |
2. 删除列 (DROP COLUMN)
基本语法
sql
ALTER TABLE 表名 DROP COLUMN 列名 [CASCADE | RESTRICT];
实战示例
示例 1:安全删除列
问题场景:商品表的某个字段不再需要
sql
-- 首先检查列的依赖关系
SELECT
tc.table_name,
tc.constraint_name,
tc.constraint_type
FROM information_schema.table_constraints tc
WHERE tc.table_name = 'products';
-- 安全删除列
ALTER TABLE products DROP COLUMN description;
-- 验证删除结果
\d products
示例 2:强制删除有依赖的列
sql
-- 创建外键依赖示例
CREATE TABLE order_items (
item_id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES products(product_id),
quantity INTEGER NOT NULL
);
-- 尝试删除被引用的列(会失败)
-- ALTER TABLE products DROP COLUMN product_id; -- 这会报错
-- 强制删除及其所有依赖
ALTER TABLE products DROP COLUMN product_id CASCADE;
⚠️ CASCADE 警告使用 CASCADE
会删除所有依赖该列的对象(外键、视图等)。在生产环境中使用前请仔细评估影响范围!
删除操作流程图
3. 添加约束 (ADD CONSTRAINT)
约束类型总览
约束类型 | 语法格式 | 用途说明 |
---|---|---|
CHECK | ADD CHECK (condition) | 数据值验证 |
UNIQUE | ADD UNIQUE (column) | 唯一性约束 |
FOREIGN KEY | ADD FOREIGN KEY (col) REFERENCES table(col) | 外键关联 |
NOT NULL | ALTER COLUMN col SET NOT NULL | 非空约束 |
PRIMARY KEY | ADD PRIMARY KEY (column) | 主键约束 |
实战示例
示例 1:添加检查约束
问题场景:确保商品价格为正数,商品名不为空字符串
sql
-- 添加价格检查约束
ALTER TABLE products ADD CONSTRAINT check_positive_price
CHECK (price > 0);
-- 添加名称检查约束
ALTER TABLE products ADD CONSTRAINT check_name_not_empty
CHECK (product_name <> '' AND LENGTH(TRIM(product_name)) > 0);
-- 测试约束效果
INSERT INTO products (product_name, price) VALUES ('iPhone 15', -100); -- 会失败
INSERT INTO products (product_name, price) VALUES ('', 999); -- 会失败
INSERT INTO products (product_name, price) VALUES ('iPhone 15', 999); -- 成功
错误示例输出:
ERROR: new row for relation "products" violates check constraint "check_positive_price"
DETAIL: Failing row contains (3, iPhone 15, -100.00, null, active, null, 2024-05-27 10:30:00).
示例 2:添加唯一约束
sql
-- 添加商品编号唯一约束
ALTER TABLE products ADD CONSTRAINT unique_product_code
UNIQUE (product_code);
-- 验证唯一约束
INSERT INTO products (product_code, product_name, price) VALUES ('P001', 'iPhone 15', 999); -- 成功
INSERT INTO products (product_code, product_name, price) VALUES ('P002', 'MacBook Pro', 1999); -- 成功
INSERT INTO products (product_code, product_name, price) VALUES ('P001', 'iPad Pro', 799); -- 失败:重复
示例 3:添加外键约束
sql
-- 首先创建分类表
CREATE TABLE categories (
category_id SERIAL PRIMARY KEY,
category_name VARCHAR(50) NOT NULL UNIQUE
);
-- 插入一些分类数据
INSERT INTO categories (category_name) VALUES
('电子产品'), ('服装'), ('图书');
-- 给商品表添加分类字段和外键约束
ALTER TABLE products ADD COLUMN category_id INTEGER;
ALTER TABLE products ADD CONSTRAINT fk_products_category
FOREIGN KEY (category_id) REFERENCES categories(category_id);
-- 测试外键约束
UPDATE products SET category_id = 1 WHERE product_name = 'iPhone 15'; -- 成功
UPDATE products SET category_id = 99 WHERE product_name = 'MacBook Pro'; -- 失败:分类不存在
示例 4:添加 NOT NULL 约束
sql
-- 添加NOT NULL约束前,先确保列中没有NULL值
UPDATE products SET category_id = 1 WHERE category_id IS NULL;
-- 添加NOT NULL约束
ALTER TABLE products ALTER COLUMN category_id SET NOT NULL;
-- 验证约束
INSERT INTO products (product_name, price) VALUES ('新产品', 100); -- 失败:category_id不能为NULL
💡 最佳实践添加约束前,确保现有数据已经满足约束条件,否则添加操作会失败。建议先进行数据清理和验证。
4. 删除约束 (DROP CONSTRAINT)
查找约束名称
sql
-- 查看表的所有约束
SELECT
tc.constraint_name,
tc.constraint_type,
kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
WHERE tc.table_name = 'products';
-- 使用 psql 命令查看表详情(更直观)
\d products
-- 查看约束的详细定义
SELECT
conname as constraint_name,
pg_get_constraintdef(oid) as definition
FROM pg_constraint
WHERE conrelid = 'products'::regclass;
查询结果示例:
constraint_name | constraint_type | column_name
---------------------+-----------------+-------------
products_pkey | PRIMARY KEY | product_id
check_positive_price| CHECK | price
unique_product_code | UNIQUE | product_code
fk_products_category| FOREIGN KEY | category_id
删除约束示例
示例 1:删除命名约束
sql
-- 删除价格检查约束
ALTER TABLE products DROP CONSTRAINT check_positive_price;
-- 删除唯一约束
ALTER TABLE products DROP CONSTRAINT unique_product_code;
-- 删除外键约束
ALTER TABLE products DROP CONSTRAINT fk_products_category CASCADE;
示例 2:删除 NOT NULL 约束
sql
-- NOT NULL约束没有名称,使用特殊语法
ALTER TABLE products ALTER COLUMN category_id DROP NOT NULL;
示例 3:处理系统生成的约束名
sql
-- 对于系统生成的约束名(如 products_price_check),需要用双引号
ALTER TABLE products DROP CONSTRAINT "products_price_check";
-- 或者先查询确切的约束名
SELECT conname FROM pg_constraint
WHERE conrelid = 'products'::regclass AND contype = 'c';
-- 然后删除
ALTER TABLE products DROP CONSTRAINT "products_price_check_1";
💭 约束命名最佳实践
建议在创建约束时就给出有意义的名称:
sql
-- 好的命名方式
ALTER TABLE products ADD CONSTRAINT chk_positive_price CHECK (price > 0);
ALTER TABLE products ADD CONSTRAINT uk_product_code UNIQUE (product_code);
ALTER TABLE products ADD CONSTRAINT fk_product_category
FOREIGN KEY (category_id) REFERENCES categories(category_id);
-- 命名规范:
-- chk_ : CHECK约束
-- uk_ : UNIQUE约束
-- fk_ : FOREIGN KEY约束
-- pk_ : PRIMARY KEY约束
5. 更改列的默认值
设置默认值
示例 1:设置静态默认值
sql
-- 为价格设置默认值
ALTER TABLE products ALTER COLUMN price SET DEFAULT 0.00;
-- 为状态设置默认值
ALTER TABLE products ALTER COLUMN status SET DEFAULT 'pending';
-- 测试默认值效果
INSERT INTO products (product_name) VALUES ('测试商品');
-- 查看插入结果
SELECT product_name, price, status FROM products WHERE product_name = '测试商品';
输出结果:
product_name | price | status
--------------+-------+---------
测试商品 | 0.00 | pending
示例 2:设置动态默认值
sql
-- 设置创建时间默认值
ALTER TABLE products ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP;
-- 设置更新时间默认值
ALTER TABLE products ALTER COLUMN updated_at SET DEFAULT NOW();
-- 插入新记录验证
INSERT INTO products (product_name, price) VALUES ('动态时间测试', 99.99);
删除默认值
sql
-- 删除价格默认值
ALTER TABLE products ALTER COLUMN price DROP DEFAULT;
-- 删除状态默认值
ALTER TABLE products ALTER COLUMN status DROP DEFAULT;
-- 验证默认值已删除
\d products
📝 默认值说明
- 删除默认值等同于将默认值设置为 NULL
- 对于没有定义默认值的列,隐式默认值就是 NULL
- 修改默认值不影响表中已存在的数据,只影响未来的 INSERT 操作
默认值类型对比
默认值类型 | 示例 | 特点 | 适用场景 |
---|---|---|---|
静态值 | DEFAULT 0 | 固定不变 | 状态标志、默认数量 |
函数调用 | DEFAULT NOW() | 每次执行时计算 | 时间戳、序列号 |
表达式 | DEFAULT (price * 0.1) | 基于其他列计算 | 计算字段、派生值 |
6. 更改列的数据类型
基本语法
sql
ALTER TABLE 表名 ALTER COLUMN 列名 TYPE 新数据类型 [USING 转换表达式];
实战示例
示例 1:兼容类型转换
sql
-- 原始数据类型:VARCHAR(50)
CREATE TABLE test_products (
id SERIAL PRIMARY KEY,
name VARCHAR(50),
price TEXT
);
-- 扩大VARCHAR长度(兼容转换)
ALTER TABLE test_products ALTER COLUMN name TYPE VARCHAR(200);
-- 将TEXT转换为NUMERIC(自动转换)
ALTER TABLE test_products ALTER COLUMN price TYPE NUMERIC(10,2);
示例 2:需要显式转换的情况
sql
-- 创建测试数据
INSERT INTO test_products (name, price) VALUES
('iPhone 15', '999.99'),
('MacBook Pro', '1999.50'),
('iPad Air', '599.00');
-- 将价格从文本转为数值(需要显式转换)
ALTER TABLE test_products ALTER COLUMN price TYPE NUMERIC(10,2)
USING price::NUMERIC;
-- 将数值转为整数(使用函数转换)
ALTER TABLE test_products ALTER COLUMN price TYPE INTEGER
USING ROUND(price);
示例 3:复杂数据转换
sql
-- 添加测试列
ALTER TABLE products ADD COLUMN old_status INTEGER DEFAULT 1;
-- 更新一些测试数据
UPDATE products SET old_status = 1 WHERE status = 'active';
UPDATE products SET old_status = 0 WHERE status = 'inactive';
-- 将整数状态转换为文本状态
ALTER TABLE products ALTER COLUMN old_status TYPE VARCHAR(20)
USING CASE
WHEN old_status = 1 THEN 'active'
WHEN old_status = 0 THEN 'inactive'
ELSE 'unknown'
END;
-- 复杂的时间格式转换
ALTER TABLE products ALTER COLUMN created_at TYPE DATE
USING created_at::DATE;
类型转换兼容性表
源类型 | 目标类型 | 转换方式 | 风险级别 |
---|---|---|---|
VARCHAR(n) | VARCHAR(m) (m>n) | 自动 | 🟢 安全 |
VARCHAR(n) | VARCHAR(m) (m<n) | 自动,可能截断 | 🟡 注意 |
TEXT | VARCHAR(n) | 自动,可能截断 | 🟡 注意 |
VARCHAR | INTEGER | USING 表达式 | 🟠 需验证 |
TIMESTAMP | DATE | USING 表达式 | 🟡 精度丢失 |
NUMERIC | INTEGER | USING 表达式 | 🟡 精度丢失 |
⚠️ 类型转换风险
- 数据丢失:精度降低或长度截断
- 转换失败:数据格式不兼容
- 约束冲突:新类型可能违反现有约束
建议在生产环境执行前先在测试环境验证!
转换前的数据验证
sql
-- 检查文本列是否能转换为数值
SELECT
price,
CASE
WHEN price ~ '^[0-9]+\.?[0-9]*$' THEN '可转换'
ELSE '不可转换'
END as conversion_status
FROM test_products;
-- 检查数值转换的精度损失
SELECT
price,
ROUND(price) as rounded_price,
price - ROUND(price) as precision_loss
FROM test_products
WHERE price - ROUND(price) != 0;
-- 检查字符串长度是否超出新限制
SELECT
name,
LENGTH(name) as current_length
FROM test_products
WHERE LENGTH(name) > 50; -- 假设要转换为VARCHAR(50)
7. 重命名列
基本语法
sql
ALTER TABLE 表名 RENAME COLUMN 旧列名 TO 新列名;
示例:
sql
-- 原始表结构(列名不规范)
CREATE TABLE user_info (
id SERIAL PRIMARY KEY,
userName VARCHAR(50),
userEmail VARCHAR(100),
createdDate TIMESTAMP
);
-- 规范化列名(遵循 snake_case 命名规范)
ALTER TABLE user_info RENAME COLUMN userName TO user_name;
ALTER TABLE user_info RENAME COLUMN userEmail TO user_email;
ALTER TABLE user_info RENAME COLUMN createdDate TO created_date;
-- 验证重命名结果
\d user_info
💡 重命名最佳实践
命名规范建议:
- 使用
snake_case
(下划线分隔) - 避免使用 SQL 关键字
- 使用有意义的英文单词
- 保持命名一致性
示例 3:批量重命名操作
sql
-- 为多个表的时间字段统一命名
DO $$
DECLARE
table_record RECORD;
BEGIN
FOR table_record IN
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
LOOP
EXECUTE format('ALTER TABLE %I RENAME COLUMN created_at TO created_time', table_record.table_name);
END LOOP;
END $$;
8. 重命名表
基本语法
sql
ALTER TABLE 旧表名 RENAME TO 新表名;
实战示例
示例 1:表名规范化
sql
-- 创建测试表(不规范的命名)
CREATE TABLE ProductInfo (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
-- 重命名为规范的表名
ALTER TABLE ProductInfo RENAME TO product_info;
-- 验证重命名结果
SELECT tablename FROM pg_tables WHERE tablename LIKE '%product%';
示例 2:业务重构时的表重命名
sql
-- 将用户表重命名为客户表(业务重构)
ALTER TABLE users RENAME TO customers;
-- 将订单详情表重命名
ALTER TABLE order_details RENAME TO order_items;
-- 重命名临时表为正式表
ALTER TABLE products_temp RENAME TO products_new;
ALTER TABLE products RENAME TO products_old;
ALTER TABLE products_new RENAME TO products;
示例 3:版本管理场景
sql
-- 数据库迁移场景:创建新版本表
CREATE TABLE products_v2 (
id SERIAL PRIMARY KEY,
product_code VARCHAR(50) UNIQUE NOT NULL,
product_name VARCHAR(200) NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 迁移完成后重命名
ALTER TABLE products RENAME TO products_v1_backup;
ALTER TABLE products_v2 RENAME TO products;
-- 清理备份(确认无误后)
-- DROP TABLE products_v1_backup;
重命名表的影响
⚠️ 重命名表的影响范围
重命名表会影响以下对象,需要手动更新:
- 应用程序代码 - 所有 SQL 查询
- 视图定义 - 依赖该表的视图
- 存储过程/函数 - 引用该表的程序
- 外键约束 - 其他表对该表的引用
- 权限设置 - 表级别的权限配置
系统会自动更新:
- 索引名称
- 约束名称
- 序列关联
重命名检查脚本
sql
-- 检查表的依赖关系
SELECT
tc.table_name,
tc.constraint_name,
tc.constraint_type,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints tc
LEFT JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
LEFT JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.table_name = 'products';
-- 检查视图依赖
SELECT
schemaname,
viewname,
definition
FROM pg_views
WHERE definition LIKE '%products%';
-- 检查存储过程依赖
SELECT
n.nspname as schema_name,
p.proname as function_name,
pg_get_functiondef(p.oid) as definition
FROM pg_proc p
LEFT JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE pg_get_functiondef(p.oid) LIKE '%products%';
综合实战案例
案例:电商系统商品表优化
让我们通过一个完整的案例来演示所有 ALTER TABLE 操作:
初始表结构
sql
-- 创建初始商品表(设计不完善)
CREATE TABLE items (
id SERIAL PRIMARY KEY,
name TEXT,
cost DECIMAL(8,2),
desc TEXT
);
第一轮优化:结构调整
sql
-- 1. 重命名表名更规范
ALTER TABLE items RENAME TO products;
-- 2. 重命名列名更具语义
ALTER TABLE products RENAME COLUMN id TO product_id;
ALTER TABLE products RENAME COLUMN name TO product_name;
ALTER TABLE products RENAME COLUMN cost TO unit_price;
ALTER TABLE products RENAME COLUMN desc TO description;
-- 3. 修改数据类型更合适
ALTER TABLE products ALTER COLUMN product_name TYPE VARCHAR(200);
ALTER TABLE products ALTER COLUMN unit_price TYPE DECIMAL(10,2);
ALTER TABLE products ALTER COLUMN description TYPE TEXT;
-- 4. 添加必要约束
ALTER TABLE products ALTER COLUMN product_name SET NOT NULL;
ALTER TABLE products ADD CONSTRAINT chk_positive_price
CHECK (unit_price > 0);
-- 5. 添加新的业务字段
ALTER TABLE products ADD COLUMN product_code VARCHAR(50) UNIQUE;
ALTER TABLE products ADD COLUMN category_id INTEGER;
ALTER TABLE products ADD COLUMN stock_quantity INTEGER DEFAULT 0;
ALTER TABLE products ADD COLUMN status VARCHAR(20) DEFAULT 'active';
ALTER TABLE products ADD COLUMN created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE products ADD COLUMN updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
第二轮优化:业务规则完善
sql
-- 1. 创建分类表并建立外键关系
CREATE TABLE categories (
category_id SERIAL PRIMARY KEY,
category_name VARCHAR(100) NOT NULL UNIQUE,
parent_id INTEGER REFERENCES categories(category_id)
);
-- 2. 添加外键约束
ALTER TABLE products ADD CONSTRAINT fk_product_category
FOREIGN KEY (category_id) REFERENCES categories(category_id);
-- 3. 完善业务约束
ALTER TABLE products ADD CONSTRAINT chk_valid_status
CHECK (status IN ('active', 'inactive', 'discontinued'));
ALTER TABLE products ADD CONSTRAINT chk_non_negative_stock
CHECK (stock_quantity >= 0);
-- 4. 添加更多业务字段
ALTER TABLE products ADD COLUMN weight DECIMAL(8,3);
ALTER TABLE products ADD CONSTRAINT chk_positive_weight
CHECK (weight > 0);
最终表结构验证
sql
-- 查看最终表结构
\d+ products
最终表结构:
Table "public.products"
Column | Type | Nullable | Default | Description
-----------------+------------------------+----------+-----------------------------------+-------------
product_id | integer | not null | nextval('products_product_id_seq')
product_name | character varying(200) | not null |
unit_price | numeric(10,2) | |
description | text | |
product_code | character varying(50) | |
category_id | integer | |
stock_quantity | integer | | 0
status | character varying(20) | | 'active'::character varying
created_time | timestamp | | CURRENT_TIMESTAMP
updated_time | timestamp | | CURRENT_TIMESTAMP
weight | numeric(8,3) | |
Indexes:
"products_pkey" PRIMARY KEY, btree (product_id)
"products_product_code_key" UNIQUE CONSTRAINT, btree (product_code)
Check constraints:
"chk_non_negative_stock" CHECK (stock_quantity >= 0)
"chk_positive_price" CHECK (unit_price > 0::numeric)
"chk_positive_weight" CHECK (weight > 0::numeric)
"chk_valid_status" CHECK (status::text = ANY (ARRAY['active'::character varying, 'inactive'::character varying, 'discontinued'::character varying]::text[]))
Foreign-key constraints:
"fk_product_category" FOREIGN KEY (category_id) REFERENCES categories(category_id)
🎯 核心技能清单
操作类型 | 核心命令 | 应用场景 | 风险等级 |
---|---|---|---|
添加列 | ADD COLUMN | 新功能开发 | 🟢 低风险 |
删除列 | DROP COLUMN | 清理冗余字段 | 🟠 中风险 |
添加约束 | ADD CONSTRAINT | 数据质量保证 | 🟡 需验证 |
删除约束 | DROP CONSTRAINT | 业务规则调整 | 🟡 需验证 |
修改默认值 | SET/DROP DEFAULT | 业务逻辑优化 | 🟢 低风险 |
修改类型 | ALTER TYPE USING | 数据结构优化 | 🔴 高风险 |
重命名列 | RENAME COLUMN | 规范化命名 | 🟡 需更新代码 |
重命名表 | RENAME TO | 架构重构 | 🟠 影响范围大 |