首页 / PostgreSQL 入门教程 / 数组、复合类型与其他特殊类型

PostgreSQL 入门教程

数组、复合类型与其他特殊类型

本教程共 50 篇 · 第 17 篇 · 更新于 2026-07-31 · 约 9 分钟阅读

PostgreSQLPostgreSQL 入门教程数组复合类型inetbytea

17. 数组、复合类型与其他特殊类型

本节目标:学完认识 PostgreSQL 的数组、复合类型、网络地址、几何、二进制、范围等非常规类型,知道各自适用场景。

除了前面的常规类型,PostgreSQL 还有一批「特殊类型」。它们不是每天都要用,但遇到对的场景能省很多事。比如一个用户有多个标签,用数组就比另开一张表简单。本章挨个过一遍,知道有这些能力、啥时候该用即可。

数组(ARRAY)

任何类型都能加 [] 变成数组,比如 TEXT[]INT[]INTEGER[]。元素类型必须一致,不能混装。

CREATE TABLE users (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(120),
  age SMALLINT,
  tags TEXT[],
  created_at TIMESTAMPTZ DEFAULT now()
);

INSERT INTO users (username, tags) VALUES ('alice', ARRAY['vip','new']);

数组字面量还能直接写花括号形式:'{vip,new}'::TEXT[]

取下标(从 1 开始)和按值搜索:

SELECT username, tags[1] FROM users;          -- 第一个标签
SELECT * FROM users WHERE 'vip' = ANY(tags);  -- 包含 vip 的用户

数组还能做很多运算:连接 ||、长度 array_length、判断是否重叠 &&、包含 @> 等:

SELECT array_append(tags, 'old') FROM users WHERE username='alice';
SELECT array_length(tags, 1) FROM users;
SELECT ARRAY[1,2,3] && ARRAY[3,4];   -- true,有重叠元素

把数组拆成多行用 unnest

SELECT username, unnest(tags) FROM users;
Note

数组下标从 1 开始,不是 0,这是 PostgreSQL 数组的约定,别和很多编程语言搞混。需要频繁按数组元素建索引或做复杂关联时,往往拆成子表更规范;数组适合简单、固定的一对多。数组上也能建 GIN 索引加速 @> 查询。

复合类型(Composite)

把多个字段打包成一个自定义类型,类似把「一行」当作一个值。比如地址由省、市、街道组成。

CREATE TYPE address AS (
  street TEXT,
  city TEXT,
  zip TEXT
);

CREATE TABLE users (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  addr address
);

INSERT INTO users (username, addr) VALUES ('alice', ROW('南京路','上海','200001'));

取复合类型的某个字段,注意括号:

SELECT username, (addr).city FROM users;   -- 上海

更新某个字段:

UPDATE users SET addr.city = '北京' WHERE username = 'alice';
Note

建表时 PostgreSQL 会隐式生成一个同名的复合类型,所以表也能当复合类型用。复合类型适合把「一组相关字段」当整体传递,但查询单个字段不如拆成独立列直观,别滥用。

网络地址(inet / cidr)

存 IP 和网段,比用文本更专业:带格式校验,还有专门的运算符(比如判断 IP 是否属于某网段)。

  • inet:主机地址,可带子网,如 192.168.1.10
  • cidr:网络段,如 192.168.1.0/24,不能带主机位。
CREATE TABLE devices (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  ip INET
);

INSERT INTO devices (ip) VALUES ('192.168.1.10');

判断某个 IP 是否落在网段内:

SELECT '192.168.1.10'::inet << '192.168.1.0/24'::cidr;  -- true
Tip

存 IP 地址优先用 inet,它会校验格式并支持网段运算,比 varchar 靠谱得多。需要严格表示「网络」时用 cidr

几何类型

存二维空间对象:point 点、line 线、box 矩形、circle 圆等。适合简单几何运算。

SELECT '((1,1),(4,4))'::box AS 矩形;     -- 一个矩形
SELECT '(3,3)'::point AS 点;               -- 一个点
SELECT '<(0,0), 5>'::circle AS 圆;         -- 圆心(0,0)半径5

几何类型之间能做距离、包含、相交等运算,做地图、图形相关时才派上用场。日常业务用得少。

二进制(bytea)

bytea 存原始二进制,比如图片、文件片段。默认以十六进制输入输出。

CREATE TABLE files (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  data BYTEA
);

INSERT INTO files (data) VALUES ('\xDEADBEEF');
Warning

大文件别直接塞 bytea,通常存文件系统、数据库只存路径。小图标这类才适合放库里——大对象会拖慢备份、膨胀表体积,还不好做缓存。

范围类型(Range)

PostgreSQL 还有一组范围类型,表示「一段连续的区间」:int4range(整数区间)、int8rangedaterange(日期区间)、numrangetsrange(时间戳区间)等。常用来表达「有效期」「排班时段」。

CREATE TABLE events (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name TEXT,
  during daterange
);

INSERT INTO events (name, during)
VALUES ('促销', daterange('2026-08-01', '2026-08-31'));

SELECT * FROM events WHERE during @> DATE '2026-08-15';  -- 该日期在区间内

范围类型自带去重、重叠判断,比自己用两个字段加条件优雅得多。

hstore 键值对

hstore 把「键 => 值」存进一个字段,键和值都是文本。它是扩展,需要先启用。

CREATE EXTENSION hstore;

CREATE TABLE books (
  id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  title VARCHAR(120),
  attr hstore
);

INSERT INTO books (title, attr)
VALUES ('PG 教程', '"publisher" => "码上学", "weight" => "500g"');

SELECT title, attr -> 'publisher' FROM books;

hstore 也支持 ?(键存在)、@>(包含)等操作符,和 jsonb 类似但功能更弱。

Tip

新项目如果要用键值结构,优先考虑 jsonb,它已内置且功能更全。hstore 主要在维护老库时遇到。

先想清楚要不要特殊类型

在介绍具体类型前,先说一句总原则:特殊类型是用来解决「常规列不好表达」的场景的。能用普通列清晰表达、又要频繁查询的,就别上特殊类型。下面这些类型是「工具箱里的备选」,而不是默认选项。

数组与范围的更多玩法

数组还能用 array_agg 把多行聚合成一个数组,和它反向的 unnest 把数组拆成多行,两者配合很常用:

-- 先把每行数组拆成多行,再按用户聚合成一个数组
SELECT username, array_agg(t) AS all_tags
FROM (SELECT username, unnest(tags) AS t FROM users) s
GROUP BY username;

-- 反过来,把数组直接拆成多行
SELECT username, unnest(tags) FROM users;

范围类型除了算「是否在区间内」,还能算重叠、相邻、取交集:

SELECT int4range(1,10) && int4range(5,15);   -- true,有重叠
SELECT int4range(1,10) * int4range(5,15);    -- [5,10),交集

范围类型自带「排除约束(EXCLUDE)」能力,能优雅地防止时间段重叠,比如会议室排班不能撞期,这是它独到的用处。

何时不该用特殊类型

这些特殊类型虽好,但不要为了用而用。判断标准很简单:如果数据固定、要频繁关联或聚合,拆成普通列或独立表更规范、约束更强、查询优化器也更容易挑对索引。数组、jsonb、范围适合「结构灵活或整体作为一个值处理」的场景。一旦你发现自己在对数组元素频繁做复杂关联查询,多半该拆表了。

一句话总结

  1. 同类型多值 → 数组 []
  2. 多字段打包 → 复合类型 CREATE TYPE ... AS ()
  3. IP / 网段 → inet / cidr
  4. 图形 → 几何类型。
  5. 一段区间 → 范围类型 daterange 等。
  6. 原始字节 → bytea(仅小数据)。
  7. 老式键值 → hstore(新项目用 jsonb)。

这些特殊类型不必强记。知道「PostgreSQL 有这能力」,等真遇到对应场景,回来翻这一章就够了。