Skip to content

修改表结构 (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)

约束类型总览

约束类型语法格式用途说明
CHECKADD CHECK (condition)数据值验证
UNIQUEADD UNIQUE (column)唯一性约束
FOREIGN KEYADD FOREIGN KEY (col) REFERENCES table(col)外键关联
NOT NULLALTER COLUMN col SET NOT NULL非空约束
PRIMARY KEYADD 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)自动,可能截断🟡 注意
TEXTVARCHAR(n)自动,可能截断🟡 注意
VARCHARINTEGERUSING 表达式🟠 需验证
TIMESTAMPDATEUSING 表达式🟡 精度丢失
NUMERICINTEGERUSING 表达式🟡 精度丢失

⚠️ 类型转换风险

  1. 数据丢失:精度降低或长度截断
  2. 转换失败:数据格式不兼容
  3. 约束冲突:新类型可能违反现有约束

建议在生产环境执行前先在测试环境验证!

转换前的数据验证

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;

重命名表的影响

⚠️ 重命名表的影响范围

重命名表会影响以下对象,需要手动更新:

  1. 应用程序代码 - 所有 SQL 查询
  2. 视图定义 - 依赖该表的视图
  3. 存储过程/函数 - 引用该表的程序
  4. 外键约束 - 其他表对该表的引用
  5. 权限设置 - 表级别的权限配置

系统会自动更新:

  • 索引名称
  • 约束名称
  • 序列关联

重命名检查脚本

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架构重构🟠 影响范围大