目录

pgsql-04 表的创建与管理

04 - 表的创建与管理

1. 📖 概述

本章介绍PostgreSQL中表的创建、修改和管理,并与MySQL进行详细对比。

2. 🏗️ 创建表

2.1 基本语法

-- PostgreSQL
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- MySQL对比
-- CREATE TABLE users (
--     id INT AUTO_INCREMENT PRIMARY KEY,
--     username VARCHAR(50) NOT NULL UNIQUE,
--     email VARCHAR(100) NOT NULL UNIQUE,
--     password_hash VARCHAR(255) NOT NULL,
--     is_active TINYINT(1) DEFAULT 1,
--     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
--     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2 IF NOT EXISTS

-- PostgreSQL & MySQL
CREATE TABLE IF NOT EXISTS products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    price NUMERIC(10, 2)
);

2.3 从查询结果创建表

-- PostgreSQL
CREATE TABLE users_backup AS
SELECT * FROM users;

-- 仅复制结构,不复制数据
CREATE TABLE users_template AS
SELECT * FROM users WHERE 1=0;

-- MySQL类似
-- CREATE TABLE users_backup AS SELECT * FROM users;

2.4 创建临时表

-- PostgreSQL
CREATE TEMP TABLE temp_calculations (
    id SERIAL,
    result NUMERIC
);
-- 临时表在会话结束时自动删除

-- MySQL
-- CREATE TEMPORARY TABLE temp_calculations (
--     id INT AUTO_INCREMENT,
--     result DECIMAL(10,2)
-- );

2.5 指定表空间

-- PostgreSQL
CREATE TABLESPACE fast_space LOCATION '/ssd/postgresql/data';

CREATE TABLE important_data (
    id SERIAL PRIMARY KEY,
    data TEXT
) TABLESPACE fast_space;

-- MySQL使用不同的存储引擎和表空间概念

3. 🔧 修改表结构

3.1 添加列

-- PostgreSQL
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- 添加带默认值的列
ALTER TABLE users ADD COLUMN age INTEGER DEFAULT 0;

-- 添加非空列(必须有默认值或先添加为可空)
ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active' NOT NULL;

-- MySQL相同
-- ALTER TABLE users ADD COLUMN phone VARCHAR(20);

3.2 删除列

-- PostgreSQL
ALTER TABLE users DROP COLUMN phone;

-- 如果不存在不报错
ALTER TABLE users DROP COLUMN IF EXISTS phone;

-- 级联删除(删除依赖该列的对象)
ALTER TABLE users DROP COLUMN email CASCADE;

-- MySQL
-- ALTER TABLE users DROP COLUMN phone;

3.3 修改列

-- PostgreSQL: 修改列名
ALTER TABLE users RENAME COLUMN username TO user_name;

-- 修改列类型
ALTER TABLE users ALTER COLUMN age TYPE BIGINT;

-- 使用USING转换数据
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(10) USING age::VARCHAR;

-- 设置/删除默认值
ALTER TABLE users ALTER COLUMN is_active SET DEFAULT FALSE;
ALTER TABLE users ALTER COLUMN is_active DROP DEFAULT;

-- 设置/删除NOT NULL
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
ALTER TABLE users ALTER COLUMN email DROP NOT NULL;
-- MySQL: 修改列(需要完整定义)
-- ALTER TABLE users CHANGE username user_name VARCHAR(50) NOT NULL;
-- ALTER TABLE users MODIFY COLUMN age BIGINT;

3.4 重命名表

-- PostgreSQL
ALTER TABLE users RENAME TO app_users;

-- MySQL
-- RENAME TABLE users TO app_users;
-- 或
-- ALTER TABLE users RENAME TO app_users;

3.5 添加/删除约束

-- PostgreSQL
-- 添加主键
ALTER TABLE users ADD PRIMARY KEY (id);

-- 添加外键
ALTER TABLE orders ADD CONSTRAINT fk_user
    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE;

-- 添加唯一约束
ALTER TABLE users ADD CONSTRAINT uk_email UNIQUE (email);

-- 添加检查约束
ALTER TABLE products ADD CONSTRAINT chk_price
    CHECK (price >= 0);

-- 删除约束
ALTER TABLE users DROP CONSTRAINT uk_email;

4. 📋 查看表信息

4.1 列出所有表

-- PostgreSQL
\dt                          -- psql命令
\dt+                         -- 详细信息

-- SQL查询
SELECT tablename FROM pg_tables WHERE schemaname = 'public';

-- MySQL
-- SHOW TABLES;

4.2 查看表结构

-- PostgreSQL
\d users                     -- psql命令
\d+ users                    -- 详细信息

-- SQL查询
SELECT
    column_name,
    data_type,
    character_maximum_length,
    is_nullable,
    column_default
FROM information_schema.columns
WHERE table_name = 'users';
-- MySQL
-- DESC users;
-- 或
-- DESCRIBE users;
-- 或
-- SHOW COLUMNS FROM users;

4.3 查看建表语句

-- PostgreSQL: 没有直接的SHOW CREATE TABLE
-- 需要使用pg_dump
-- pg_dump -t users --schema-only dbname

-- 或查询系统表
SELECT pg_get_tabledef('users');  -- 需要自定义函数

-- MySQL
-- SHOW CREATE TABLE users;

4.4 查看表大小

-- PostgreSQL
SELECT
    pg_size_pretty(pg_total_relation_size('users')) AS total_size,
    pg_size_pretty(pg_relation_size('users')) AS table_size,
    pg_size_pretty(pg_indexes_size('users')) AS indexes_size;

-- 查看所有表大小
SELECT
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
-- MySQL
-- SELECT
--     table_name,
--     ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb
-- FROM information_schema.tables
-- WHERE table_schema = 'mydb';

5. 🗑️ 删除表

-- PostgreSQL
DROP TABLE users;

-- 如果不存在不报错
DROP TABLE IF EXISTS users;

-- 级联删除(删除依赖对象)
DROP TABLE users CASCADE;

-- 一次删除多个表
DROP TABLE users, products, orders;

-- MySQL相同
-- DROP TABLE IF EXISTS users;

5.1 TRUNCATE vs DELETE

-- TRUNCATE: 快速清空表
TRUNCATE TABLE users;
TRUNCATE TABLE users RESTART IDENTITY;  -- 重置序列
TRUNCATE TABLE users CASCADE;            -- 级联清空

-- DELETE: 逐行删除
DELETE FROM users;

-- 对比
-- TRUNCATE: 快,不记录单行日志,重置自增,不触发触发器
-- DELETE: 慢,记录日志,可回滚,触发触发器

6. 🔄 表继承(PostgreSQL独有)

-- PostgreSQL: 表继承
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    salary NUMERIC(10, 2)
);

CREATE TABLE managers (
    department VARCHAR(100)
) INHERITS (employees);

CREATE TABLE developers (
    programming_language VARCHAR(50)
) INHERITS (employees);

-- 插入数据
INSERT INTO employees (name, salary) VALUES ('Alice', 50000);
INSERT INTO managers (name, salary, department) VALUES ('Bob', 80000, 'Sales');
INSERT INTO developers (name, salary, programming_language) VALUES ('Charlie', 70000, 'Python');

-- 查询所有员工(包括子表)
SELECT * FROM employees;

-- 仅查询employees表
SELECT * FROM ONLY employees;

-- MySQL没有表继承功能

7. 📊 分区表

7.1 PostgreSQL分区表(10+版本)

-- 范围分区
CREATE TABLE orders (
    id SERIAL,
    order_date DATE NOT NULL,
    amount NUMERIC(10, 2),
    customer_id INTEGER
) PARTITION BY RANGE (order_date);

-- 创建分区
CREATE TABLE orders_2023 PARTITION OF orders
    FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');

CREATE TABLE orders_2024 PARTITION OF orders
    FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');

CREATE TABLE orders_2025 PARTITION OF orders
    FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');

-- 默认分区
CREATE TABLE orders_default PARTITION OF orders DEFAULT;

-- 插入数据自动路由到对应分区
INSERT INTO orders (order_date, amount, customer_id)
VALUES ('2024-06-15', 99.99, 1);

7.2 列表分区

-- PostgreSQL
CREATE TABLE sales (
    id SERIAL,
    region VARCHAR(50),
    amount NUMERIC(10, 2)
) PARTITION BY LIST (region);

CREATE TABLE sales_north PARTITION OF sales
    FOR VALUES IN ('US', 'Canada');

CREATE TABLE sales_europe PARTITION OF sales
    FOR VALUES IN ('UK', 'Germany', 'France');

CREATE TABLE sales_asia PARTITION OF sales
    FOR VALUES IN ('China', 'Japan', 'India');

7.3 哈希分区

-- PostgreSQL 11+
CREATE TABLE users_partitioned (
    id SERIAL,
    username VARCHAR(50),
    email VARCHAR(100)
) PARTITION BY HASH (id);

CREATE TABLE users_p0 PARTITION OF users_partitioned
    FOR VALUES WITH (MODULUS 4, REMAINDER 0);

CREATE TABLE users_p1 PARTITION OF users_partitioned
    FOR VALUES WITH (MODULUS 4, REMAINDER 1);

CREATE TABLE users_p2 PARTITION OF users_partitioned
    FOR VALUES WITH (MODULUS 4, REMAINDER 2);

CREATE TABLE users_p3 PARTITION OF users_partitioned
    FOR VALUES WITH (MODULUS 4, REMAINDER 3);

7.4 MySQL分区表(8.0+)

-- MySQL 8.0+ 范围分区
-- CREATE TABLE orders (
--     id INT AUTO_INCREMENT,
--     order_date DATE NOT NULL,
--     amount DECIMAL(10, 2),
--     PRIMARY KEY (id, order_date)
-- )
-- PARTITION BY RANGE (YEAR(order_date)) (
--     PARTITION p2023 VALUES LESS THAN (2024),
--     PARTITION p2024 VALUES LESS THAN (2025),
--     PARTITION p2025 VALUES LESS THAN (2026)
-- );

8. 🔐 表级权限

-- PostgreSQL
-- 授予所有权限
GRANT ALL PRIVILEGES ON TABLE users TO alice;

-- 授予特定权限
GRANT SELECT, INSERT, UPDATE ON TABLE users TO developer;

-- 授予所有表权限
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO admin;

-- 撤销权限
REVOKE INSERT, UPDATE ON TABLE users FROM developer;

-- 查看权限
\dp users

-- MySQL
-- GRANT SELECT, INSERT, UPDATE ON mydb.users TO 'alice'@'localhost';
-- REVOKE INSERT, UPDATE ON mydb.users FROM 'alice'@'localhost';

9. 📝 注释

-- PostgreSQL
COMMENT ON TABLE users IS '用户表';
COMMENT ON COLUMN users.email IS '用户邮箱地址';

-- 查看注释
\d+ users

-- MySQL
-- ALTER TABLE users COMMENT = '用户表';
-- ALTER TABLE users MODIFY COLUMN email VARCHAR(100) COMMENT '用户邮箱地址';

10. 🎯 最佳实践

10.1 1. 命名规范

-- 好的命名
CREATE TABLE user_profiles (        -- 表名:复数,小写,下划线
    id SERIAL PRIMARY KEY,           -- 主键:id
    user_id INTEGER,                 -- 外键:表名_id
    created_at TIMESTAMPTZ,          -- 时间戳:_at结尾
    is_active BOOLEAN                -- 布尔:is_/has_前缀
);

-- 避免
CREATE TABLE UserProfile (           -- 避免驼峰命名
    ID SERIAL PRIMARY KEY,           -- 避免大写
    userId INTEGER,                  -- 避免驼峰
    createdOn TIMESTAMPTZ            -- 不一致的命名
);

10.2 2. 主键策略

-- 推荐:自增整数主键
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    sku VARCHAR(50) UNIQUE NOT NULL
);

-- 或UUID主键(分布式系统)
CREATE TABLE sessions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id INTEGER
);

-- 复合主键
CREATE TABLE order_items (
    order_id INTEGER,
    product_id INTEGER,
    quantity INTEGER,
    PRIMARY KEY (order_id, product_id)
);

10.3 3. 默认值和时间戳

CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    status VARCHAR(20) DEFAULT 'draft',
    view_count INTEGER DEFAULT 0,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 自动更新updated_at(需要触发器)
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_posts_updated_at
BEFORE UPDATE ON posts
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();

10.4 4. 软删除

-- 使用deleted_at实现软删除
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(300),
    content TEXT,
    deleted_at TIMESTAMPTZ DEFAULT NULL
);

-- 查询未删除的记录
SELECT * FROM articles WHERE deleted_at IS NULL;

-- 软删除
UPDATE articles SET deleted_at = NOW() WHERE id = 1;

11. 📋 实战示例

11.1 电商系统表设计

-- 用户表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    phone VARCHAR(20),
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 商品表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    sku VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(200) NOT NULL,
    description TEXT,
    price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
    stock INTEGER DEFAULT 0 CHECK (stock >= 0),
    category_id INTEGER REFERENCES categories(id),
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 分类表
CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    parent_id INTEGER REFERENCES categories(id),
    level INTEGER DEFAULT 1
);

-- 订单表
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    order_number VARCHAR(50) UNIQUE NOT NULL,
    user_id INTEGER REFERENCES users(id),
    total_amount NUMERIC(12, 2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 订单明细表
CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id) ON DELETE CASCADE,
    product_id INTEGER REFERENCES products(id),
    quantity INTEGER NOT NULL CHECK (quantity > 0),
    price NUMERIC(10, 2) NOT NULL,
    subtotal NUMERIC(12, 2) NOT NULL
);

-- 创建索引
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_products_category_id ON products(category_id);

12. 📚 下一步


关键要点:

  • 使用SERIAL作为自增主键
  • 为时间戳字段使用TIMESTAMPTZ
  • 合理使用外键约束
  • PostgreSQL的表继承和分区是强大特性
  • 始终为表和列添加注释