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