PostgreSQL 完全实战指南:从小白到高手 DDL篇

数据类型 - PostgreSQL的"百宝箱"

1.1 基础类型

SQL 复制代码
-- 创建测试表
CREATE TABLE user_basic (
    id SERIAL PRIMARY KEY,                    -- 自增整数
    name VARCHAR(50) NOT NULL,                -- 变长字符串
    email VARCHAR(100) UNIQUE,               -- 唯一约束
    age SMALLINT CHECK (age >= 0 AND age <= 150),  -- 小整数 + 检查
    height DECIMAL(5,2),                      -- 精确小数,总5位小数2位
    salary NUMERIC(10,2),                     -- 同DECIMAL
    is_vip BOOLEAN DEFAULT false,             -- 布尔值
    register_date DATE DEFAULT CURRENT_DATE,  -- 日期
    last_login TIMESTAMP,                     -- 时间戳
    login_time TIME,                          -- 时间
    active_range TSRANGE,                     -- 时间范围
    ip_address INET,                          -- IP地址
    mac_address MACADDR,                      -- MAC地址
    bit_data BIT(8),                          -- 位数据
    uuid_data UUID DEFAULT gen_random_uuid(), -- UUID
    xml_data XML,                             -- XML数据
    binary_data BYTEA                         -- 二进制
);

1.2 进阶类型(PostgreSQL特色)

SQL 复制代码
-- 真实案例:电商商品表
CREATE TABLE product_advanced (
    id SERIAL PRIMARY KEY,
    
    -- ARRAY 数组:商品标签
    tags TEXT[] DEFAULT '{}',  -- 空数组
    
    -- JSONB:商品属性(动态结构)
    attributes JSONB,  -- 二进制JSON,可索引
    
    -- HSTORE:键值对(已废弃,JSONB更好)
    -- extra HSTORE,
    
    -- 几何类型:配送范围
    delivery_zone POLYGON,
    
    -- 枚举:商品状态
    status product_status_enum,  -- 需要先创建枚举
    
    -- 复合类型:尺寸
    dimensions dimension_type  -- 需要先创建复合类型
);
-- 创建枚举类型
CREATE TYPE product_status_enum AS ENUM (
    'draft',      -- 草稿
    'published',  -- 已发布
    'out_of_stock', -- 缺货
    'discontinued'   -- 已下架
);
-- 创建复合类型
CREATE TYPE dimension_type AS (
    length DECIMAL(8,2),
    width DECIMAL(8,2),
    height DECIMAL(8,2),
    weight DECIMAL(8,2)
);
-- 插入数据
INSERT INTO product_advanced 
VALUES (
    1,
    '{"电子产品", "电脑", "游戏"}',
    '{"brand": "Apple", "model": "MacBook Pro", "specs": {"cpu": "M3", "ram": "16GB"}}',
    '((0,0),(10,0),(10,10),(0,10),(0,0))',
    'published',
    ROW(30.5, 21.2, 1.55, 1.4)
);

2.3 查询演示

SQL 复制代码
-- 查询包含"电脑"标签的商品
SELECT * FROM product_advanced 
WHERE '电脑' = ANY(tags);
-- 查询品牌是Apple的商品(JSONB查询)
SELECT * FROM product_advanced 
WHERE attributes @> '{"brand": "Apple"}';
-- 或者更精确
SELECT 
    id,
    attributes->>'brand' as 品牌,
    attributes->'specs'->>'cpu' as 处理器
FROM product_advanced 
WHERE attributes->>'brand' = 'Apple';
-- 查询特定范围的商品
SELECT * FROM product_advanced 
WHERE delivery_zone @> point(5,5);

SCHEMA - 数据库的"楼层规划"

2.1 什么是SCHEMA?

想象一栋大楼:

  • Database(数据库) = 整栋大楼
  • Schema(模式) = 每个楼层(部门)
  • Table(表) = 每个房间
SQL 复制代码
-- 创建公司数据库的不同部门
CREATE SCHEMA hr;         -- 人力资源部楼层
CREATE SCHEMA finance;    -- 财务部楼层
CREATE SCHEMA sales;      -- 销售部楼层
CREATE SCHEMA inventory; -- 仓库部楼层
-- 设置默认SCHEMA
SET search_path TO sales, public;
-- 查看所有SCHEMA
SELECT schema_name FROM information_schema.schemata;

2.2 实战:电商多租户架构

SQL 复制代码
-- 场景:SaaS电商平台,每个客户独立schema
CREATE SCHEMA client_taobao;   -- 淘宝
CREATE SCHEMA client_jd;       -- 京东
CREATE SCHEMA client_pdd;      -- 拼多多
-- 在每个schema创建相同的表结构
CREATE TABLE client_taobao.products (
    product_id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    price DECIMAL(10,2),
    tenant_id VARCHAR(50) DEFAULT 'taobao'
);
CREATE TABLE client_jd.products (
    product_id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    price DECIMAL(10,2),
    tenant_id VARCHAR(50) DEFAULT 'jd'
);
-- 使用函数动态查询
CREATE OR REPLACE FUNCTION get_tenant_products(tenant_name VARCHAR)
RETURNS TABLE (
    product_id INT,
    product_name VARCHAR(100),
    product_price DECIMAL(10,2)
) AS $$
BEGIN
    RETURN QUERY EXECUTE format(
        'SELECT product_id, name, price FROM %I.products',
        'client_' || tenant_name
    );
END;
$$ LANGUAGE plpgsql;
-- 使用
SELECT * FROM get_tenant_products('taobao');
SELECT * FROM get_tenant_products('jd');

2.3 SCHEMA权限管理

SQL 复制代码
-- 创建角色
CREATE ROLE sales_team;
CREATE ROLE hr_team;
CREATE ROLE admin_role;
-- 给权限
GRANT USAGE ON SCHEMA sales TO sales_team;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA sales TO sales_team;
GRANT USAGE ON SCHEMA hr TO hr_team;
GRANT SELECT ON ALL TABLES IN SCHEMA hr TO hr_team;
-- 不给财务部门访问销售数据
REVOKE ALL ON SCHEMA sales FROM hr_team;
-- 创建用户并分配角色
CREATE USER zhangsan WITH PASSWORD 'password123';
GRANT sales_team TO zhangsan;

DDL - 创建和修改数据库结构

3.1 完整DDL操作

SQL 复制代码
-- 1. 创建表(完整语法)
CREATE TABLE ecommerce.products (
    product_id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    product_name VARCHAR(255) NOT NULL,
    category_id INT NOT NULL,
    price DECIMAL(10,2) NOT NULL CHECK (price > 0),
    stock_quantity INT DEFAULT 0 CHECK (stock_quantity >= 0),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status VARCHAR(20) DEFAULT 'active',
    
    -- 外键约束
    CONSTRAINT fk_category 
        FOREIGN KEY (category_id) 
        REFERENCES categories(category_id)
        ON DELETE CASCADE
        ON UPDATE CASCADE,
    
    -- 唯一约束
    CONSTRAINT unique_product_name 
        UNIQUE (product_name, category_id)
    
    -- 部分索引(只对active产品)
) WITH (
    OIDS = FALSE,
    autovacuum_enabled = true
) TABLESPACE pg_default;
-- 2. 修改表结构(真实场景)
-- 添加新列(商品促销)
ALTER TABLE products 
ADD COLUMN discount_price DECIMAL(10,2) 
CHECK (discount_price <= price);
-- 添加促销时间范围
ALTER TABLE products 
ADD COLUMN promotion_period TSRANGE;
-- 修改列类型(发现价格需要更高精度)
ALTER TABLE products 
ALTER COLUMN price TYPE DECIMAL(15,2);
-- 重命名列
ALTER TABLE products 
RENAME COLUMN stock_quantity TO inventory_count;
-- 添加检查约束(折扣不能超过原价90%)
ALTER TABLE products 
ADD CONSTRAINT check_discount 
CHECK (discount_price >= price * 0.1);
-- 3. 索引管理
-- 创建唯一索引
CREATE UNIQUE INDEX idx_product_sku 
ON products(product_name, category_id);
-- 创建部分索引(只索引有效商品)
CREATE INDEX idx_active_products 
ON products(product_name) 
WHERE status = 'active';
-- 创建表达式索引(搜索不区分大小写)
CREATE INDEX idx_product_name_lower 
ON products(LOWER(product_name));
-- 4. 删除表
-- 先检查依赖
SELECT * FROM pg_depend 
WHERE objid = 'products'::regclass;
-- 删除表
DROP TABLE IF EXISTS products CASCADE;
  1. OIDS = FALSE
  • OIDS 是对象标识符(Object IDentifiers)
  • FALSE 表示不为此表自动创建系统 OID 列
  • 在 PostgreSQL 8.1+ 版本中,默认就是 FALSE,显式声明是为了明确关闭
  • OID 曾用于系统内部标识记录,但现代版本已弃用
  1. autovacuum_enabled = true
  • 开启自动清理(AutoVACUUM)

  • 自动清理会:

    • 回收被删除或更新记录占用的空间
    • 更新表的统计信息(用于查询优化)
    • 防止事务ID回绕
  • 对于频繁更新的表,建议保持开启(默认就是 true

  1. TABLESPACE pg_default
  • 指定表存储在 pg_default 表空间
  • pg_default 是 PostgreSQL 的默认表空间
  • 对应基础目录(通常是 $PGDATA/base/
  • 如果没有指定表空间,就会使用这个默认值

3.2 实战:电商订单表演进

SQL 复制代码
-- 第一版:简单订单表
CREATE TABLE orders_v1 (
    order_id SERIAL PRIMARY KEY,
    user_id INT NOT NULL,
    amount DECIMAL(10,2),
    order_date TIMESTAMP DEFAULT now()
);
-- 第二版:增加更多字段
ALTER TABLE orders_v1 RENAME TO orders_v2;
CREATE TABLE orders_v2 (
    order_id BIGSERIAL PRIMARY KEY,  -- 改为BIGINT
    order_no VARCHAR(50) UNIQUE NOT NULL,  -- 添加订单号
    user_id INT NOT NULL,
    amount DECIMAL(15,2) NOT NULL,
    tax_amount DECIMAL(15,2),
    shipping_fee DECIMAL(8,2),
    total_amount DECIMAL(15,2) GENERATED ALWAYS AS (amount + COALESCE(tax_amount,0) + COALESCE(shipping_fee,0)) STORED,
    
    -- 订单状态
    status order_status DEFAULT 'pending',  -- 需要定义枚举
    payment_status VARCHAR(20),
    
    -- 时间戳
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_at TIMESTAMPTZ DEFAULT now(),
    paid_at TIMESTAMPTZ,
    shipped_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    
    -- 用户信息(反范式设计)
    username VARCHAR(100),
    email VARCHAR(255),
    shipping_address JSONB,
    
    -- 检查约束
    CONSTRAINT check_total_amount CHECK (total_amount >= amount),
    CONSTRAINT check_dates CHECK (created_at <= COALESCE(updated_at, created_at)),
    
    -- 外键
    CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- 第三版:分表分区(大表优化)
CREATE TABLE orders_v3 (
    LIKE orders_v2 INCLUDING ALL
) PARTITION BY RANGE (created_at);
-- 创建月份分区
CREATE TABLE orders_2024_01 PARTITION OF orders_v3
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE orders_2024_02 PARTITION OF orders_v3
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
  • COALESCE(tax_amount,0)

    • 如果 tax_amountNULL,则当作 0
    • 否则使用 tax_amount 的值
  1. CREATE TABLE orders_v3 (
  • 创建新表 orders_v3
  1. LIKE orders_v2 INCLUDING ALL
  • 继承结构 :复制 orders_v2 表的所有结构

  • 包括内容

    • 所有列定义
    • 索引(Indexes)
    • 约束(Constraints)
    • 默认值(Defaults)
    • 等等...
  • 不包括内容

    • 数据(空的)
    • 触发器(Triggers)除非指定 INCLUDING ALL
  1. PARTITION BY RANGE (created_at)
  • 分区策略:按范围分区(Range Partitioning)
  • 分区键 :使用 created_at 列作为分区依据
  • 分区方式 :根据 created_at 的值创建不同时间段的分区

约束 - 数据质量的"守护神"

4.1 基本约束类型

SQL 复制代码
-- 创建带有各种约束的用户表
CREATE TABLE users_with_constraints (
    -- PRIMARY KEY:主键约束
    user_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    
    -- NOT NULL:非空约束
    username VARCHAR(50) NOT NULL,
    email VARCHAR(255) NOT NULL,
    
    -- UNIQUE:唯一约束
    CONSTRAINT unique_username UNIQUE (username),
    CONSTRAINT unique_email UNIQUE (email),
    
    -- FOREIGN KEY:外键约束
    role_id INT NOT NULL REFERENCES user_roles(role_id),
    department_id INT REFERENCES departments(dept_id) ON DELETE SET NULL,
    
    -- AGE约束:自定义检查
    age INT NOT NULL CHECK (
        age >= 18 AND age <= 100
    ),
    
    -- PASSWORD约束:必须包含字母和数字
    password_hash VARCHAR(255) NOT NULL CHECK (
        LENGTH(password_hash) >= 8
        AND password_hash ~ '[A-Za-z]'  -- 至少一个字母
        AND password_hash ~ '\d'        -- 至少一个数字
    ),
    
    -- 注册约束:电子邮件格式
    CONSTRAINT valid_email CHECK (
        email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$'
    ),
    
    -- 业务约束:薪资必须为正数
    salary DECIMAL(10,2) DEFAULT 0.00 
    CHECK (salary >= 0),
    
    -- 复合约束:高级经理薪资必须>10000
    CONSTRAINT check_manager_salary 
    CHECK (
        NOT (role_id IN (1,2) AND salary < 10000)
    ),
    
    -- 时间约束:入职时间不能晚于今天
    join_date DATE DEFAULT CURRENT_DATE 
    CHECK (join_date <= CURRENT_DATE),
    
    -- 条件约束:离职时间必须晚于入职时间
    leave_date DATE CHECK (
        leave_date IS NULL OR leave_date > join_date
    )
);
-- 查看表的约束
SELECT 
    tc.constraint_name,
    tc.constraint_type,
    kcu.column_name,
    ccu.table_name AS foreign_table,
    ccu.column_name AS foreign_column
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 tc.constraint_name = ccu.constraint_name
WHERE tc.table_name = 'users_with_constraints';

5.2 CHECK约束实战案例

SQL 复制代码
-- 电商商品检查约束
CREATE TABLE products_with_checks (
    product_id SERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    
    -- 基础价格检查
    base_price DECIMAL(10,2) NOT NULL 
    CHECK (base_price > 0),
    
    discount_price DECIMAL(10,2) 
    CHECK (
        discount_price IS NULL 
        OR (
            discount_price > 0 
            AND discount_price <= base_price
        )
    ),
    
    -- 库存检查
    stock_quantity INT DEFAULT 0 
    CHECK (stock_quantity >= 0),
    reserved_stock INT DEFAULT 0 
    CHECK (reserved_stock >= 0 AND reserved_stock <= stock_quantity),
    available_stock INT GENERATED ALWAYS AS (stock_quantity - reserved_stock) STORED,
    
    -- 时间检查:促销时间有效
    promotion_start TIMESTAMP,
    promotion_end TIMESTAMP,
    CONSTRAINT valid_promotion_period CHECK (
        (promotion_start IS NULL AND promotion_end IS NULL)
        OR (promotion_start < promotion_end)
    ),
    
    -- 状态检查:状态转换有效
    current_status VARCHAR(20) DEFAULT 'draft',
    next_status VARCHAR(20),
    CONSTRAINT valid_status_transition CHECK (
        (current_status = 'draft' AND next_status IN ('published', 'archived'))
        OR (current_status = 'published' AND next_status IN ('out_of_stock', 'archived', 'suspended'))
        OR (current_status = 'out_of_stock' AND next_status IN ('published', 'archived'))
        OR (current_status = 'archived' AND next_status IS NULL)
        OR next_status IS NULL
    ),
    
    -- 商品重量和尺寸逻辑检查
    weight_kg DECIMAL(8,3) CHECK (weight_kg > 0),
    volume_m3 DECIMAL(8,4) CHECK (volume_m3 > 0),
    shipping_type VARCHAR(10) CHECK (shipping_type IN ('small', 'medium', 'large')),
    
    -- 复合检查:根据重量和体积判断运输类型
    CONSTRAINT check_shipping_type 
    CHECK (
        (weight_kg < 1 AND volume_m3 < 0.01 AND shipping_type = 'small')
        OR (weight_kg BETWEEN 1 AND 10 AND volume_m3 BETWEEN 0.01 AND 0.1 AND shipping_type = 'medium')
        OR (weight_kg > 10 OR volume_m3 > 0.1 AND shipping_type = 'large')
    )
);
-- 插入测试数据(成功的)
INSERT INTO products_with_checks 
(name, base_price, discount_price, stock_quantity, reserved_stock, weight_kg, volume_m3, shipping_type)
VALUES 
('iPhone 15', 999.99, 899.99, 100, 10, 0.174, 0.00008, 'small');
-- 这个会失败:折扣价大于原价
INSERT INTO products_with_checks 
(name, base_price, discount_price)
VALUES ('Test Product', 100, 200);  -- ERROR: check constraint violation
-- 这个会失败:运输类型不匹配
INSERT INTO products_with_checks 
(name, base_price, weight_kg, volume_m3, shipping_type)
VALUES ('TV', 2000, 15, 0.2, 'small');  -- ERROR: 应该为large

继承 - PostgreSQL的"特殊能力"

6.1 表继承基础

SQL 复制代码
-- 真实案例:电商产品继承体系
-- 父表:所有产品的通用信息
CREATE TABLE products_base (
    product_id SERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    description TEXT,
    base_price DECIMAL(10,2) NOT NULL CHECK (base_price > 0),
    brand VARCHAR(100),
    created_at TIMESTAMP DEFAULT now(),
    updated_at TIMESTAMP DEFAULT now()
);
-- 子表1:服装类产品
CREATE TABLE clothing_products (
    -- 继承所有父表字段
    size VARCHAR(10) NOT NULL,
    color VARCHAR(50),
    material VARCHAR(100),
    gender CHAR(1) CHECK (gender IN ('M', 'F', 'U')),  -- M男, F女, U中性
    season VARCHAR(20),
    washing_instruction TEXT
) INHERITS (products_base);
-- 子表2:电子产品
CREATE TABLE electronic_products (
    warranty_months INT CHECK (warranty_months > 0),
    power_consumption DECIMAL(6,2),  -- 功耗
    voltage_requirements VARCHAR(50),
    has_battery BOOLEAN DEFAULT false,
    battery_capacity VARCHAR(50),
    interfaces TEXT[]  -- 接口数组,如['USB-C', 'HDMI']
) INHERITS (products_base);
-- 子表3:食品类产品
CREATE TABLE food_products (
    expiry_date DATE NOT NULL,
    storage_temperature DECIMAL(4,1),  -- 存储温度
    nutrition_facts JSONB,  -- 营养成分表
    allergens TEXT[],  -- 过敏原数组
    net_weight DECIMAL(8,2) NOT NULL,
    serving_size VARCHAR(50)
) INHERITS (products_base);
-- 插入数据
INSERT INTO clothing_products 
(name, description, base_price, brand, size, color, material)
VALUES 
('男士T恤', '纯棉舒适T恤', 99.00, '优衣库', 'L', '白色', '棉100%');
INSERT INTO electronic_products 
(name, description, base_price, brand, warranty_months, has_battery)
VALUES 
('蓝牙耳机', '降噪蓝牙耳机', 1299.00, 'Sony', 24, true);
INSERT INTO food_products 
(name, description, base_price, brand, expiry_date, net_weight)
VALUES 
('鲜牛奶', '巴氏杀菌鲜牛奶', 12.50, '光明', '2024-01-20', 950.00);

6.2 继承查询的魔法

SQL 复制代码
-- 查询所有产品(包括父表和所有子表)
SELECT * FROM products_base;
-- 这会返回所有产品:服装 + 电子 + 食品
-- 只查询父表本身的数据(没有!因为都是通过子表插入的)
SELECT * FROM ONLY products_base;
-- 查询特定类型的产品
SELECT 
    p.name,
    p.base_price,
    p.brand,
    CASE
        WHEN p.tableoid = 'clothing_products'::regclass THEN '服装'
        WHEN p.tableoid = 'electronic_products'::regclass THEN '电子'
        WHEN p.tableoid = 'food_products'::regclass THEN '食品'
    END as category,
    
    -- 服装特有字段
    cp.size,
    cp.color,
    
    -- 电子特有字段
    ep.warranty_months,
    ep.has_battery,
    
    -- 食品特有字段
    fp.expiry_date,
    fp.net_weight
    
FROM products_base p
LEFT JOIN clothing_products cp ON p.product_id = cp.product_id
LEFT JOIN electronic_products ep ON p.product_id = ep.product_id
LEFT JOIN food_products fp ON p.product_id = fp.product_id;
-- 条件查询
-- 查询所有即将过期的食品
SELECT 
    p.name,
    p.brand,
    fp.expiry_date,
    fp.net_weight || 'g' as weight
FROM food_products fp
JOIN products_base p ON fp.product_id = p.product_id
WHERE fp.expiry_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '7 days';
-- 查询所有需要保修的产品
SELECT 
    p.name,
    p.base_price,
    ep.warranty_months,
    ep.has_battery
FROM electronic_products ep
JOIN products_base p ON ep.product_id = p.product_id
WHERE ep.warranty_months > 12 AND p.base_price > 1000;

6.3 继承的约束特点

SQL 复制代码
-- 重要:约束不会自动继承!
-- 我们需要手动添加外键约束到每个子表
-- 假设我们有一个品牌表
CREATE TABLE brands (
    brand_id SERIAL PRIMARY KEY,
    brand_name VARCHAR(100) UNIQUE NOT NULL,
    country VARCHAR(50)
);
-- 为每个子表添加外键约束
ALTER TABLE clothing_products
ADD CONSTRAINT fk_clothing_brand
FOREIGN KEY (brand) REFERENCES brands(brand_name);
ALTER TABLE electronic_products
ADD CONSTRAINT fk_electronic_brand
FOREIGN KEY (brand) REFERENCES brands(brand_name);
ALTER TABLE food_products
ADD CONSTRAINT fk_food_brand  
FOREIGN KEY (brand) REFERENCES brands(brand_name);
-- 但是索引和触发器可以继承!
-- 创建在父表上的索引,子表可以自动使用
CREATE INDEX idx_products_name ON products_base(name);
CREATE INDEX idx_products_brand ON products_base(brand);
-- 性能提示:对子表的查询通常比对所有表的查询快
EXPLAIN ANALYZE SELECT * FROM clothing_products WHERE name LIKE '%T恤%';
EXPLAIN ANALYZE SELECT * FROM products_base WHERE name LIKE '%T恤%';

第七章:自增序列和标识列 - 主键生成的艺术

7.1 传统方式:SERIAL

SQL 复制代码
-- 最简单的方式(旧方式,但常用)
CREATE TABLE orders_serial (
    order_id SERIAL PRIMARY KEY,      -- 背后创建序列 orders_serial_order_id_seq
    order_no VARCHAR(50) UNIQUE NOT NULL,
    user_id INT NOT NULL,
    amount DECIMAL(10,2) NOT NULL
);
-- 查看创建的序列
SELECT 
    sequence_schema,
    sequence_name,
    column_default
FROM information_schema.columns 
WHERE table_name = 'orders_serial' 
AND column_name = 'order_id';
-- 插入数据
INSERT INTO orders_serial (order_no, user_id, amount)
VALUES 
('ORD2024000001', 1001, 2999.00),
('ORD2024000002', 1002, 1599.00);
SELECT * FROM orders_serial;
-- 结果:
-- order_id | order_no      | user_id | amount
-- 1        | ORD2024000001 | 1001    | 2999.00
-- 2        | ORD2024000002 | 1002    | 1599.00

7.2 现代方式:IDENTITY(SQL标准)

SQL 复制代码
-- 推荐的新方式(SQL标准,功能更强)
CREATE TABLE orders_identity (
    order_id INT GENERATED ALWAYS AS IDENTITY 
    (START WITH 1000 INCREMENT BY 1 CACHE 20) PRIMARY KEY,
    -- ALWAYS:必须使用自动生成的值
    -- START WITH 1000:从1000开始
    -- INCREMENT BY 1:每次加1
    -- CACHE 20:缓存20个值提高性能
    
    order_no VARCHAR(50) UNIQUE NOT NULL,
    user_id INT NOT NULL
);
-- 或者使用 BY DEFAULT(允许手动指定)
CREATE TABLE orders_identity_default (
    order_id INT GENERATED BY DEFAULT AS IDENTITY 
    (MINVALUE 1 MAXVALUE 999999 CYCLE) PRIMARY KEY,
    -- BY DEFAULT:可以手动指定,如果没指定则自动生成
    -- CYCLE:达到最大值后从头开始
    order_no VARCHAR(50)
);
-- 插入数据
INSERT INTO orders_identity (order_no, user_id)
VALUES ('ORD2024001000', 1001);  -- order_id会自动为1000
INSERT INTO orders_identity_default (order_id, order_no) 
VALUES (999999, '手动指定');  -- 可以手动指定
-- 查看序列当前值
SELECT currval(pg_get_serial_sequence('orders_identity', 'order_id'));

7.3 手动管理序列

SQL 复制代码
-- 1. 创建独立的序列
CREATE SEQUENCE order_id_seq
START WITH 100000   -- 起始值
INCREMENT BY 1      -- 增量
MINVALUE 100000     -- 最小值
MAXVALUE 999999     -- 最大值
CACHE 10            -- 缓存值
CYCLE               -- 循环
OWNED BY orders_identity.order_id;  -- 关联到表
-- 2. 将序列应用到表
CREATE TABLE orders_custom (
    order_id INT PRIMARY KEY DEFAULT nextval('order_id_seq'),
    order_no VARCHAR(50),
    -- 其他字段...
    created_at TIMESTAMP DEFAULT now()
);
-- 或者修改现有表
ALTER TABLE orders_custom 
ALTER COLUMN order_id 
SET DEFAULT nextval('order_id_seq');
-- 3. 使用序列
-- 获取下一个值
SELECT nextval('order_id_seq');  -- 返回:100000
-- 查看当前值(不增长)
SELECT currval('order_id_seq');
-- 设置序列值
SELECT setval('order_id_seq', 200000);
-- 4. 复杂的序列使用场景
-- 场景:每月重置的订单号
CREATE SEQUENCE order_monthly_seq
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 999999
NO CYCLE;
-- 每月1号重置序列的函数
CREATE OR REPLACE FUNCTION reset_monthly_order_seq()
RETURNS void AS $$
BEGIN
    -- 检查是否是新月份
    IF EXTRACT(DAY FROM CURRENT_DATE) = 1 THEN
        -- 重置序列
        PERFORM setval('order_monthly_seq', 1);
    END IF;
END;
$$ LANGUAGE plpgsql;
-- 创建表使用这个序列
CREATE TABLE monthly_orders (
    order_id VARCHAR(20) PRIMARY KEY,
    real_id INT DEFAULT nextval('order_monthly_seq'),
    customer_name VARCHAR(100),
    order_date DATE DEFAULT CURRENT_DATE
);
-- 触发器:自动生成订单号 YYMM + 序列号
CREATE OR REPLACE FUNCTION generate_monthly_order_id()
RETURNS TRIGGER AS $$
BEGIN
    NEW.order_id = TO_CHAR(NEW.order_date, 'YYMM') || 
                   LPAD(NEW.real_id::TEXT, 6, '0');
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_monthly_order_id
BEFORE INSERT ON monthly_orders
FOR EACH ROW EXECUTE FUNCTION generate_monthly_order_id();
-- 测试
INSERT INTO monthly_orders (customer_name) VALUES ('张三');
INSERT INTO monthly_orders (customer_name) VALUES ('李四');
SELECT * FROM monthly_orders;
-- 结果:
-- order_id  | real_id | customer_name | order_date
-- 2401000001| 1       | 张三          | 2024-01-15
-- 2401000002| 2       | 李四          | 2024-01-15

7.4 三种方式的对比

SQL 复制代码
-- 创建三个表分别使用三种方式
CREATE TABLE table_serial (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50)
);
CREATE TABLE table_identity (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name VARCHAR(50)
);
CREATE SEQUENCE custom_seq;
CREATE TABLE table_custom_seq (
    id INT PRIMARY KEY DEFAULT nextval('custom_seq'),
    name VARCHAR(50)
);
-- 插入数据比较
INSERT INTO table_serial (name) VALUES ('Serial 1');
INSERT INTO table_identity (name) VALUES ('Identity 1');
INSERT INTO table_custom_seq (name) VALUES ('Custom 1');
-- 查看背后的序列
SELECT 
    t.table_name,
    c.column_name,
    c.column_default
FROM information_schema.tables t
JOIN information_schema.columns c ON t.table_name = c.table_name
WHERE t.table_name IN ('table_serial', 'table_identity', 'table_custom_seq')
AND c.column_name = 'id';
-- 性能对比
EXPLAIN ANALYZE INSERT INTO table_serial (name) 
SELECT generate_series(1,10000)::text || ' name';
EXPLAIN ANALYZE INSERT INTO table_identity (name) 
SELECT generate_series(1,10000)::text || ' name';
EXPLAIN ANALYZE INSERT INTO table_custom_seq (name) 
SELECT generate_series(1,10000)::text || ' name';

7.5 实战建议:使用指南

SQL 复制代码
-- 场景1:简单的单表主键 → 用 IDENTITY
CREATE TABLE users_simple (
    user_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,  -- 推荐
    username VARCHAR(50) UNIQUE,
    email VARCHAR(100)
);
-- 场景2:需要起始值和步长 → 用 IDENTITY with options
CREATE TABLE invoice_numbers (
    invoice_id INT GENERATED ALWAYS AS IDENTITY 
    (START WITH 10000 INCREMENT BY 1) PRIMARY KEY,
    invoice_no VARCHAR(20) UNIQUE
);
-- 场景3:需要跨表共享序列 → 用自定义序列
CREATE SEQUENCE global_task_id_seq;
CREATE TABLE tasks_a (
    task_id BIGINT PRIMARY KEY DEFAULT nextval('global_task_id_seq'),
    task_name VARCHAR(100)
);
CREATE TABLE tasks_b (
    task_id BIGINT PRIMARY KEY DEFAULT nextval('global_task_id_seq'),
    task_name VARCHAR(100)
);
-- 场景4:需要特殊格式(如日期+序列)→ 自定义函数
CREATE OR REPLACE FUNCTION generate_order_no()
RETURNS TRIGGER AS $$
DECLARE
    seq_val INT;
BEGIN
    -- 获取或创建当天的序列
    SELECT COALESCE(
        (SELECT last_value FROM order_daily_seq 
         WHERE seq_date = CURRENT_DATE),
        0
    ) + 1 INTO seq_val;
    
    -- 更新序列值
    DELETE FROM order_daily_seq WHERE seq_date = CURRENT_DATE;
    INSERT INTO order_daily_seq VALUES (CURRENT_DATE, seq_val);
    
    -- 生成订单号:年月日+5位序列
    NEW.order_no = TO_CHAR(CURRENT_DATE, 'YYYYMMDD') || 
                   LPAD(seq_val::TEXT, 5, '0');
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 场景5:UUID主键(分布式系统)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE distributed_users (
    user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 随机UUID
    -- 或者
    -- user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    username VARCHAR(50),
    created_at TIMESTAMP DEFAULT now()
);

第八章:综合实战 - 电商系统完整设计

8.1 完整电商数据库设计

SQL 复制代码
-- 1. 创建数据库
CREATE DATABASE ecommerce_db
    WITH 
    OWNER = postgres
    ENCODING = 'UTF8'
    LC_COLLATE = 'zh_CN.UTF-8'
    LC_CTYPE = 'zh_CN.UTF-8'
    CONNECTION LIMIT = -1;
-- 连接到数据库
\c ecommerce_db;
-- 2. 创建枚举类型
CREATE TYPE order_status AS ENUM (
    'pending',      -- 待支付
    'paid',         -- 已支付
    'shipped',      -- 已发货
    'delivered',    -- 已送达
    'cancelled',    -- 已取消
    'refunded'      -- 已退款
);
CREATE TYPE user_gender AS ENUM ('M', 'F', 'U');
CREATE TYPE product_status AS ENUM ('draft', 'published', 'out_of_stock', 'archived');
-- 3. 创建序列(用于分布式ID生成)
CREATE SEQUENCE global_id_seq
START WITH 1000000000
INCREMENT BY 1
CACHE 1000;
-- 4. 创建SCHEMA
CREATE SCHEMA users;
CREATE SCHEMA products;
CREATE SCHEMA orders;
CREATE SCHEMA logistics;
-- 5. 用户模块
CREATE TABLE users.user_profiles (
    user_id BIGINT GENERATED ALWAYS AS IDENTITY 
    (START WITH 1000 INCREMENT BY 1) PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL 
    CHECK (LENGTH(username) >= 3 AND username ~ '^[a-zA-Z0-9_]+$'),
    email VARCHAR(255) UNIQUE NOT NULL 
    CHECK (email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,}$'),
    phone VARCHAR(20) UNIQUE 
    CHECK (phone ~ '^+?[0-9]{10,15}$'),
    password_hash VARCHAR(255) NOT NULL,
    full_name VARCHAR(100),
    gender user_gender,
    birth_date DATE CHECK (birth_date <= CURRENT_DATE - INTERVAL '13 years'),
    avatar_url VARCHAR(500),
    is_active BOOLEAN DEFAULT true,
    is_vip BOOLEAN DEFAULT false,
    vip_expiry_date DATE,
    registration_ip INET,
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_at TIMESTAMPTZ DEFAULT now(),
    last_login_at TIMESTAMPTZ,
    
    -- 检查约束
    CONSTRAINT check_vip_expiry 
    CHECK (NOT is_vip OR vip_expiry_date > CURRENT_DATE),
    CONSTRAINT check_email_domain 
    CHECK (email NOT LIKE '%@spam.com%')
);
-- 用户地址表
CREATE TABLE users.user_addresses (
    address_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES users.user_profiles(user_id) 
    ON DELETE CASCADE,
    address_tag VARCHAR(20) CHECK (address_tag IN ('home', 'office', 'other')),
    recipient_name VARCHAR(100) NOT NULL,
    mobile VARCHAR(20) NOT NULL,
    province VARCHAR(50) NOT NULL,
    city VARCHAR(50) NOT NULL,
    district VARCHAR(50),
    detail_address TEXT NOT NULL,
    postal_code CHAR(6),
    is_default BOOLEAN DEFAULT false,
    latitude DECIMAL(10, 8),
    longitude DECIMAL(11, 8),
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_at TIMESTAMPTZ DEFAULT now(),
    
    -- 每个用户只有一个默认地址的复杂约束
    EXCLUDE USING gist (
        user_id WITH =,
        is_default WITH <> false
    ) WHERE (is_default = true)
);
-- 6. 产品模块(使用继承)
-- 父表:产品基础信息
CREATE TABLE products.products_base (
    product_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    product_code VARCHAR(50) UNIQUE NOT NULL,
    product_name VARCHAR(200) NOT NULL,
    category_id INT NOT NULL,
    brand_id INT NOT NULL,
    short_description VARCHAR(500),
    full_description TEXT,
    main_image_url VARCHAR(500),
    image_gallery TEXT[],  -- JSON数组
    status product_status DEFAULT 'draft',
    base_price DECIMAL(12, 2) NOT NULL CHECK (base_price > 0),
    cost_price DECIMAL(12, 2) CHECK (cost_price < base_price),
    tax_rate DECIMAL(5, 4) DEFAULT 0.13,
    weight_kg DECIMAL(8, 3) CHECK (weight_kg >= 0),
    volume_m3 DECIMAL(8, 4) CHECK (volume_m3 >= 0),
    warranty_months INT DEFAULT 0 CHECK (warranty_months >= 0),
    is_deleted BOOLEAN DEFAULT false,
    seo_title VARCHAR(200),
    seo_keywords TEXT[],
    seo_description TEXT,
    view_count INT DEFAULT 0 CHECK (view_count >= 0),
    sales_count INT DEFAULT 0 CHECK (sales_count >= 0),
    rating_avg DECIMAL(3, 2) DEFAULT 0 CHECK (rating_avg BETWEEN 0 AND 5),
    rating_count INT DEFAULT 0,
    created_by BIGINT NOT NULL,
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_by BIGINT,
    updated_at TIMESTAMPTZ DEFAULT now(),
    version INT DEFAULT 1,
    
    -- 索引
    INDEX idx_products_category (category_id),
    INDEX idx_products_brand (brand_id),
    INDEX idx_products_status (status),
    INDEX idx_products_price (base_price),
    
    -- 约束
    CONSTRAINT fk_products_created_by 
    FOREIGN KEY (created_by) REFERENCES users.user_profiles(user_id),
    CONSTRAINT chk_products_dates 
    CHECK (created_at <= updated_at),
    CONSTRAINT chk_products_rating 
    CHECK (rating_count = 0 OR rating_avg > 0)
);
-- 子表:服装产品
CREATE TABLE products.clothing_products (
    size_system VARCHAR(20) DEFAULT 'CN',
    sizes TEXT[] NOT NULL,  -- 可用尺码
    colors TEXT[] NOT NULL,  -- 可用颜色
    gender user_gender,
    age_group VARCHAR(20),
    fabric_material VARCHAR(100),
    washing_instructions TEXT,
    season VARCHAR(20),
    style VARCHAR(50)
) INHERITS (products.products_base);
-- 子表:电子产品
CREATE TABLE products.electronic_products (
    model_number VARCHAR(100),
    power_supply VARCHAR(100),
    power_consumption DECIMAL(8, 2),
    interfaces TEXT[],
    additional_accessories TEXT[],
    has_battery BOOLEAN DEFAULT false,
    battery_capacity VARCHAR(50),
    battery_life VARCHAR(50),
    certification TEXT[],
    tech_specs JSONB
) INHERITS (products.products_base);
-- 7. 订单模块
CREATE TABLE orders.orders (
    order_id BIGINT GENERATED ALWAYS AS IDENTITY 
    (START WITH 1000000000) PRIMARY KEY,
    order_no VARCHAR(50) UNIQUE NOT NULL,
    user_id BIGINT NOT NULL REFERENCES users.user_profiles(user_id),
    order_status order_status DEFAULT 'pending',
    total_amount DECIMAL(15, 2) NOT NULL CHECK (total_amount >= 0),
    discount_amount DECIMAL(12, 2) DEFAULT 0 CHECK (discount_amount >= 0),
    shipping_fee DECIMAL(8, 2) DEFAULT 0 CHECK (shipping_fee >= 0),
    tax_amount DECIMAL(12, 2) DEFAULT 0 CHECK (tax_amount >= 0),
    final_amount DECIMAL(15, 2) GENERATED ALWAYS AS 
        (total_amount - discount_amount + shipping_fee + tax_amount) STORED,
    payment_method VARCHAR(20),
    payment_status VARCHAR(20) DEFAULT 'pending',
    shipping_address JSONB NOT NULL,
    billing_address JSONB,
    invoice_needed BOOLEAN DEFAULT false,
    invoice_title VARCHAR(200),
    invoice_tax_number VARCHAR(50),
    notes TEXT,
    source_channel VARCHAR(50) DEFAULT 'web',
    ip_address INET,
    user_agent TEXT,
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_at TIMESTAMPTZ DEFAULT now(),
    paid_at TIMESTAMPTZ,
    shipped_at TIMESTAMPTZ,
    delivered_at TIMESTAMPTZ,
    cancelled_at TIMESTAMPTZ,
    
    -- 检查约束
    CONSTRAINT chk_order_amounts CHECK (
        final_amount >= 0 AND 
        discount_amount <= total_amount AND
        final_amount >= total_amount * 0.5  -- 最终金额不能低于原价50%
    ),
    CONSTRAINT chk_order_dates CHECK (
        created_at <= updated_at AND
        (paid_at IS NULL OR paid_at >= created_at) AND
        (shipped_at IS NULL OR shipped_at >= COALESCE(paid_at, created_at)) AND
        (delivered_at IS NULL OR delivered_at >= COALESCE(shipped_at, created_at)) AND
        (cancelled_at IS NULL OR cancelled_at >= created_at)
    ),
    CONSTRAINT chk_order_status CHECK (
        (order_status = 'cancelled' AND cancelled_at IS NOT NULL) OR
        (order_status != 'cancelled' AND cancelled_at IS NULL)
    )
);
-- 订单详情表
CREATE TABLE orders.order_items (
    item_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    order_id BIGINT NOT NULL REFERENCES orders.orders(order_id) ON DELETE CASCADE,
    product_id BIGINT NOT NULL,
    sku VARCHAR(100) NOT NULL,
    product_name VARCHAR(200) NOT NULL,
    unit_price DECIMAL(12, 2) NOT NULL CHECK (unit_price >= 0),
    quantity INT NOT NULL CHECK (quantity > 0),
    discount_rate DECIMAL(5, 4) DEFAULT 0 CHECK (discount_rate BETWEEN 0 AND 1),
    discount_amount DECIMAL(12, 2) DEFAULT 0 CHECK (discount_amount >= 0),
    tax_rate DECIMAL(5, 4) DEFAULT 0.13,
    subtotal DECIMAL(12, 2) GENERATED ALWAYS AS (unit_price * quantity) STORED,
    final_price DECIMAL(12, 2) GENERATED ALWAYS AS 
        (unit_price * quantity * (1 - discount_rate) - discount_amount) STORED,
    tax_amount DECIMAL(12, 2) GENERATED ALWAYS AS 
        (final_price * tax_rate) STORED,
    total_amount DECIMAL(12, 2) GENERATED ALWAYS AS 
        (final_price + tax_amount) STORED,
    product_snapshot JSONB,  -- 购买时的产品快照
    refund_quantity INT DEFAULT 0 CHECK (refund_quantity >= 0 AND refund_quantity <= quantity),
    refund_amount DECIMAL(12, 2) DEFAULT 0 CHECK (refund_amount >= 0),
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_at TIMESTAMPTZ DEFAULT now(),
    
    -- 检查约束
    CONSTRAINT chk_item_discount CHECK (
        discount_amount <= subtotal AND
        (discount_rate = 0 OR discount_amount = 0)
    ),
    CONSTRAINT chk_item_refund CHECK (
        refund_amount <= total_amount AND
        refund_quantity = 0 OR refund_amount > 0
    )
);
-- 8. 触发器:自动生成订单号
CREATE OR REPLACE FUNCTION generate_order_no()
RETURNS TRIGGER AS $$
DECLARE
    date_prefix VARCHAR(6);
    daily_seq INT;
    new_order_no VARCHAR(50);
BEGIN
    -- 生成日期前缀 YYMMDD
    date_prefix = TO_CHAR(NEW.created_at, 'YYMMDD');
    
    -- 获取或创建当天的序列
    LOCK TABLE orders.daily_order_counter IN EXCLUSIVE MODE;
    
    SELECT counter INTO daily_seq 
    FROM orders.daily_order_counter 
    WHERE counter_date = CURRENT_DATE;
    
    IF daily_seq IS NULL THEN
        daily_seq = 1;
        INSERT INTO orders.daily_order_counter (counter_date, counter) 
        VALUES (CURRENT_DATE, daily_seq);
    ELSE
        daily_seq = daily_seq + 1;
        UPDATE orders.daily_order_counter 
        SET counter = daily_seq 
        WHERE counter_date = CURRENT_DATE;
    END IF;
    
    -- 生成订单号:日期前缀 + 6位序列
    new_order_no = date_prefix || LPAD(daily_seq::TEXT, 6, '0');
    
    NEW.order_no = new_order_no;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建计数器表
CREATE TABLE orders.daily_order_counter (
    counter_date DATE PRIMARY KEY DEFAULT CURRENT_DATE,
    counter INT DEFAULT 0 NOT NULL
);
-- 创建触发器
CREATE TRIGGER trg_generate_order_no
BEFORE INSERT ON orders.orders
FOR EACH ROW EXECUTE FUNCTION generate_order_no();
-- 9. 视图:销售报表
CREATE VIEW orders.sales_summary AS
SELECT 
    DATE(created_at) as sale_date,
    COUNT(*) as order_count,
    COUNT(DISTINCT user_id) as customer_count,
    SUM(final_amount) as total_sales,
    AVG(final_amount) as avg_order_value,
    COUNT(CASE WHEN order_status = 'cancelled' THEN 1 END) as cancelled_count,
    SUM(CASE WHEN order_status = 'cancelled' THEN final_amount ELSE 0 END) as cancelled_amount
FROM orders.orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(created_at)
ORDER BY sale_date DESC;
-- 10. 索引优化
CREATE INDEX idx_orders_user_date ON orders.orders(user_id, created_at DESC);
CREATE INDEX idx_orders_status_date ON orders.orders(order_status, created_at);
CREATE INDEX idx_order_items_order ON orders.order_items(order_id);
CREATE INDEX idx_users_email ON users.user_profiles USING HASH(email);
CREATE INDEX idx_users_phone ON users.user_profiles USING HASH(phone);
CREATE INDEX idx_products_code ON products.products_base USING HASH(product_code);
CREATE INDEX idx_products_search ON products.products_base 
USING gin(to_tsvector('chinese', product_name || ' ' || COALESCE(short_description, '')));
-- 11. 分区表:订单历史(按月分区)
CREATE TABLE orders.orders_history (
    LIKE orders.orders INCLUDING ALL
) PARTITION BY RANGE (created_at);
-- 创建分区
CREATE TABLE orders.orders_history_2024_01 
PARTITION OF orders.orders_history
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE orders.orders_history_2024_02 
PARTITION OF orders.orders_history
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');

8.2 数据验证和测试

SQL 复制代码
-- 插入测试数据
-- 用户数据
INSERT INTO users.user_profiles 
(username, email, phone, password_hash, full_name) 
VALUES 
('zhangsan', 'zhangsan@example.com', '13800138000', 'hashed_pw_1', '张三'),
('lisi', 'lisi@example.com', '13900139000', 'hashed_pw_2', '李四');
-- 地址数据
INSERT INTO users.user_addresses 
(user_id, address_tag, recipient_name, mobile, province, city, detail_address) 
VALUES 
(1000, 'home', '张三', '13800138000', '北京市', '北京市', '朝阳区建国路88号'),
(1000, 'office', '张三', '13800138000', '上海市', '上海市', '浦东新区张江高科');
-- 产品数据
INSERT INTO products.clothing_products 
(product_code, product_name, category_id, brand_id, base_price, sizes, colors) 
VALUES 
('CLO001', '男士纯棉T恤', 1, 1, 99.00, '{"S", "M", "L", "XL"}', '{"白色", "黑色", "灰色"}');
INSERT INTO products.electronic_products 
(product_code, product_name, category_id, brand_id, base_price, model_number, power_consumption) 
VALUES 
('ELE001', '无线蓝牙耳机', 2, 2, 299.00, 'BT-2024', 5.5);
-- 订单数据(触发器会自动生成订单号)
INSERT INTO orders.orders 
(user_id, total_amount, shipping_fee, shipping_address, payment_method)
VALUES 
(1000, 398.00, 10.00, '{"recipient": "张三", "mobile": "13800138000", "address": "北京市朝阳区"}', 'alipay');
-- 查看结果
SELECT * FROM orders.sales_summary;
SELECT * FROM orders.orders WHERE order_no LIKE '240115%';
-- 测试继承查询
SELECT 
    p.product_name,
    p.base_price,
    CASE 
        WHEN p.tableoid = 'products.clothing_products'::regclass THEN '服装'
        WHEN p.tableoid = 'products.electronic_products'::regclass THEN '电子'
        ELSE '其他'
    END as category,
    cp.sizes,
    ep.model_number
FROM products.products_base p
LEFT JOIN products.clothing_products cp ON p.product_id = cp.product_id
LEFT JOIN products.electronic_products ep ON p.product_id = ep.product_id;
-- 测试约束
-- 这个会失败:价格不能为负
INSERT INTO products.products_base (product_code, product_name, category_id, brand_id, base_price)
VALUES ('TEST001', '测试商品', 1, 1, -100);  -- ERROR: 违反检查约束
-- 这个会失败:电子邮件格式错误
INSERT INTO users.user_profiles (username, email, password_hash)
VALUES ('testuser', 'invalid-email', 'hashed');  -- ERROR: 违反检查约束

第九章:学习要点总结

9.1 PostgreSQL的核心优势

SQL 复制代码
-- 1. 数据类型丰富:不仅仅是数字和字符串
SELECT 
    ARRAY[1,2,3] as "数组",
    '{"name": "张三", "age": 25}'::jsonb as "JSONB",
    point(10,20) as "点坐标",
    '2024-01-15'::date as "日期类型",
    '12:30:00'::time as "时间类型",
    true::boolean as "布尔值";
-- 2. 约束强大:数据质量的保证
-- NOT NULL, UNIQUE, FOREIGN KEY, CHECK, EXCLUDE
-- 3. 继承能力:面向对象的数据建模
-- TABLE inheritance = 代码复用 + 数据组织
-- 4. 多种自增方式:适应不同场景
-- SERIAL:简单快速
-- IDENTITY:标准,功能强
-- 自定义序列:完全控制
-- 5. 完整的DDL:灵活的表定义
-- CREATE/ALTER/DROP 各种对象(表、索引、视图、函数等)

9.2 何时使用什么?

需求场景 推荐方案 示例
简单自增主键 IDENTITY列 id INT GENERATED ALWAYS AS IDENTITY
复杂ID生成 自定义序列 CREATE SEQUENCE order_seq;
数据强约束 CHECK约束 CHECK (price > 0 AND stock >= 0)
多类别产品 表继承 电子产品表 INHERITS (产品基础表)
JSON数据 JSONB类型 attributes JSONB
空间数据 PostGIS扩展 location GEOGRAPHY(POINT)
全文搜索 tsvector类型 search_vector tsvector

9.3 最佳实践清单

  1. 命名规范
SQL 复制代码
-- 表:复数,小写,下划线分隔
users, order_items, product_categories
-- 列:小写,下划线分隔
user_id, created_at, is_active
-- 主键:表名_id
user_id, order_id, product_id
-- 外键:引用表_引用列
user_id -> users.user_id
  1. 始终使用事务
SQL 复制代码
BEGIN;
-- 一系列操作
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
INSERT INTO transactions (user_id, amount, type) VALUES (1, -100, 'withdraw');
UPDATE system_log SET last_transaction_id = ...;
COMMIT;  -- 或者 ROLLBACK;
  1. 合理使用索引
SQL 复制代码
-- 查询频繁的字段加索引
CREATE INDEX idx_users_email ON users(email);
-- 复合索引考虑顺序
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);
-- 部分索引优化性能
CREATE INDEX idx_active_products ON products WHERE is_active = true;
-- 表达式索引应对特殊查询
CREATE INDEX idx_lower_username ON users(LOWER(username));
  1. 监控和优化
SQL 复制代码
-- 查看表大小
SELECT 
    table_name,
    pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) as size
FROM information_schema.tables 
WHERE table_schema = 'public'
ORDER BY pg_total_relation_size(quote_ident(table_name)) DESC;
-- 查看索引使用情况
SELECT 
    schemaname,
    tablename,
    indexname,
    idx_scan as scans,
    idx_tup_read as rows_read,
    idx_tup_fetch as rows_fetched
FROM pg_stat_user_indexes 
ORDER BY idx_scan DESC;
相关推荐
UrbanJazzerati1 小时前
Python实现Salesforce Bulk API 2.0批量数据导入:从Excel到云端的高效方案
后端·面试
豆苗学前端1 小时前
彻底讲透医院移动端手持设备PDA离线同步架构:从"记账本"到"分布式共识",吊打面试官
前端·javascript·后端
用户298698530141 小时前
C#中如何创建目录(TOC):使用Spire.Doc for .NET实现Word TOC自动化
后端·c#·.net
大鹏19882 小时前
警惕 Python 的"甜蜜陷阱":Pickle 反序列化漏洞深度剖析
后端
鱼人2 小时前
PHP 入门指南:从零基础到掌握核心语法
后端
却尘2 小时前
一个 ERR_SSL_PROTOCOL_ERROR 让我们排查了三层问题,最后发现根本不是 SSL 的锅
前端·后端·网络协议
yhyyht2 小时前
Apache Camel 框架入门记录(二)
后端
paterWang2 小时前
基于SpringBoot+Vue的鞋类商品购物商城系统的设计与实现
vue.js·spring boot·后端
paterWang2 小时前
基于SpringBoot的商铺共享点评系统的设计与实现
spring boot·后端