这里的内容完全基于 Regina Obe Leo Hsu 著, 丁奇鹏 译《PostgreSQL即学即用》
其他参考文献: 刘增杰、张少军 编著 《PostgreSQL 9 从零开始学》
PostgreSQL 支持常用的整数、小数、浮点数等数字类型.
| 类型名称 | 说明 | 存储需求 | 取值范围 | |
| 小范围的整数 | 2 个字节 | (-2^15, 2^15-1) | -32768 ~ 32767 | |
| 普通大小的整数 | 4 个字节 | (-2^31, 2^31-1) | -2147483648 ~ 2147483647 | |
| 大整数 | 8 个字节 | (-2^63, 2^63-1) | -9223372036854775808 ~ 9223372036854775807 |
| 类型名称 | 说明 | 存储需求 |
| 6位十进制数字精度 | 4个字节 | |
| 15位十进制数字精度 | 8 个字节 |
PostgreSQL 也支持 SQL 标准表示法,
任意精度类型指
PostgreSQL 中有多种表示日期的数据类型, 主要有:
PostgreSQL 还支持时区, 并能够按照不同时区对夏令时进行自动转换. 此外, PostgreSQL 还支持一些特殊类型的数据类型, 如
对于
注意, PostgreSQL 中没有
全球划分为24个时区
时区(Time Zone), 是指地球上的各个区域使用同一个时间定义。
地球是自西向东自转, 东边比西边先看到太阳, 东边的时间也就比西边的早.
为了克服时间上的混乱, 1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上, 规定将全球划分为24个时区:
以英国格林尼治天文台旧址为中时区(零时区), 东边划分1-12区, 西边划分1-12区. 每个时区横跨经度15度, 时间正好是1小时. 最后的东、西第12区各跨经度7.5度, 以东、西经180度为界.
世界标准时间(Universal Time Coordinated),又称世界统一时间、国际时间、全球时钟、协调世界时,简称UTC,由原子钟提供,以国际原子时的秒长为基准计量,非常精确。
由于英文(Coodinated Universal Time, 简写为CUT)和法文(Temps universel coordonné, 简写为TUC)的缩写不同, 作为妥协, 简称UTC. UTC 是现在全球通用的时间标准, 全球各地都同意将各自的时间进行同步协调. UTC 时间是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以秒为单位的国际原子时所综合精算而成.
世界标准时间与北京时间的时差是8小时. 以现在北京时间减去8小时, 即为世界标准时间.
即, 世界标准时间=北京时间 - 8小时
PostgreSQL 对时区的处理并不是简单地在日期和时间类型的基础上额外增加一个标记.
比如对于带时区的信息
当你回调该数据以用于显示时, PostgreSQL 内部是这样运作的:
可以看到, PostgreSQL 并没有存储时区信息而仅是使用时区信息来把日期和时间转换为 UTC 标准时间再存储下来.
此后, 时区信息就丢失了. 当 PostgreSQL 需要显示该日期时间信息时, 它会按顺序查找当前
世界各国对于夏令时的规定五花八门, 如果某个数据库可能会被全球各地的应用访问, 那么就需要及时按照最新的全球夏令时规定来更新库中的时间信息. 手工跟踪全球夏令时的变化是一件无比繁琐的工作, 这需要一个全职的程序员专门来收集各国的夏令时安排, 并在前述数据库中刷新这些国家(包括海外飞地)的相关时间数据.
一位出差中的销售员需要坐飞机回家, 起点是旧金山, 终点是奥克兰附近. 当她登上飞机时, 当地时钟显示的时间为 2012年3月11日凌晨1点50分. 当她降落时, 当地时钟显示时间为2012年3月11日凌晨3点10分. 那么请问这段旅程共花了多长时间?
下面使用带时区信息的时间戳, 计算出来的时间仅为 20 分钟.
postgres@localhost:5436 tutorial# SELECT '2012-03-11 3:10 AM America/Los_Angeles'::timestamptz tutorial-# -'2012-03-11 1:50 AM America/Los_Angeles'::timestamptz; ?column? ---------- 00:20:00 (1 行记录)
主要是这段飞行过程中发生了夏令时的转换, 也就是时间向前跃迁了. 比如
下面的查询返回结果是 1小时20分钟.
postgres@localhost:5436 tutorial# SELECT '2012-03-11 3:10 AM'::timestamp-'2012-03-11 1:50 AM'::timestamp; ?column? ---------- 01:20:00 (1 行记录)
例. 输入时使用的是一个时区的本地时间, 输出却是另一个时区的本地时间.
postgres@localhost:5436 tutorial# SELECT '2012-02-28 10:00 PM America/Los_Angeles'::timestamptz;
timestamptz
------------------------
2012-02-29 14:00:00+08
(1 行记录)
postgres@localhost:5436 tutorial# SELECT '2025-11-05 10:00 PM Asia/Shanghai'::timestamptz;
timestamptz
------------------------
2025-11-05 22:00:00+08
(1 行记录)
假设当前时间是 2025年11月6日 15:40, 那么输入下面的会发生错误.
postgres@localhost:5436 tutorial# SELECT '2025-11-06 15:40 PM Asia/Shanghai'::timestamptz;
ERROR: date/time field value out of range: "2025-11-06 15:40 PM Asia/Shanghai"
第1行SELECT '2025-11-06 15:40 PM Asia/Shanghai'::timestamptz;
^
时间:11.347 ms
原因是2025年11月6日 15:40是我们这边的本地时间.
例. 将带时区信息的时间戳数据转换为不带时区的时间戳数据. 也就是将
假设洛杉矶本地时间是 2012年2月28日晚上10:00, 问此时巴黎当地时间是几号几点?
postgres@localhost:5436 tutorial# SELECT '2012-02-28 10:00 PM America/Los_Angeles'::timestamptz AT TIME ZONE 'Europe/Paris';
timezone
---------------------
2012-02-29 07:00:00
(1 行记录)
巴黎比洛杉矶早9个小时.
假设现在扬州本地时间是 2025年11月06日下午3点50分, 请问此时巴黎当地时间是几号几点, 此时洛杉矶当地时间是多少?
postgres@localhost:5436 tutorial# SELECT '2025-11-06 3:50 PM Asia/Shanghai'::timestamptz AT TIME ZONE 'Europe/Paris';
timezone
---------------------
2025-11-06 08:50:00
(1 行记录)
巴黎比我们晚7个小时.
postgres@localhost:5436 tutorial# SELECT '2025-11-06 3:50 PM Asia/Shanghai'::timestamptz AT TIME ZONE 'America/Los_Angeles';
timezone
---------------------
2025-11-05 23:50:00
(1 行记录)
洛杉矶比我们晚16个小时.
时间间隔类型(
如果没有
下面的例子展示了可用于日期和时间类型的运算符和函数.
运算符可以在一个时间类型值上加上一段时间间隔.
将时间值 2025年11月06日下午3点50分 加上一个小时.
postgres@localhost:5436 tutorial# SELECT '2025-11-06 3:50 PM'::timestamp + interval '1 hour';
?column?
---------------------
2025-11-06 16:50:00
(1 行记录)
时间间隔可以是 1.5 个小时这种值.
postgres@localhost:5436 tutorial# SELECT '2025-11-06 3:50 PM'::timestamp + interval '1.5 hour';
?column?
---------------------
2025-11-06 17:20:00
(1 行记录)
可以将两个时间间隔类型的值相加. 表示时间间隔时, 可以在字符串前加上
postgres@localhost:5436 tutorial# SELECT '23 hours 20 minutes'::interval + interval '1.5 hour'; ?column? ---------- 24:50:00 (1 行记录)
postgres@localhost:5436 tutorial# SELECT '23 hours 20 minutes'::interval + '1.5 hour'::interval; ?column? ---------- 24:50:00 (1 行记录)
可以将时间类型值减去一段时间间隔. 例, 将时间值 2025年11月06日下午3点50分 减去两个小时.
postgres@localhost:5436 tutorial# SELECT '2025-11-06 3:50 PM'::timestamp - '2 hours'::interval;
?column?
---------------------
2025-11-06 13:50:00
(1 行记录)
区间重叠运算符
postgres@localhost:5436 tutorial# SELECT ('2025-11-06 10:00 AM'::timestamp, '2025-11-06 2:00 PM'::timestamp)
tutorial-# OVERLAPS ('2025-11-06 11:00 AM'::timestamp, '2025-11-06 3:00 PM'::timestamp);
overlaps
----------
t
(1 行记录)
postgres@localhost:5436 tutorial# SELECT ('2025-10-06'::date, '2025-11-06'::date) OVERLAPS ('2025-11-12'::date, '2025-11-21'::date);
overlaps
----------
f
(1 行记录)
除了运算符以外, PostgreSQL 还提供了一些时间类型的函数. 参见 https://www.postgresql.org/docs/current/functions-datetime.html
postgres@localhost:5436 tutorial# SELECT generate_series('2025/01/02', '2025/11/02', interval '1 month') As dt;
dt
------------------------
2025-01-02 00:00:00+08
2025-02-02 00:00:00+08
2025-03-02 00:00:00+08
2025-04-02 00:00:00+08
2025-05-02 00:00:00+08
2025-06-02 00:00:00+08
2025-07-02 00:00:00+08
2025-08-02 00:00:00+08
2025-09-02 00:00:00+08
2025-10-02 00:00:00+08
2025-11-02 00:00:00+08
(11 行记录)
将上面结果集中的每个时间值减去一天.
postgres@localhost:5436 tutorial# SELECT (dt - interval '1 day')::date As eom
tutorial-# FROM generate_series('2025/01/02', '2025/11/02', interval '1 month') As dt;
eom
------------
2025-01-01
2025-02-01
2025-03-01
2025-04-01
2025-05-01
2025-06-01
2025-07-01
2025-08-01
2025-09-01
2025-10-01
2025-11-01
(11 行记录)
另一种经常使用的操作是从日期和时间类型的数值中抽取某一部分. 在 PostgreSQL 中, 联用
SELECT dt, date_part('hour', dt) As hour, to_char(dt, 'HH12:MI AM') As tm
FROM
generate_series('2025-11-05 12:30 AM', '2025-11-06 3:00 AM', interval '15 minutes') As dt;
postgres@localhost:5436 tutorial# SELECT dt, date_part('hour', dt) As hour, to_char(dt, 'HH12:MI AM') As tm
tutorial-# FROM
tutorial-# generate_series('2025-11-05 12:30 AM', '2025-11-06 3:00 AM', interval '15 minutes') As dt;
dt | hour | tm
------------------------+------+----------
2025-11-05 00:30:00+08 | 0 | 12:30 AM
2025-11-05 00:45:00+08 | 0 | 12:45 AM
2025-11-05 01:00:00+08 | 1 | 01:00 AM
2025-11-05 01:15:00+08 | 1 | 01:15 AM
2025-11-05 01:30:00+08 | 1 | 01:30 AM
2025-11-05 01:45:00+08 | 1 | 01:45 AM
2025-11-05 02:00:00+08 | 2 | 02:00 AM
2025-11-05 02:15:00+08 | 2 | 02:15 AM
2025-11-05 02:30:00+08 | 2 | 02:30 AM
2025-11-05 02:45:00+08 | 2 | 02:45 AM
2025-11-05 03:00:00+08 | 3 | 03:00 AM
2025-11-05 03:15:00+08 | 3 | 03:15 AM
2025-11-05 03:30:00+08 | 3 | 03:30 AM
2025-11-05 03:45:00+08 | 3 | 03:45 AM
2025-11-05 04:00:00+08 | 4 | 04:00 AM
2025-11-05 04:15:00+08 | 4 | 04:15 AM
2025-11-05 04:30:00+08 | 4 | 04:30 AM
2025-11-05 04:45:00+08 | 4 | 04:45 AM
2025-11-05 05:00:00+08 | 5 | 05:00 AM
2025-11-05 05:15:00+08 | 5 | 05:15 AM
2025-11-05 05:30:00+08 | 5 | 05:30 AM
2025-11-05 05:45:00+08 | 5 | 05:45 AM
2025-11-05 06:00:00+08 | 6 | 06:00 AM
2025-11-05 06:15:00+08 | 6 | 06:15 AM
2025-11-05 06:30:00+08 | 6 | 06:30 AM
2025-11-05 06:45:00+08 | 6 | 06:45 AM
2025-11-05 07:00:00+08 | 7 | 07:00 AM
2025-11-05 07:15:00+08 | 7 | 07:15 AM
2025-11-05 07:30:00+08 | 7 | 07:30 AM
2025-11-05 07:45:00+08 | 7 | 07:45 AM
2025-11-05 08:00:00+08 | 8 | 08:00 AM
2025-11-05 08:15:00+08 | 8 | 08:15 AM
2025-11-05 08:30:00+08 | 8 | 08:30 AM
2025-11-05 08:45:00+08 | 8 | 08:45 AM
2025-11-05 09:00:00+08 | 9 | 09:00 AM
2025-11-05 09:15:00+08 | 9 | 09:15 AM
2025-11-05 09:30:00+08 | 9 | 09:30 AM
2025-11-05 09:45:00+08 | 9 | 09:45 AM
2025-11-05 10:00:00+08 | 10 | 10:00 AM
2025-11-05 10:15:00+08 | 10 | 10:15 AM
2025-11-05 10:30:00+08 | 10 | 10:30 AM
2025-11-05 10:45:00+08 | 10 | 10:45 AM
2025-11-05 11:00:00+08 | 11 | 11:00 AM
2025-11-05 11:15:00+08 | 11 | 11:15 AM
2025-11-05 11:30:00+08 | 11 | 11:30 AM
2025-11-05 11:45:00+08 | 11 | 11:45 AM
2025-11-05 12:00:00+08 | 12 | 12:00 PM
2025-11-05 12:15:00+08 | 12 | 12:15 PM
2025-11-05 12:30:00+08 | 12 | 12:30 PM
2025-11-05 12:45:00+08 | 12 | 12:45 PM
2025-11-05 13:00:00+08 | 13 | 01:00 PM
2025-11-05 13:15:00+08 | 13 | 01:15 PM
2025-11-05 13:30:00+08 | 13 | 01:30 PM
2025-11-05 13:45:00+08 | 13 | 01:45 PM
2025-11-05 14:00:00+08 | 14 | 02:00 PM
2025-11-05 14:15:00+08 | 14 | 02:15 PM
2025-11-05 14:30:00+08 | 14 | 02:30 PM
2025-11-05 14:45:00+08 | 14 | 02:45 PM
2025-11-05 15:00:00+08 | 15 | 03:00 PM
2025-11-05 15:15:00+08 | 15 | 03:15 PM
2025-11-05 15:30:00+08 | 15 | 03:30 PM
2025-11-05 15:45:00+08 | 15 | 03:45 PM
2025-11-05 16:00:00+08 | 16 | 04:00 PM
2025-11-05 16:15:00+08 | 16 | 04:15 PM
2025-11-05 16:30:00+08 | 16 | 04:30 PM
2025-11-05 16:45:00+08 | 16 | 04:45 PM
2025-11-05 17:00:00+08 | 17 | 05:00 PM
2025-11-05 17:15:00+08 | 17 | 05:15 PM
2025-11-05 17:30:00+08 | 17 | 05:30 PM
2025-11-05 17:45:00+08 | 17 | 05:45 PM
2025-11-05 18:00:00+08 | 18 | 06:00 PM
2025-11-05 18:15:00+08 | 18 | 06:15 PM
2025-11-05 18:30:00+08 | 18 | 06:30 PM
2025-11-05 18:45:00+08 | 18 | 06:45 PM
2025-11-05 19:00:00+08 | 19 | 07:00 PM
2025-11-05 19:15:00+08 | 19 | 07:15 PM
2025-11-05 19:30:00+08 | 19 | 07:30 PM
2025-11-05 19:45:00+08 | 19 | 07:45 PM
2025-11-05 20:00:00+08 | 20 | 08:00 PM
2025-11-05 20:15:00+08 | 20 | 08:15 PM
2025-11-05 20:30:00+08 | 20 | 08:30 PM
2025-11-05 20:45:00+08 | 20 | 08:45 PM
2025-11-05 21:00:00+08 | 21 | 09:00 PM
2025-11-05 21:15:00+08 | 21 | 09:15 PM
2025-11-05 21:30:00+08 | 21 | 09:30 PM
2025-11-05 21:45:00+08 | 21 | 09:45 PM
2025-11-05 22:00:00+08 | 22 | 10:00 PM
2025-11-05 22:15:00+08 | 22 | 10:15 PM
2025-11-05 22:30:00+08 | 22 | 10:30 PM
2025-11-05 22:45:00+08 | 22 | 10:45 PM
2025-11-05 23:00:00+08 | 23 | 11:00 PM
2025-11-05 23:15:00+08 | 23 | 11:15 PM
2025-11-05 23:30:00+08 | 23 | 11:30 PM
2025-11-05 23:45:00+08 | 23 | 11:45 PM
2025-11-06 00:00:00+08 | 0 | 12:00 AM
2025-11-06 00:15:00+08 | 0 | 12:15 AM
2025-11-06 00:30:00+08 | 0 | 12:30 AM
2025-11-06 00:45:00+08 | 0 | 12:45 AM
2025-11-06 01:00:00+08 | 1 | 01:00 AM
2025-11-06 01:15:00+08 | 1 | 01:15 AM
2025-11-06 01:30:00+08 | 1 | 01:30 AM
2025-11-06 01:45:00+08 | 1 | 01:45 AM
2025-11-06 02:00:00+08 | 2 | 02:00 AM
2025-11-06 02:15:00+08 | 2 | 02:15 AM
2025-11-06 02:30:00+08 | 2 | 02:30 AM
2025-11-06 02:45:00+08 | 2 | 02:45 AM
2025-11-06 03:00:00+08 | 3 | 03:00 AM
(107 行记录)
PostgreSQL 中的字符串类型有:
这里
声明为
如果存储的字符串长度达不到
没有大小修饰的
PostgreSQL 的 TOAST(The Oversized-Attribute Storage Technique, 超大属性存储技术)是针对大尺寸数据(如长文本、二进制数据等)的存储优化机制, 当字段数据超过阈值时, 会自动将其压缩或拆分后存储到独立的 TOAST 表中, 主表仅保留引用指针, 既解决了单条记录存储容量受限问题, 又通过透明操作、多种存储策略和独立表设计, 平衡了存储效率与访问性能, 对 TEXT、BYTEA、JSONB 等可能存储大数据的类型尤为有效.
有时候, 为了保持跨平台应用的兼容性, 你需要使字符串类型的操作变得不区分大小写. 要实现此目标, 你需要重写那些区分大小写的比较运算符. 相比
常见的字符串操作包括:
使用
postgres@localhost:5436 tutorial# SELECT lpad('ab',4,'0') As ab_lpad,
tutorial-# rpad('ab',4,'0') As ab_rpad,
tutorial-# lpad('abcde',4,'0') As ab_lpad_trunc;
ab_lpad | ab_rpad | ab_lpad_trunc
---------+---------+---------------
00ab | ab00 | abcd
(1 行记录)
从上面看到, 如果字符串超过指定长度,
默认情况下,
下面的代码, 先使用
SELECT
a As a_before, trim(a) As a_trim, rtrim(a) As a_rtrim,
i As i_before, ltrim(i,'0') As i_ltrim_0, rtrim(i,'0') As i_rtrim_0, trim(i,'0') As i_trim_0
FROM (
SELECT repeat(' ',4) || i || repeat(' ',4) As a,
'0' || i As i
FROM generate_series(0,200,50) As i
) As x;
postgres@localhost:5436 tutorial# SELECT
tutorial-# a As a_before, trim(a) As a_trim, rtrim(a) As a_rtrim,
tutorial-# i As i_before, ltrim(i,'0') As i_ltrim_0, rtrim(i,'0') As i_rtrim_0, trim(i,'0') As i_trim_0
tutorial-# FROM (
tutorial(# SELECT repeat(' ',4) || i || repeat(' ',4) As a,
tutorial(# '0' || i As i
tutorial(# FROM generate_series(0,200,50) As i
tutorial(# ) As x;
a_before | a_trim | a_rtrim | i_before | i_ltrim_0 | i_rtrim_0 | i_trim_0
-------------+--------+---------+----------+-----------+-----------+----------
0 | 0 | 0 | 00 | | |
50 | 50 | 50 | 050 | 50 | 05 | 5
100 | 100 | 100 | 0100 | 100 | 01 | 1
150 | 150 | 150 | 0150 | 150 | 015 | 15
200 | 200 | 200 | 0200 | 200 | 02 | 2
(5 行记录)
postgres@localhost:5436 tutorial# SELECT repeat(' ',4) || i || repeat(' ',4) As a,
tutorial-# '0' || i As i
tutorial-# FROM generate_series(0,200,50) As i
tutorial-# ;
a | i
-------------+------
0 | 00
50 | 050
100 | 0100
150 | 0150
200 | 0200
(5 行记录)
如果将其中的空格换成其他符号, 比如 x 和 *. 则变为
postgres@localhost:5436 tutorial# SELECT repeat('x',4) || i || repeat('*',4) As a,
tutorial-# '0' || i As i
tutorial-# FROM generate_series(0,200,50) As i;
a | i
-------------+------
xxxx0**** | 00
xxxx50**** | 050
xxxx100**** | 0100
xxxx150**** | 0150
xxxx200**** | 0200
(5 行记录)
执行下面的查询.
SELECT
a As a_before, trim(a,'x') As a_trim, rtrim(a,'x') As a_rtrim,
i As i_before, ltrim(i,'0') As i_ltrim_0, rtrim(i,'0') As i_rtrim_0, trim(i,'0') As i_trim_0
FROM (
SELECT repeat('x',4) || i || repeat('x',4) As a,
'0' || i As i
FROM generate_series(0,200,50) As i
) As x;
postgres@localhost:5436 tutorial# SELECT
tutorial-# a As a_before, trim(a,'x') As a_trim, rtrim(a,'x') As a_rtrim,
tutorial-# i As i_before, ltrim(i,'0') As i_ltrim_0, rtrim(i,'0') As i_rtrim_0, trim(i,'0') As i_trim_0
tutorial-# FROM (
tutorial(# SELECT repeat('x',4) || i || repeat('x',4) As a,
tutorial(# '0' || i As i
tutorial(# FROM generate_series(0,200,50) As i
tutorial(# ) As x;
a_before | a_trim | a_rtrim | i_before | i_ltrim_0 | i_rtrim_0 | i_trim_0
-------------+--------+---------+----------+-----------+-----------+----------
xxxx0xxxx | 0 | xxxx0 | 00 | | |
xxxx50xxxx | 50 | xxxx50 | 050 | 50 | 05 | 5
xxxx100xxxx | 100 | xxxx100 | 0100 | 100 | 01 | 1
xxxx150xxxx | 150 | xxxx150 | 0150 | 150 | 015 | 15
xxxx200xxxx | 200 | xxxx200 | 0200 | 200 | 02 | 2
(5 行记录)
PostgreSQL 中有一些函数可以对字符串进行拆分操作, 比如
postgres@localhost:5436 tutorial# SELECT split_part('www.atzjg.net','.',2) As x;
x
-------
atzjg
(1 行记录)
分隔符也可以是一个字符串.
postgres@localhost:5436 tutorial# SELECT split_part('www.atzjg.net','zjg',2) As y;
y
------
.net
(1 行记录)
这里我们将
postgres@localhost:5436 tutorial# SELECT unnest(string_to_array('www.atzjg.net','.')) As z;
z
-------
www
atzjg
net
(3 行记录)
PostgreSQL 中的
其语法为
regexp_replace(string, pattern, replacement[,flags])
或者
regexp_replace(string, pattern, replacement, start[, N[, flags]])
postgres@localhost:5436 tutorial# SELECT REGEXP_REPLACE('apple pie', 'apple', 'banana');
regexp_replace
----------------
banana pie
(1 行记录)
postgres@localhost:5436 tutorial# SELECT REGEXP_REPLACE('apple pie', 'Apple', 'banana');
regexp_replace
----------------
apple pie
(1 行记录)
postgres@localhost:5436 tutorial# SELECT REGEXP_REPLACE('apple pie', 'Apple', 'banana', 'i');
regexp_replace
----------------
banana pie
(1 行记录)
postgres@localhost:5436 tutorial# SELECT REGEXP_REPLACE('123-456-7890', '(\d{3})-(\d{3})-(\d{4})', E'(\\1) \\2-\\3');
regexp_replace
----------------
(123) 456-7890
(1 行记录)
这里回溯引用如果不加
postgres@localhost:5436 tutorial# SELECT REGEXP_REPLACE('123-456-7890', '(\d{3})-(\d{3})-(\d{4})', '(\\1) \\2-\\3');
regexp_replace
----------------
(\1) \2-\3
(1 行记录)
可以看到, 这里的
如果
postgres@localhost:5436 tutorial# SELECT REGEXP_REPLACE('123-456-7890', '(\d{3})-(\d{3})-(\d{4})', E'(\1) \2-\3');
regexp_replace
------------------
(\x01) \x02-\x03
(1 行记录)
这里将
例. 下面的SQL语句返回的结果是什么?
SELECT regexp_replace('6197306254','([0-9]{3})([0-9]{3})([0-9]{4})',E'\(\\1\) \\2-\\3') As x;
postgres@localhost:5436 tutorial# SELECT regexp_replace(
tutorial(# '6197306254',
tutorial(# '([0-9]{3})([0-9]{3})([0-9]{4})',
tutorial(# E'\(\\1\) \\2-\\3'
tutorial(# ) As x;
x
----------------
(619) 730-6254
(1 行记录)
SELECT unnest(regexp_matches( 'Cell (619)852-5083. Casa 619-730-6254. Bésame mucho.',
E'[(]{0,1}[0-9]{3}[)-.]{0,1}[0-9]{3}[-.]{0,1}[0-9]{4}',
'g')
) As x;
同一个正则表达式可以有多种写法. 比如
可以将正则表达式与相似运算符
以下查询可以查出所有内嵌了电话号码的字符串.
SELECT description
FROM mytable
WHERE description ~ E'[(]{0,1}[0-9]{3}[)-.]{0,1}[0-9]{3}[-.]{0,1}[0-9]{4}';
PostgreSQL 支持两类字符型数据:
对于二进制字符串的存储, PostgreSQL 提供了
postgres@localhost:5436 tutorial# CREATE TABLE t1(b BYTEA); CREATE TABLE 时间:16.390 ms
postgres@localhost:5436 tutorial# INSERT INTO t1 VALUES (E'\000'); ERROR: invalid byte sequence for encoding "UTF8": 0x00 时间:11.965 ms postgres@localhost:5436 tutorial# INSERT INTO t1 VALUES (E'\\000'); INSERT 0 1 时间:10.865 ms
postgres@localhost:5436 tutorial# SELECT * FROM t1; b ------ \x00 (1 行记录)
用户也可以使用文本值替代
创建表
数组在 PostgreSQL 中扮演着重要的角色. 它在构造聚合函数、形成
在 PostgreSQL 中, 每种数据类型都有相应的以该类型为基础的数组类型. 如果你自定义了一个数据类型, 那么 PostgreSQL 会在后台自动为此类型创建一个数组类型. 例如,
PostgreSQL 支持多维数组. 例如,
primes INT[]; A INT[3][3]; remark TEXT[][];
目前 PostgreSQL 并不强制数组的长度.
如果是一维数组, 也可以使用 SQL 标准声明, SQL 语句如下:
PAY_BY_QUARTER INT ARRAY[5];
此种声明方式仍然不强制数组的长度.
插入数组元素时, 用大括号把数组元素括起来并且用逗号将它们分开.
postgres@localhost:5436 tutorial# CREATE TABLE tmp12(bt int[]);
CREATE TABLE
时间:101.550 ms
postgres@localhost:5436 tutorial# INSERT INTO tmp12 VALUES(2,3,5,7,11);
ERROR: INSERT has more expressions than target columns
第1行INSERT INTO tmp12 VALUES(2,3,5,7,11);
^
时间:72.312 ms
postgres@localhost:5436 tutorial# SELECT * FROM tmp12;
bt
----
(0 行记录)
postgres@localhost:5436 tutorial# INSERT INTO tmp12 VALUES({2,3,5,7,11});
ERROR: syntax error at or near "{"
第1行INSERT INTO tmp12 VALUES({2,3,5,7,11});
^
时间:19.539 ms
注意使用正确的语法.
postgres@localhost:5436 tutorial# INSERT INTO tmp12 VALUES('{2,3,5,7,11}');
INSERT 0 1
时间:40.035 ms
postgres@localhost:5436 tutorial# INSERT INTO tmp12 VALUES('{1,2,3},{3,5,7},{11,23,0}');
ERROR: malformed array literal: "{1,2,3},{3,5,7},{11,23,0}"
第1行INSERT INTO tmp12 VALUES('{1,2,3},{3,5,7},{11,23,0}');
^
描述: Junk after closing right brace.
时间:25.851 ms
postgres@localhost:5436 tutorial# INSERT INTO tmp12 VALUES('{{1,2,3},{3,5,7},{11,23,0}}');
INSERT 0 1
时间:38.187 ms
postgres@localhost:5436 tutorial# SELECT * FROM tmp12;
bt
-----------------------------
{2,3,5,7,11}
{{1,2,3},{3,5,7},{11,23,0}}
(2 行记录)
postgres@localhost:5436 tutorial# SELECT bt[2] FROM tmp12;
bt
------
3
NULL
(2 行记录)
postgres@localhost:5436 tutorial# SELECT bt[2][3] FROM tmp12;
bt
------
NULL
7
(2 行记录)
最基本的构造数组的方法就是一个个元素手动录入, 语法如下:
postgres@localhost:5436 tutorial# SELECT ARRAY[2001,2002,2003] As yrs;
yrs
------------------
{2001,2002,2003}
(1 行记录)
你可以把一个直接以字符串书写的数组转换为一个真正的数组, 语法如下:
postgres@localhost:5436 tutorial# SELECT '{Alex,Sonia}'::text[] As name, '{43,40}'::smallint[] As age;
name | age
--------------+---------
{Alex,Sonia} | {43,40}
(1 行记录)
可以用
postgres@localhost:5436 tutorial# SELECT string_to_array('数学分析,高等代数,常微分方程,实变函数',',') As 课程;
课程
-----------------------------------------
{数学分析,高等代数,常微分方程,实变函数}
(1 行记录)
SELECT array_agg(log_ts ORDER BY log_ts) As x FROM logs WHERE log_ts BETWEEN '2025-11-01'::timestamptz AND '2025-11-06'::timestamptz;
在 PostgreSQL 中, 可以用数组下标来引用数组元素, 与 C/C++ 不一样的是, PostgreSQL 的数组下标是从 1 开始. 并且, 如果越界访问一个数组并不会返回错误, 而是会得到一个空值(NULL).
postgres@localhost:5436 postgresql_book# SELECT fact_subcats[1] As primero, postgresql_book-# fact_subcats[array_upper(fact_subcats, 1)] As segundo postgresql_book-# FROM census.lu_fact_types; primero | segundo ---------+-------------------------------------------------- S01 | Number S02 | Number S03 | Number S04 | Number S05 | Number S06 | Number S07 | Number S08 | Number S09 | Number S10 | Number S11 | Number S12 | Number S13 | Number S14 | Number S15 | Number S16 | Number S17 | Number S18 | Number S19 | Number S20 | Number S21 | Number S22 | Number S23 | Number S24 | Number S25 | Number S26 | Number S27 | Number S28 | Number S29 | Number S30 | Number S31 | Number S32 | Number S33 | Number S34 | Number S35 | Number S36 | Number S37 | Number S38 | Number S39 | Number S40 | Number S41 | Number S42 | Number S43 | Number S44 | Number S45 | Number S46 | Number S47 | Number S48 | Number S49 | Number S50 | Number S51 | Number D001 | Total: D002 | Not Hispanic or Latino: D003 | White alone D004 | Black or African American alone D005 | American Indian and Alaska Native alone D006 | Asian alone D007 | Native Hawaiian and Other Pacific Islander alone D008 | Some Other Race alone D009 | Two or More Races D010 | Hispanic or Latino: D011 | White alone D012 | Black or African American alone D013 | American Indian and Alaska Native alone D014 | Asian alone D015 | Native Hawaiian and Other Pacific Islander alone D016 | Some Other Race alone D017 | Two or More Races (68 行记录)
这里
postgres@localhost:5436 postgresql_book# SELECT array_upper(fact_subcats, 1) FROM census.lu_fact_types;
array_upper
-------------
5
6
6
5
6
7
7
6
5
6
6
6
6
6
6
6
5
6
7
8
8
8
8
8
8
8
7
8
8
8
8
8
8
8
6
7
8
8
8
8
8
8
8
7
8
8
8
8
8
8
8
2
3
4
4
4
4
4
4
4
3
4
4
4
4
4
4
4
(68 行记录)
PostgreSQL 支持使用
下面列出表
postgres@localhost:5436 postgresql_book# SELECT fact_subcats[2:4] FROM census.lu_fact_types limit 10;
fact_subcats
---------------------------------------------------------------------
{"OCCUPANCY STATUS","Total housing units",HD01}
{"OCCUPANCY STATUS","Total housing units","Occupied housing units"}
{"OCCUPANCY STATUS","Total housing units","Vacant housing units"}
{TENURE,"Occupied housing units",HD01}
{TENURE,"Occupied housing units","Owner occupied"}
{TENURE,"Occupied housing units","Owner occupied"}
{TENURE,"Occupied housing units","Owner occupied"}
{TENURE,"Occupied housing units","Renter occupied"}
{"VACANCY STATUS","Vacant housing units",HD01}
{"VACANCY STATUS","Vacant housing units","For rent"}
(10 行记录)
注意
如果要将两个数组连接到一起, 则可以使用连接运算符
postgres@localhost:5436 postgresql_book# SELECT fact_subcats[1:2] || fact_subcats[3:4] FROM census.lu_fact_types limit 10;
?column?
-------------------------------------------------------------------------
{S01,"OCCUPANCY STATUS","Total housing units",HD01}
{S02,"OCCUPANCY STATUS","Total housing units","Occupied housing units"}
{S03,"OCCUPANCY STATUS","Total housing units","Vacant housing units"}
{S04,TENURE,"Occupied housing units",HD01}
{S05,TENURE,"Occupied housing units","Owner occupied"}
{S06,TENURE,"Occupied housing units","Owner occupied"}
{S07,TENURE,"Occupied housing units","Owner occupied"}
{S08,TENURE,"Occupied housing units","Renter occupied"}
{S09,"VACANCY STATUS","Vacant housing units",HD01}
{S10,"VACANCY STATUS","Vacant housing units","For rent"}
(10 行记录)
postgres@localhost:5436 postgresql_book# SELECT fact_subcats[3:4] || fact_subcats[1:2] FROM census.lu_fact_types limit 10;
?column?
-------------------------------------------------------------------------
{"Total housing units",HD01,S01,"OCCUPANCY STATUS"}
{"Total housing units","Occupied housing units",S02,"OCCUPANCY STATUS"}
{"Total housing units","Vacant housing units",S03,"OCCUPANCY STATUS"}
{"Occupied housing units",HD01,S04,TENURE}
{"Occupied housing units","Owner occupied",S05,TENURE}
{"Occupied housing units","Owner occupied",S06,TENURE}
{"Occupied housing units","Owner occupied",S07,TENURE}
{"Occupied housing units","Renter occupied",S08,TENURE}
{"Vacant housing units",HD01,S09,"VACANCY STATUS"}
{"Vacant housing units","For rent",S10,"VACANCY STATUS"}
(10 行记录)
postgres@localhost:5436 postgresql_book# SELECT unnest('{ABC,ACB,BAC,BCA,CAB,CBA}'::char(3)[]) As ABC;
abc
-----
ABC
ACB
BAC
BCA
CAB
CBA
(6 行记录)
你可以在一个
postgres@localhost:5436 postgresql_book# SELECT unnest('{南京,苏州,扬州}'::text[]) As city,
postgresql_book-# unnest('{025, 0512, 0514}'::smallint[]) As 区号;
city | 区号
------+------
南京 | 25
苏州 | 512
扬州 | 514
(3 行记录)
当然这里区号应该使用字符串类型比较好. 这里两个向量是对齐的, 从而组成一个二维矩阵.
尝试将其中一个数组中的元素删除.
postgres@localhost:5436 postgresql_book# SELECT unnest('{南京,苏州,扬州}'::text[]) As city,
postgresql_book-# unnest('{025, 0512}'::varchar[]) As 区号;
city | 区号
------+------
南京 | 025
苏州 | 0512
扬州 | 025
南京 | 0512
苏州 | 025
扬州 | 0512
(6 行记录)
PostgreSQL 9.4 版引入了一个多实参 unnest 函数. 该函数会在数组不平衡的位置置入空占位符(NULL).
postgres@localhost:5436 postgresql_book# SELECT * FROM unnest('{南京,苏州,扬州}'::text[], '{025,0512}'::varchar[]) As f(city, 区号);
city | 区号
------+------
南京 | 025
苏州 | 0512
扬州 | NULL
(3 行记录)
建表时如果指定了一个字段类型为 serial, 那么 PostgreSQL 会首先将其作为整型处理, 同时自动在该表所在 schema 中创建一个名为
在 PostgreSQL 中, 序列自身是一种数据库对象. 可以通过 pgAdmin 图形界面或者
PostgreSQL 有一个名为
postgres@localhost:5436 tutorial# SELECT x FROM generate_series(1,51,13) As x; x ---- 1 14 27 40 (4 行记录)
这里第三个参数是步长, 可以不指定, 在不指定的情况下默认是1. 另外, 生成的序列不会超出指定的区间.
有点类似于 Sowya 中的 printSeries()函数.
>> printSeries(13*n+1,n,0,5) 1,14,27,40,53,66, ------------------------
PostgreSQL 9.2 引入了区间数据类型(range types). 该数据类型可以定义一个值区间. 由于该类型的出现, 原本需要两个字段才能定义的区间现在仅使用一个字段即可.
PostgreSQL 为区间类型提供了很多配套的运算符和函数. 例如判定区间是否重叠, 判定某个值是否落在区间内, 以及将相邻的若干区间合并为一个完整的区间等.
PostgreSQL 对离散区间和连续区间是区别对待的. 整数类型或者日期类型的区间是离散区间, 因为区间内每一个值都是可以被枚举出来的.
对于离散区间, 很多写法认为是一样的. 比如
PostgreSQL 原生支持六种区间类型, 都是关于数字和日期时间型.
对于数字类型的区间来说, 如果区间的起点值(左端点值)或终点值(右端点值)未指定, 那么 PostgreSQL 会自动填入
理论上讲, 你可以将该
使用类型转换的方法来定义区间.
postgres@localhost:5436 tutorial# SELECT '[2025/11/07, 2025/11/08]'::daterange;
daterange
-------------------------
[2025-11-07,2025-11-09)
(1 行记录)
postgres@localhost:5436 tutorial# SELECT '[2025-11-07, 2025-11-09]'::daterange;
daterange
-------------------------
[2025-11-07,2025-11-10)
(1 行记录)
postgres@localhost:5436 tutorial# SELECT '[2025#11#07, 2025#11#09]'::daterange;
daterange
-------------------------
[2025-11-07,2025-11-10)
(1 行记录)
postgres@localhost:5436 tutorial# SELECT '(2025#11@07, 2025$11*09]'::daterange;
daterange
-------------------------
[2025-11-08,2025-11-10)
(1 行记录)
可以测试一下年月日间可以使用哪些分隔符.
postgres@localhost:5436 tutorial# SELECT '(,)'::int8range; int8range ----------- (,) (1 行记录) postgres@localhost:5436 tutorial# SELECT '[0,)'::int8range; int8range ----------- [0,) (1 行记录)
查询当前的时间戳.
postgres@localhost:5436 tutorial# SELECT CURRENT_TIMESTAMP;
now
----------------------------
2025-11-07 10:43:39.794+08
(1 行记录)
postgres@localhost:5436 tutorial# SELECT '(2025-11-07 10:43:39, 2025-12-09 12:43:59]'::tsrange;
tsrange
-----------------------------------------------
("2025-11-07 10:43:39","2025-12-09 12:43:59"]
(1 行记录)
这里定义了一个从
区间也可以使用范围的构造函数(constructor of range)来定义. 该函数的名称与区间类型的名称一致. 可以输入两个或三个实参. 示例如下:
postgres@localhost:5436 tutorial# SELECT daterange('2025-11-07','2025-12-19','[]');
daterange
-------------------------
[2025-11-07,2025-12-20)
(1 行记录)
第三个参数默认是
时间类型区间是很常用的. 假如你有一张雇佣表
postgres@localhost:5436 tutorial# CREATE TABLE employment( tutorial(# id serial PRIMARY KEY, tutorial(# employee VARCHAR(20), tutorial(# period daterange); CREATE TABLE 时间:87.593 ms
postgres@localhost:5436 tutorial# \d employment
数据表 "public.employment"
栏位 | 类型 | 校对规则 | 可空的 | 预设
----------+-----------------------+----------+----------+----------------------------------------
id | integer | | not null | nextval('employment_id_seq'::regclass)
employee | character varying(20) | | |
period | daterange | | |
索引:
"employment_pkey" PRIMARY KEY, btree (id)
在
postgres@localhost:5436 tutorial# CREATE INDEX idx_employment_period tutorial-# ON employment USING gist(period); CREATE INDEX 时间:16.857 ms
GiST(通用搜索树)索引是一种灵活的索引机制, 专为几何数据、文本搜索和范围查询等复杂数据类型设计. 它们通过支持自定义操作符和数据类型, 实现高效查询.
查看一下表
postgres@localhost:5436 tutorial# \d employment
数据表 "public.employment"
栏位 | 类型 | 校对规则 | 可空的 | 预设
----------+-----------------------+----------+----------+----------------------------------------
id | integer | | not null | nextval('employment_id_seq'::regclass)
employee | character varying(20) | | |
period | daterange | | |
索引:
"employment_pkey" PRIMARY KEY, btree (id)
"idx_employment_period" gist (period)
对
postgres@localhost:5436 tutorial# INSERT INTO employment (employee, period)
tutorial-# VALUES ('Alex', '[2012-04-24, infinity)'::daterange),
tutorial-# ('Sonia', '[2011-04-24, 2012-06-01)'::daterange),
tutorial-# ('Leo', '[2012-06-20, 2013-04-20)'::daterange),
tutorial-# ('Regina', '[2012-06-20, 2013-04-20)'::daterange);
INSERT 0 4
时间:42.800 ms
postgres@localhost:5436 tutorial# SELECT * FROM employment; id | employee | period ----+----------+------------------------- 1 | Alex | [2012-04-24,infinity) 2 | Sonia | [2011-04-24,2012-06-01) 3 | Leo | [2012-06-20,2013-04-20) 4 | Regina | [2012-06-20,2013-04-20) (4 行记录)
区间类型上用得最多的两个运算符是
顾名思义, 重叠运算符
查询谁与谁曾经同在公司工作过.
SELECT e1.employee, string_agg(DISTINCT e2.employee, ',' ORDER BY e2.employee) As colleagues FROM employment As e1 INNER JOIN employment As e2 ON e1.period && e2.period WHERE e1.employee <> e2.employee GROUP BY e1.employee;
postgres@localhost:5436 tutorial# SELECT e1.employee, string_agg(DISTINCT e2.employee, ',' ORDER BY e2.employee) As colleagues tutorial-# FROM employment As e1 INNER JOIN employment As e2 tutorial-# ON e1.period && e2.period tutorial-# WHERE e1.employee <> e2.employee tutorial-# GROUP BY e1.employee; employee | colleagues ----------+------------------ Alex | Leo,Regina,Sonia Leo | Alex,Regina Regina | Alex,Leo Sonia | Alex (4 行记录)
对于包含关系运算符
查询当前还在公司工作的雇员名单.
postgres@localhost:5436 tutorial# SELECT employee FROM employment tutorial-# WHERE period @> CURRENT_DATE tutorial-# GROUP BY employee; employee ---------- Alex (1 行记录)
JSON 数据类型及其相关操作函数是从 9.2 版开始支持的. JSON 是 Web 开发领域非常流行的一种数据类型, 它是 JavaScript 语言中的通用数据交换格式.
首先在 tutorial 数据库中新建一张表
postgres@localhost:5436 tutorial# CREATE TABLE families_j( tutorial(# id serial PRIMARY KEY, tutorial(# profile json); CREATE TABLE 时间:148.359 ms
postgres@localhost:5436 tutorial# \d families_j
数据表 "public.families_j"
栏位 | 类型 | 校对规则 | 可空的 | 预设
---------+---------+----------+----------+----------------------------------------
id | integer | | not null | nextval('families_j_id_seq'::regclass)
profile | json | | |
索引:
"families_j_pkey" PRIMARY KEY, btree (id)
插入一条 JSON 数据记录.
INSERT INTO families_j (profile) VALUES(
'{"name":"Gomez", "members":[
{"member":{"relation":"padre", "name":"Alex"}},
{"member":{"relation":"madre", "name":"Sonia"}},
{"member":{"relation":"hijo", "name":"Brandon"}},
{"member":{"relation":"hija", "name":"Azaleah"}}
]}');
注: 这里 padre、madre、hijo、hija 是西班牙语单词, 分别是 “父亲”、“母亲”、“儿子”、“女儿”的意思.
postgres@localhost:5436 tutorial# INSERT INTO families_j (profile) VALUES(
tutorial(# '{"name":"Gomez", "members":[
tutorial'# {"member":{"relation":"padre", "name":"Alex"}},
tutorial'# {"member":{"relation":"madre", "name":"Sonia"}},
tutorial'# {"member":{"relation":"hijo", "name":"Brandon"}},
tutorial'# {"member":{"relation":"hija", "name":"Azaleah"}}
tutorial'# ]}');
INSERT 0 1
时间:46.298 ms
postgres@localhost:5436 tutorial# SELECT * FROM families_j;
id | profile
----+---------------------------------------------------
1 | {"name":"Gomez", "members":[ +
| {"member":{"relation":"padre", "name":"Alex"}}, +
| {"member":{"relation":"madre", "name":"Sonia"}}, +
| {"member":{"relation":"hijo", "name":"Brandon"}},+
| {"member":{"relation":"hija", "name":"Azaleah"}} +
| ]}
(1 行记录)
使用
从
postgres@localhost:5436 tutorial# SELECT json_extract_path_text(profile, 'name') As family FROM families_j; family -------- Gomez (1 行记录)
postgres@localhost:5436 tutorial# SELECT profile->>'name' As family FROM families_j; family -------- Gomez (1 行记录)
使用
postgres@localhost:5436 tutorial# SELECT json_extract_path(profile, 'members') FROM families_j;
json_extract_path
---------------------------------------------------
[ +
{"member":{"relation":"padre", "name":"Alex"}}, +
{"member":{"relation":"madre", "name":"Sonia"}}, +
{"member":{"relation":"hijo", "name":"Brandon"}},+
{"member":{"relation":"hija", "name":"Azaleah"}} +
]
(1 行记录)
SELECT json_extract_path_text(profile, 'name') As family, -- 提取出家庭的名称, 以文本格式输出
json_extract_path_text( -- 提取出家庭成员的名称, 以文本格式输出
json_array_elements( -- 将家庭成员信息数组中的每个元素展开为独立的 JSON 对象.
json_extract_path(profile, 'members') -- 获取所有家庭成员的信息列表, 作为一个独立的 JSON 对象输出
), 'member', 'name'
) As member
FROM families_j;
postgres@localhost:5436 tutorial# SELECT json_extract_path_text(profile, 'name') As family, -- 提取出家庭的名称, 以文本格式输出 tutorial-# json_extract_path_text( -- 提取出家庭成员的名称, 以文本格式输出 tutorial(# json_array_elements( -- 将家庭成员信息数组中的每个元素展开为独立的 JSON 对象. tutorial(# json_extract_path(profile, 'members') -- 获取所有家庭成员的信息列表, 作为一个独立的 JSON 对象输出 tutorial(# ), 'member', 'name' tutorial(# ) As member tutorial-# FROM families_j; family | member --------+--------- Gomez | Alex Gomez | Sonia Gomez | Brandon Gomez | Azaleah (4 行记录)
运算符
SELECT profile->>'name' As family, json_array_elements((profile->'members'))
#>> '{member,name}'::text[] As member
FROM families_j;
这里
SELECT profile->>'name' As family, json_extract_path_text(json_array_elements((profile->'members')), 'member','name') As member FROM families_j;
postgres@localhost:5436 tutorial# SELECT profile->>'name' As family, json_array_elements((profile->'members'))
tutorial-# #>> '{member,name}'::text[] As member
tutorial-# FROM families_j;
family | member
--------+---------
Gomez | Alex
Gomez | Sonia
Gomez | Brandon
Gomez | Azaleah
(4 行记录)
postgres@localhost:5436 tutorial# SELECT profile->>'name' As family, json_extract_path_text(json_array_elements((profile->'members')), 'member','name') As member tutorial-# FROM families_j; family | member --------+--------- Gomez | Alex Gomez | Sonia Gomez | Brandon Gomez | Azaleah (4 行记录)
如果要把一个复合 JSON 对象剥离出来并传递给别的函数作进一步处理, 就需要使用
SELECT id, json_array_length(profile->'members') As numero, profile->'members'->0#>>'{member,name}'::text[] As primero
FROM families_j;
postgres@localhost:5436 tutorial# SELECT id, json_array_length(profile->'members') As numero, profile->'members'->0#>>'{member,name}'::text[] As primero
tutorial-# FROM families_j;
id | numero | primero
----+--------+---------
1 | 4 | Alex
(1 行记录)
通过这个例子可以看出,
PostgreSQL 中更多关于 json/jsonb 类型的函数和运算符可参考 https://www.postgresql.org/docs/current/functions-json.html
PostgreSQL 除了可以查询库中已有的 JSON 数据外, 还支持将别的数据类型转换为 JSON 类型.
将多条记录转换为单个 JSON 对象. (注意版本要求 >=9.3)
postgres@localhost:5436 tutorial# SELECT row_to_json(f) As x
tutorial-# FROM (SELECT id, profile->>'name' As name FROM families_j) As f;
x
-------------------------
{"id":1,"name":"Gomez"}
(1 行记录)
回顾一下
postgres@localhost:5436 tutorial# select * from families_j;
id | profile
----+---------------------------------------------------
1 | {"name":"Gomez", "members":[ +
| {"member":{"relation":"padre", "name":"Alex"}}, +
| {"member":{"relation":"madre", "name":"Sonia"}}, +
| {"member":{"relation":"hijo", "name":"Brandon"}},+
| {"member":{"relation":"hija", "name":"Azaleah"}} +
| ]}
(1 行记录)
如果要将
SELECT row_to_json(f) FROM families_j As f;
postgres@localhost:5436 tutorial# SELECT row_to_json(f) FROM families_j As f;
row_to_json
---------------------------------------------------
{"id":1,"profile":{"name":"Gomez", "members":[ +
{"member":{"relation":"padre", "name":"Alex"}}, +
{"member":{"relation":"madre", "name":"Sonia"}}, +
{"member":{"relation":"hijo", "name":"Brandon"}},+
{"member":{"relation":"hija", "name":"Azaleah"}} +
]}}
(1 行记录)
“查询时将一行记录作为单个字段输出”这种功能只有 PostgreSQL 才支持. 该功能对于创建复合 JSON 对象特别有用.
PostgreSQL 9.4 版本中引入了新的
为此, 我们新建一张名为
CREATE TABLE families_b(id serial PRIMARY KEY, profile jsonb);
postgres@localhost:5436 tutorial# CREATE TABLE families_b(id serial PRIMARY KEY, profile jsonb); CREATE TABLE 时间:103.746 ms
重复前面的过程, 插入一条 JSON 数据记录.
INSERT INTO families_b (profile) VALUES(
'{"name":"Gomez", "members":[
{"member":{"relation":"padre", "name":"Alex"}},
{"member":{"relation":"madre", "name":"Sonia"}},
{"member":{"relation":"hijo", "name":"Brandon"}},
{"member":{"relation":"hija", "name":"Azaleah"}}
]}');
postgres@localhost:5436 tutorial# INSERT INTO families_b (profile) VALUES(
tutorial(# '{"name":"Gomez", "members":[
tutorial'# {"member":{"relation":"padre", "name":"Alex"}},
tutorial'# {"member":{"relation":"madre", "name":"Sonia"}},
tutorial'# {"member":{"relation":"hijo", "name":"Brandon"}},
tutorial'# {"member":{"relation":"hija", "name":"Azaleah"}}
tutorial'# ]}');
INSERT 0 1
时间:16.052 ms
postgres@localhost:5436 tutorial# select * from families_b;
id | profile
----+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 | {"name": "Gomez", "members": [{"member": {"name": "Alex", "relation": "padre"}}, {"member": {"name": "Sonia", "relation": "madre"}}, {"member": {"name": "Brandon", "relation": "hijo"}}, {"member": {"name": "Azaleah", "relation": "hija"}}]}
(1 行记录)
可以看出,
而
postgres@localhost:5436 tutorial# select * from families_j;
id | profile
----+---------------------------------------------------
1 | {"name":"Gomez", "members":[ +
| {"member":{"relation":"padre", "name":"Alex"}}, +
| {"member":{"relation":"madre", "name":"Sonia"}}, +
| {"member":{"relation":"hijo", "name":"Brandon"}},+
| {"member":{"relation":"hija", "name":"Azaleah"}} +
| ]}
(1 行记录)
列出所有包含姓名为 "Alex" 的家庭成员的家庭.
SELECT profile->>'name' As family
FROM families_b
WHERE profile @> '{"members":[{"member":{"name":"Alex"} }]}';
postgres@localhost:5436 tutorial# SELECT profile->>'name' As family
tutorial-# FROM families_b
tutorial-# WHERE profile @> '{"members":[{"member":{"name":"Alex"} }]}';
family
--------
Gomez
(1 行记录)
如果再
CREATE INDEX idx_familes_jb_profile_gin ON families_b USING gin (profile);
未创建索引前,
postgres@localhost:5436 tutorial# \d families_b
数据表 "public.families_b"
栏位 | 类型 | 校对规则 | 可空的 | 预设
---------+---------+----------+----------+----------------------------------------
id | integer | | not null | nextval('families_b_id_seq'::regclass)
profile | jsonb | | |
索引:
"families_b_pkey" PRIMARY KEY, btree (id)
创建索引后,
postgres@localhost:5436 tutorial# CREATE INDEX idx_familes_jb_profile_gin ON families_b USING gin (profile);
CREATE INDEX
时间:39.720 ms
postgres@localhost:5436 tutorial# \d families_b
数据表 "public.families_b"
栏位 | 类型 | 校对规则 | 可空的 | 预设
---------+---------+----------+----------+----------------------------------------
id | integer | | not null | nextval('families_b_id_seq'::regclass)
profile | jsonb | | |
索引:
"families_b_pkey" PRIMARY KEY, btree (id)
"idx_familes_jb_profile_gin" gin (profile)
所有的高级关系型数据库(比如 IBM DB2、Oracle、SQL Server)都支持
XML 数据可以存入到类型为
注意, 即使
首先创建表
CREATE TABLE families(id serial PRIMARY KEY, profile xml);
postgres@localhost:5436 tutorial# CREATE TABLE families(id serial PRIMARY KEY, profile xml); CREATE TABLE 时间:41.888 ms
插入数据.
INSERT INTO families(profile)
VALUES(
'<family name="Gomez">
<member><relation>padre</relation><name>Alex</name></member>
<member><relation>madre</relation><name>Sonia</name></member>
<member><relation>hijo</relation><name>Brandon</name></member>
<member><relation>hija</relation><name>Azaleah</name></member>
</family>
'
);
postgres@localhost:5436 tutorial# CREATE TABLE families(id serial PRIMARY KEY, profile xml); CREATE TABLE 时间:41.888 ms postgres@localhost:5436 tutorial# INSERT INTO families(profile) tutorial-# VALUES( tutorial(# '<family name="Gomez"> tutorial'# <member><relation>padre</relation><name>Alex</name></member> tutorial'# <member><relation>madre</relation><name>Sonia</name></member> tutorial'# <member><relation>hijo</relation><name>Brandon</name></member> tutorial'# <member><relation>hija</relation><name>Azaleah</name></member> tutorial'# </family> tutorial'# ' tutorial(# ); INSERT 0 1 时间:35.583 ms
postgres@localhost:5436 tutorial# select * from families;
id | profile
----+---------------------------------------------------------------------------
1 | <family name="Gomez"> +
| <member><relation>padre</relation><name>Alex</name></member> +
| <member><relation>madre</relation><name>Sonia</name></member> +
| <member><relation>hijo</relation><name>Brandon</name></member>+
| <member><relation>hija</relation><name>Azaleah</name></member>+
| </family> +
|
(1 行记录)
;
可以对 XML 字段设置一个 check 约束以确保输入的 XML 数据都符合某种格式.
确保所有 XML 字段记录中都有至少一个 member 节点和一个 relation 节点. (有时也将节点写为结点.)
ALTER TABLE families ADD CONSTRAINT chk_has_relation
CHECK (xpath_exists('/family/member/relation', profile));
未添加约束前的
postgres@localhost:5436 tutorial# \d families
数据表 "public.families"
栏位 | 类型 | 校对规则 | 可空的 | 预设
---------+---------+----------+----------+--------------------------------------
id | integer | | not null | nextval('families_id_seq'::regclass)
profile | xml | | |
索引:
"families_pkey" PRIMARY KEY, btree (id)
执行上面的添加约束(ADD CONSTRAINT)语句.
postgres@localhost:5436 tutorial# ALTER TABLE families ADD CONSTRAINT chk_has_relation
tutorial-# CHECK (xpath_exists('/family/member/relation', profile));
ALTER TABLE
时间:10.940 ms
postgres@localhost:5436 tutorial# \d families
数据表 "public.families"
栏位 | 类型 | 校对规则 | 可空的 | 预设
---------+---------+----------+----------+--------------------------------------
id | integer | | not null | nextval('families_id_seq'::regclass)
profile | xml | | |
索引:
"families_pkey" PRIMARY KEY, btree (id)
检查约束限制
"chk_has_relation" CHECK (xpath_exists('/family/member/relation'::text, profile))
此时下面的插入语句是否违反约束.
INSERT INTO families (profile) VALUES('<family name="HsuObe"></family>');
postgres@localhost:5436 tutorial# INSERT INTO families (profile) VALUES('<family name="HsuObe"></family>');
ERROR: new row for relation "families" violates check constraint "chk_has_relation"
描述: Failing row contains (2, <family name="HsuObe"></family>).
时间:22.147 ms
如果需要基于 DTD 或者 XSD 对 XML 数据进行格式检查, 你需要自行编写格式检查函数, 然后将此函数放到 check 约束中调用.
SELECT family,
(xpath('/member/relation/text()',f))[1]::text As relation,
(xpath('/member/name/text()',f))[1]::text As mem_name
FROM
(SELECT (xpath('/family/@name', profile))[1]::text As family,
unnest(xpath('/family/member', profile)) As f
FROM families) x;
postgres@localhost:5436 tutorial# SELECT family,
tutorial-# (xpath('/member/relation/text()',f))[1]::text As relation,
tutorial-# (xpath('/member/name/text()',f))[1]::text As mem_name
tutorial-# FROM
tutorial-# (SELECT (xpath('/family/@name', profile))[1]::text As family,
tutorial(# unnest(xpath('/family/member', profile)) As f
tutorial(# FROM families) x;
family | relation | mem_name
--------+----------+----------
Gomez | padre | Alex
Gomez | madre | Sonia
Gomez | hijo | Brandon
Gomez | hija | Azaleah
(4 行记录)
PostgreSQL 中所有表都有一个对应的自定义数据类型. 在建表时, PostgreSQL 会自动创建一个与表结构完全相同的自定义数据类型, 称为
可以在建表时指定某字段为
建立关于矩阵的嵌套表. 每个 $n$ 阶方阵都有 $n$ 个特征值.
CREATE TABLE eigenvalues(id integer PRIMARY KEY);
尽管通过建表就可以创建复合数据类型, 但有时我们需要从头开始构建自己的数据类型.
构建复数类型.
CREATE TYPE complex_number AS ( r double precision, i double precision );
postgres@localhost:5436 tutorial# CREATE TYPE complex_number AS ( tutorial(# r double precision, tutorial(# i double precision tutorial(# ); CREATE TYPE 时间:29.416 ms
使用
postgres@localhost:5436 tutorial# \dT
数据类型列表
架构模式 | 名称 | 描述
----------+----------------+------
public | complex_number | NULL
(1 行记录)
将此类型作为字段类型定义使用. 例如下面建立一张电路表(circuits). 字段
CREATE TABLE circuits( circuit_id serial PRIMARY KEY, ac_volt complex_number );
postgres@localhost:5436 tutorial# CREATE TABLE circuits( tutorial(# circuit_id serial PRIMARY KEY, tutorial(# ac_volt complex_number tutorial(# ); CREATE TABLE 时间:19.125 ms
插入数据.
INSERT INTO circuits(ac_volt) VALUES((1,2)::complex_number);
postgres@localhost:5436 tutorial# INSERT INTO circuits(ac_volt) VALUES((1,2)::complex_number); INSERT 0 1 时间:16.660 ms
对这个表的查询可以使用下面的语句:
SELECT circuit_id, (ac_volt).* FROM circuits;
postgres@localhost:5436 tutorial# SELECT circuit_id, (ac_volt).* FROM circuits;
circuit_id | r | i
------------+---+---
1 | 1 | 2
(1 行记录)
或者
SELECT circuit_id, (ac_volt).r, (ac_volt).i FROM circuits;
postgres@localhost:5436 tutorial# SELECT circuit_id, (ac_volt).r, (ac_volt).i FROM circuits;
circuit_id | r | i
------------+---+---
1 | 1 | 2
(1 行记录)
上面
对于构建的自定义数据类型, 可以为其创建相应的函数和运算符.
每个运算符都有一个底层实现函数, 该函数需要一个或两个实参, 运算符就是这个函数的符号化别名.
运算符不仅仅是其底层实现函数的别名, 它还可以提供一些可以帮助规划器更好工作的优化信息. 规划器借助这些信息可以判定如何使用索引, 如何以最低的成本访问数据, 以及哪些运算符表达式是等价的.
创建运算符的第一步是创建其底层实现函数. 我们先使用
postgres@localhost:5436 tutorial# \df
函数列表
架构模式 | 名称 | 结果数据类型 | 参数数据类型 | 类型
----------+------+--------------+--------------+------
(0 行记录)
下面为
CREATE OR REPLACE FUNCTION add(complex_number, complex_number)
RETURNS complex_number AS
$$
SELECT (
(COALESCE(($1).r,0)+COALESCE(($2).r,0)),
(COALESCE(($1).i,0)+COALESCE(($2).i,0))
)::complex_number;
$$
language sql;
postgres@localhost:5436 tutorial# CREATE OR REPLACE FUNCTION add(complex_number, complex_number) tutorial-# RETURNS complex_number AS tutorial-# $$ tutorial$# SELECT ( tutorial$# (COALESCE(($1).r,0)+COALESCE(($2).r,0)), tutorial$# (COALESCE(($1).i,0)+COALESCE(($2).i,0)) tutorial$# )::complex_number; tutorial$# $$ tutorial-# language sql; CREATE FUNCTION 时间:27.808 ms
使用
postgres@localhost:5436 tutorial# \df
函数列表
架构模式 | 名称 | 结果数据类型 | 参数数据类型 | 类型
----------+------+----------------+--------------------------------+------
public | add | complex_number | complex_number, complex_number | 函数
(1 行记录)
为
postgres@localhost:5436 tutorial# \do
运算子列表
架构模式 | 名称 | 左参数类型 | 右参数类型 | 结果类型 | 描述
----------+------+------------+------------+----------+------
(0 行记录)
输入下面的
CREATE OPERATOR +( PROCEDURE = add, LEFTARG = complex_number, RIGHTARG = complex_number, COMMUTATOR = + );
postgres@localhost:5436 tutorial# CREATE OPERATOR +( tutorial(# PROCEDURE = add, tutorial(# LEFTARG = complex_number, tutorial(# RIGHTARG = complex_number, tutorial(# COMMUTATOR = + tutorial(# ); CREATE OPERATOR 时间:36.946 ms
然后再使用
postgres@localhost:5436 tutorial# \do
运算子列表
架构模式 | 名称 | 左参数类型 | 右参数类型 | 结果类型 | 描述
----------+------+----------------+----------------+----------------+------
public | + | complex_number | complex_number | complex_number | NULL
(1 行记录)
使用
postgres@localhost:5436 tutorial# \do+
运算子列表
架构模式 | 名称 | 左参数类型 | 右参数类型 | 结果类型 | 函数 | 描述
----------+------+----------------+----------------+----------------+------+------
public | + | complex_number | complex_number | complex_number | add | NULL
(1 行记录)
测试一下
SELECT (1,2)::complex_number + (3,-10)::complex_number;
postgres@localhost:5436 tutorial# SELECT (1,2)::complex_number + (3,-10)::complex_number As result; result -------- (4,-8) (1 行记录)
上面的
首先我们要重载
CREATE OR REPLACE FUNCTION add(complex_number, integer)
RETURNS complex_number AS
$$
SELECT (
(COALESCE(($1).r,0)+COALESCE(($2),0)),
COALESCE(($1).i,0)
)::complex_number;
$$
language sql;
postgres@localhost:5436 tutorial# CREATE OR REPLACE FUNCTION add(complex_number, integer) tutorial-# RETURNS complex_number AS tutorial-# $$ tutorial$# SELECT ( tutorial$# (COALESCE(($1).r,0)+COALESCE(($2),0)), tutorial$# COALESCE(($1).i,0) tutorial$# )::complex_number; tutorial$# $$ tutorial-# language sql; CREATE FUNCTION 时间:23.598 ms
postgres@localhost:5436 tutorial# \df
函数列表
架构模式 | 名称 | 结果数据类型 | 参数数据类型 | 类型
----------+------+----------------+--------------------------------+------
public | add | complex_number | complex_number, complex_number | 函数
public | add | complex_number | complex_number, integer | 函数
(2 行记录)
然后重载
postgres@localhost:5436 tutorial# SELECT (1,2)::complex_number +3;
ERROR: operator does not exist: complex_number + integer
第1行SELECT (1,2)::complex_number +3;
^
提示: No operator matches the given name and argument type(s). You might need to add explicit type casts.
时间:26.860 ms
postgres@localhost:5436 tutorial# SELECT (1,2)::complex_number +3::integer;
ERROR: operator does not exist: complex_number + integer
第1行SELECT (1,2)::complex_number +3::integer;
^
提示: No operator matches the given name and argument type(s). You might need to add explicit type casts.
时间:7.600 ms
输入下面的 CREATE OPERATOR 语句, 注意右参数设置为
CREATE OPERATOR +( PROCEDURE = add, LEFTARG = complex_number, RIGHTARG = integer, COMMUTATOR = + );
postgres@localhost:5436 tutorial# CREATE OPERATOR +(
tutorial(# PROCEDURE = add,
tutorial(# LEFTARG = complex_number,
tutorial(# RIGHTARG = integer,
tutorial(# COMMUTATOR = +
tutorial(# );
CREATE OPERATOR
时间:39.932 ms
postgres@localhost:5436 tutorial# \do
运算子列表
架构模式 | 名称 | 左参数类型 | 右参数类型 | 结果类型 | 描述
----------+------+----------------+----------------+----------------+------
public | + | complex_number | complex_number | complex_number | NULL
public | + | complex_number | integer | complex_number | NULL
public | + | integer | complex_number | - | NULL
(3 行记录)
可见
重新执行 $1+2i$ 加上 $3$ 的运算.
postgres@localhost:5436 tutorial# SELECT (1,2)::complex_number +3; ?column? ---------- (4,2) (1 行记录)
https://chl.cn/shijian/