1. 首页 > PostgreSQL教程 > 正文

PostgreSQL教程FG048-PG自定义数据类型:基础创建与应用

本文档详细介绍PostgreSQL自定义数据类型的创建与应用,包括复合类型、枚举类型、域类型和范围类型,风哥教程参考PostgreSQL官方文档内容,适合数据库管理员和开发人员在生产环境中使用自定义数据类型进行数据建模。

Part01-基础概念与理论知识

1.1 PostgreSQL自定义数据类型概述

PostgreSQL允许用户创建自定义数据类型,以满足特定业务需求。自定义数据类型可以扩展PostgreSQL的内置类型系统,提供更符合业务逻辑的数据结构。PostgreSQL支持多种类型的自定义数据类型,包括复合类型、枚举类型、域类型和范围类型。更多视频教程www.fgedu.net.cn

自定义数据类型的用途:

  • 数据建模:创建更符合业务逻辑的数据结构
  • 数据验证:通过域类型实现数据验证
  • 代码可读性:使用有意义的类型名称提高代码可读性
  • 数据一致性:确保数据的一致性和完整性

1.2 自定义数据类型的类型

PostgreSQL支持的自定义数据类型:

  • 复合类型:由多个字段组成的类型,类似于结构体
  • 枚举类型:由预定义值集合组成的类型
  • 域类型:基于现有类型的约束类型
  • 范围类型:表示值的范围的类型

1.3 自定义数据类型的优势

自定义数据类型的优势:

  • 数据封装:将相关数据封装在一起
  • 类型安全:提供更强的类型检查
  • 代码重用:可以在多个表中使用相同的类型
  • 业务逻辑表达:更准确地表达业务逻辑
风哥提示:自定义数据类型可以提高数据库设计的灵活性和可维护性,适合复杂的业务场景。学习交流加群风哥微信: itpux-com

Part02-生产环境规划与建议

2.1 自定义数据类型最佳实践

— 自定义数据类型最佳实践

— 1. 合理使用自定义类型
— – 只在必要时创建自定义类型
— – 考虑类型的可维护性
— – 确保类型名称有意义

— 2. 复合类型使用建议
— – 用于关联紧密的数据
— – 避免嵌套过深的复合类型
— – 考虑性能影响

— 3. 枚举类型使用建议
— – 用于有限的预定义值
— – 避免频繁修改枚举值
— – 考虑使用查找表的替代方案

— 4. 域类型使用建议
— – 用于添加约束到现有类型
— – 确保约束的一致性
— – 考虑使用检查约束的替代方案

— 5. 范围类型使用建议
— – 用于表示连续的值范围
— – 确保范围的有效性
— – 考虑索引优化

2.2 性能考虑

自定义数据类型的性能考虑:

  • 复合类型:可能增加存储开销,查询时需要额外的解析
  • 枚举类型:存储效率高,但修改困难
  • 域类型:几乎没有性能开销,只是添加约束
  • 范围类型:存储效率高,支持范围索引
风哥教程针对生产环境建议:在性能敏感的场景中,评估自定义数据类型的性能影响。from PostgreSQL视频:www.itpux.com

2.3 维护考虑

自定义数据类型的维护考虑:

  • 版本管理:跟踪自定义类型的变更
  • 向后兼容:确保类型变更的向后兼容性
  • 文档:为自定义类型提供详细的文档
  • 测试:确保自定义类型在各种场景下正常工作

Part03-生产环境项目实施方案

3.1 复合类型的创建与使用

3.1.1 复合类型的创建

— 复合类型的创建

— 创建复合类型
CREATE TYPE fgedu_address AS (
street VARCHAR(100),
city VARCHAR(50),
state VARCHAR(50),
zip_code VARCHAR(20),
country VARCHAR(50)
);

— 创建包含复合类型的表
CREATE TABLE fgedu_customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
address fgedu_address
);

3.1.2 复合类型的使用

— 复合类型的使用

— 插入数据
INSERT INTO fgedu_customers (name, email, address) VALUES (
‘John Doe’,
‘john.doe@fgedu.net.cn’,
ROW(‘123 Main St’, ‘New York’, ‘NY’, ‘10001’, ‘USA’)
);

— 查询数据
SELECT id, name, email, (address).street, (address).city FROM fgedu_customers;

— 更新数据
UPDATE fgedu_customers SET address = ROW(‘456 Oak Ave’, ‘Boston’, ‘MA’, ‘02101’, ‘USA’) WHERE id = 1;

— 比较复合类型
SELECT * FROM fgedu_customers WHERE address = ROW(‘123 Main St’, ‘New York’, ‘NY’, ‘10001’, ‘USA’);

3.2 枚举类型的创建与使用

3.2.1 枚举类型的创建

— 枚举类型的创建

— 创建枚举类型
CREATE TYPE fgedu_order_status AS ENUM (
‘pending’,
‘processing’,
‘shipped’,
‘delivered’,
‘cancelled’
);

— 创建包含枚举类型的表
CREATE TABLE fgedu_orders (
id SERIAL PRIMARY KEY,
order_id VARCHAR(20) UNIQUE NOT NULL,
customer_id INTEGER NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
status fgedu_order_status DEFAULT ‘pending’
);

3.2.2 枚举类型的使用

— 枚举类型的使用

— 插入数据
INSERT INTO fgedu_orders (order_id, customer_id, total_amount, status) VALUES
(‘ORD-20260407-001’, 1, 100.00, ‘pending’),
(‘ORD-20260407-002’, 2, 200.00, ‘processing’),
(‘ORD-20260407-003’, 3, 300.00, ‘shipped’);

— 查询数据
SELECT * FROM fgedu_orders WHERE status = ‘shipped’;

— 更新数据
UPDATE fgedu_orders SET status = ‘delivered’ WHERE id = 3;

— 枚举类型排序
SELECT * FROM fgedu_orders ORDER BY status;

3.3 域类型的创建与使用

3.3.1 域类型的创建

— 域类型的创建

— 创建域类型
CREATE DOMAIN fgedu_email AS VARCHAR(100) CHECK (
VALUE ~ ‘^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$’
);

CREATE DOMAIN fgedu_positive_integer AS INTEGER CHECK (
VALUE > 0
);

CREATE DOMAIN fgedu_phone_number AS VARCHAR(20) CHECK (
VALUE ~ ‘^[0-9]{10,15}$’
);

— 创建包含域类型的表
CREATE TABLE fgedu_contacts (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email fgedu_email UNIQUE NOT NULL,
phone fgedu_phone_number,
age fgedu_positive_integer
);

3.3.2 域类型的使用

— 域类型的使用

— 插入有效数据
INSERT INTO fgedu_contacts (name, email, phone, age) VALUES
(‘John Doe’, ‘john.doe@fgedu.net.cn’, ‘1234567890’, 30),
(‘Jane Smith’, ‘jane.smith@fgedu.net.cn’, ‘0987654321’, 25);

— 尝试插入无效数据(会失败)
— INSERT INTO fgedu_contacts (name, email, phone, age) VALUES
— (‘Invalid User’, ‘invalid-email’, ‘123’, -5);

— 查询数据
SELECT * FROM fgedu_contacts;

— 更新数据
UPDATE fgedu_contacts SET age = 31 WHERE id = 1;

3.4 范围类型的创建与使用

3.4.1 范围类型的创建

— 范围类型的创建

— 使用内置范围类型
CREATE TABLE fgedu_reservations (
id SERIAL PRIMARY KEY,
room_id INTEGER NOT NULL,
guest_name VARCHAR(100) NOT NULL,
reservation_period DATERANGE NOT NULL,
CHECK (lower(reservation_period) < upper(reservation_period)) ); -- 创建自定义范围类型 CREATE TYPE fgedu_int_range AS RANGE ( subtype = INTEGER ); CREATE TABLE fgedu_price_ranges ( id SERIAL PRIMARY KEY, product_id INTEGER NOT NULL, price_range fgedu_int_range NOT NULL, discount DECIMAL(5,2) NOT NULL );

3.4.2 范围类型的使用

— 范围类型的使用

— 插入数据
INSERT INTO fgedu_reservations (room_id, guest_name, reservation_period) VALUES
(101, ‘John Doe’, DATERANGE(‘2026-04-07’, ‘2026-04-10’)),
(102, ‘Jane Smith’, DATERANGE(‘2026-04-08’, ‘2026-04-12’));

— 插入价格范围数据
INSERT INTO fgedu_price_ranges (product_id, price_range, discount) VALUES
(1, ‘[-infinity, 100)’, 0),
(1, ‘[100, 500)’, 5),
(1, ‘[500, 1000)’, 10),
(1, ‘[1000, infinity)’, 15);

— 查询数据
SELECT * FROM fgedu_reservations WHERE reservation_period && DATERANGE(‘2026-04-09’, ‘2026-04-09’);

— 查询价格范围
SELECT * FROM fgedu_price_ranges WHERE price_range @> 750;

— 更新数据
UPDATE fgedu_reservations SET reservation_period = DATERANGE(‘2026-04-07’, ‘2026-04-11’) WHERE id = 1;

风哥提示:范围类型对于表示时间区间、价格区间等连续值范围非常有用,支持丰富的范围操作符。更多学习教程公众号风哥教程itpux_com

Part04-生产案例与实战讲解

4.1 复合类型使用案例

— 复合类型使用案例:地址管理

— 创建地址复合类型
CREATE TYPE fgedu_address AS (
street VARCHAR(100),
city VARCHAR(50),
state VARCHAR(50),
zip_code VARCHAR(20),
country VARCHAR(50)
);

— 创建客户表
CREATE TABLE fgedu_customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
billing_address fgedu_address,
shipping_address fgedu_address
);

— 插入数据
INSERT INTO fgedu_customers (name, email, billing_address, shipping_address) VALUES (
‘John Doe’,
‘john.doe@fgedu.net.cn’,
ROW(‘123 Main St’, ‘New York’, ‘NY’, ‘10001’, ‘USA’),
ROW(‘456 Oak Ave’, ‘Boston’, ‘MA’, ‘02101’, ‘USA’)
);

— 查询数据
SELECT
id,
name,
email,
(billing_address).street AS billing_street,
(billing_address).city AS billing_city,
(shipping_address).street AS shipping_street,
(shipping_address).city AS shipping_city
FROM fgedu_customers;

— 更新地址
UPDATE fgedu_customers SET
shipping_address = ROW(‘789 Pine Rd’, ‘Chicago’, ‘IL’, ‘60601’, ‘USA’)
WHERE id = 1;

4.2 枚举类型使用案例

— 枚举类型使用案例:订单状态管理

— 创建订单状态枚举类型
CREATE TYPE fgedu_order_status AS ENUM (
‘pending’,
‘processing’,
‘shipped’,
‘delivered’,
‘cancelled’
);

— 创建订单表
CREATE TABLE fgedu_orders (
id SERIAL PRIMARY KEY,
order_id VARCHAR(20) UNIQUE NOT NULL,
customer_id INTEGER NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
status fgedu_order_status DEFAULT ‘pending’,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

— 插入数据
INSERT INTO fgedu_orders (order_id, customer_id, total_amount, status) VALUES
(‘ORD-20260407-001’, 1, 100.00, ‘pending’),
(‘ORD-20260407-002’, 2, 200.00, ‘processing’),
(‘ORD-20260407-003’, 3, 300.00, ‘shipped’);

— 查询不同状态的订单
SELECT status, COUNT(*) AS order_count FROM fgedu_orders GROUP BY status;

— 更新订单状态
UPDATE fgedu_orders SET status = ‘delivered’, updated_at = CURRENT_TIMESTAMP WHERE id = 3;

— 查询已完成的订单
SELECT * FROM fgedu_orders WHERE status IN (‘delivered’, ‘cancelled’);

4.3 域类型使用案例

— 域类型使用案例:数据验证

— 创建域类型
CREATE DOMAIN fgedu_email AS VARCHAR(100) CHECK (
VALUE ~ ‘^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$’
);

CREATE DOMAIN fgedu_phone_number AS VARCHAR(20) CHECK (
VALUE ~ ‘^[0-9]{10,15}$’
);

CREATE DOMAIN fgedu_positive_decimal AS DECIMAL(10,2) CHECK (
VALUE > 0
);

— 创建产品表
CREATE TABLE fgedu_products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price fgedu_positive_decimal NOT NULL,
stock INTEGER CHECK (stock >= 0)
);

— 创建供应商表
CREATE TABLE fgedu_suppliers (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email fgedu_email UNIQUE NOT NULL,
phone fgedu_phone_number
);

— 插入有效数据
INSERT INTO fgedu_products (name, description, price, stock) VALUES
(‘iPhone 15’, ‘Apple iPhone 15’, 5999.99, 100),
(‘MacBook Pro’, ‘Apple MacBook Pro’, 12999.99, 50);

INSERT INTO fgedu_suppliers (name, email, phone) VALUES
(‘Apple Inc.’, ‘supplier@apple.com’, ‘1234567890’),
(‘Microsoft’, ‘supplier@microsoft.com’, ‘0987654321’);

— 尝试插入无效数据(会失败)
— INSERT INTO fgedu_products (name, description, price, stock) VALUES
— (‘Invalid Product’, ‘Invalid’, -100.00, -5);

— INSERT INTO fgedu_suppliers (name, email, phone) VALUES
— (‘Invalid Supplier’, ‘invalid-email’, ‘123’);

4.4 范围类型使用案例

— 范围类型使用案例:资源预订

— 创建房间表
CREATE TABLE fgedu_rooms (
id SERIAL PRIMARY KEY,
room_number VARCHAR(10) UNIQUE NOT NULL,
capacity INTEGER NOT NULL,
room_type VARCHAR(50) NOT NULL
);

— 创建预订表
CREATE TABLE fgedu_reservations (
id SERIAL PRIMARY KEY,
room_id INTEGER REFERENCES fgedu_rooms(id),
guest_name VARCHAR(100) NOT NULL,
reservation_period DATERANGE NOT NULL,
CHECK (lower(reservation_period) < upper(reservation_period)) ); -- 插入房间数据 INSERT INTO fgedu_rooms (room_number, capacity, room_type) VALUES ('101', 2, 'Standard'), ('102', 4, 'Deluxe'), ('103', 1, 'Single'); -- 插入预订数据 INSERT INTO fgedu_reservations (room_id, guest_name, reservation_period) VALUES (1, 'John Doe', DATERANGE('2026-04-07', '2026-04-10')), (2, 'Jane Smith', DATERANGE('2026-04-08', '2026-04-12')), (3, 'Bob Johnson', DATERANGE('2026-04-09', '2026-04-11')); -- 查询特定日期的预订 SELECT r.*, rm.room_number, rm.room_type FROM fgedu_reservations r JOIN fgedu_rooms rm ON r.room_id = rm.id WHERE reservation_period && DATERANGE('2026-04-09', '2026-04-09'); -- 查询可用房间(在特定日期范围内) SELECT * FROM fgedu_rooms rm WHERE NOT EXISTS ( SELECT 1 FROM fgedu_reservations r WHERE r.room_id = rm.id AND r.reservation_period && DATERANGE('2026-04-13', '2026-04-15') ); -- 检查预订冲突 SELECT * FROM fgedu_reservations r1 WHERE EXISTS ( SELECT 1 FROM fgedu_reservations r2 WHERE r1.room_id = r2.room_id AND r1.id != r2.id AND r1.reservation_period && r2.reservation_period );

Part05-风哥经验总结与分享

5.1 自定义数据类型使用技巧

自定义数据类型使用技巧:

  • 复合类型:
    • 用于关联紧密的数据,如地址、坐标等
    • 避免嵌套过深,影响查询性能
    • 使用ROW()构造器创建复合类型值
    • 使用括号语法访问复合类型字段
  • 枚举类型:
    • 用于有限的预定义值,如订单状态、用户角色等
    • 避免频繁修改枚举值,因为修改需要重建表
    • 考虑使用查找表作为替代方案,提高灵活性
    • 使用枚举类型可以提高代码可读性和类型安全性
  • 域类型:
    • 用于添加约束到现有类型,如邮箱、电话号码等
    • 确保约束的一致性,避免重复定义检查约束
    • 域类型可以被其他域类型继承,提高代码重用
    • 使用域类型可以简化表定义,提高代码可读性
  • 范围类型:
    • 用于表示连续的值范围,如时间区间、价格区间等
    • 使用内置范围类型或创建自定义范围类型
    • 利用范围操作符进行复杂查询
    • 为范围类型创建索引,提高查询性能

5.2 自定义数据类型常见问题解决

— 自定义数据类型常见问题解决

— 1. 复合类型问题
— 问题:无法直接更新复合类型的单个字段
— 解决:
— – 读取整个复合类型,修改后再写回
— – 使用PL/pgSQL函数更新复合类型

— 2. 枚举类型问题
— 问题:无法轻松添加或删除枚举值
— 解决:
— – 使用ALTER TYPE语句修改枚举类型
— – 考虑使用查找表作为替代方案

— 3. 域类型问题
— 问题:域类型约束与表级约束冲突
— 解决:
— – 确保约束的一致性
— – 避免重复定义约束

— 4. 范围类型问题
— 问题:范围查询性能差
— 解决:
— – 为范围类型创建索引
— – 使用合适的范围操作符
— – 避免过度使用复杂的范围表达式

5.3 自定义数据类型性能优化

— 自定义数据类型性能优化

— 1. 复合类型优化
— – 避免在频繁查询的列中使用复合类型
— – 考虑将复合类型拆分为单独的列,提高查询性能
— – 为复合类型的字段创建索引

— 2. 枚举类型优化
— – 枚举类型存储效率高,不需要特殊优化
— – 为枚举类型列创建索引,提高查询性能

— 3. 域类型优化
— – 域类型几乎没有性能开销,不需要特殊优化
— – 确保约束的有效性,避免过度复杂的约束

— 4. 范围类型优化
— – 为范围类型创建GiST索引,提高范围查询性能
— – 合理使用范围操作符,避免复杂的范围表达式
— – 考虑使用范围类型的边界条件,提高查询效率

— 5. 通用优化
— – 只在必要时创建自定义类型
— – 评估自定义类型的性能影响
— – 定期分析表,更新统计信息
— – 考虑使用生成列优化复合类型的查询

风哥提示:自定义数据类型是PostgreSQL的强大特性,可以提高数据库设计的灵活性和可维护性。在生产环境中,应该根据具体业务需求选择合适的自定义类型,并注意性能优化和维护考虑。

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息