数据类型 - 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;
- OIDS = FALSE
OIDS是对象标识符(Object IDentifiers)FALSE表示不为此表自动创建系统 OID 列- 在 PostgreSQL 8.1+ 版本中,默认就是
FALSE,显式声明是为了明确关闭 - OID 曾用于系统内部标识记录,但现代版本已弃用
- autovacuum_enabled = true
-
开启自动清理(AutoVACUUM)
-
自动清理会:
- 回收被删除或更新记录占用的空间
- 更新表的统计信息(用于查询优化)
- 防止事务ID回绕
-
对于频繁更新的表,建议保持开启(默认就是
true)
- 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_amount是NULL,则当作 0 - 否则使用
tax_amount的值
- 如果
CREATE TABLE orders_v3 (
- 创建新表
orders_v3
LIKE orders_v2 INCLUDING ALL
-
继承结构 :复制
orders_v2表的所有结构 -
包括内容:
- 所有列定义
- 索引(Indexes)
- 约束(Constraints)
- 默认值(Defaults)
- 等等...
-
不包括内容:
- 数据(空的)
- 触发器(Triggers)除非指定
INCLUDING ALL
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 最佳实践清单
- 命名规范
SQL
-- 表:复数,小写,下划线分隔
users, order_items, product_categories
-- 列:小写,下划线分隔
user_id, created_at, is_active
-- 主键:表名_id
user_id, order_id, product_id
-- 外键:引用表_引用列
user_id -> users.user_id
- 始终使用事务
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;
- 合理使用索引
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));
- 监控和优化
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;