Postgres中的数组和操作

Postgers数据库,在很早(应该是从7.4版本)就提供了数组类型。虽然数组在大部分信息系统、开发平台和语言中都有非常广泛的应用,但不知什么原因,好像在数据库系统中的应用并不广泛。同时SQL标准,应该也还没有包括数组类型和相关的操作方式,相当于是一个pg自己的扩展功能。

这是因为觉得数据库本身就已经可以很好的操作数据集合,不需要这个特性了吗?还是SQL语句处理起数组来比较麻烦? 这些问题笔者没有答案,也希望有谁能够很好的解释一下。

不管如何,PG是提供数组相关的功能和操作方式的,本文就来探讨一下它的实现思路、方式,还有一些使用的经验和场景笔者也想分享一些,希望能对大家的工作有所帮助。

数组操作的常规

笔者理解的数组,就是一个数据结构,它包括数据或者信息的集合,并具备以下特性:

  • 有序,可以按照在数组中的位置来访问数组元素
  • 有限,数组成员数量,一般情况下在创建时就已经确定
  • 数组的成员,一般情况下数据类型是相同的(这应该是底层实现和内存存储方式的一个限制)

所以,一套数组操作的方法,应当包括:

  • 获取长度(length)
  • 可以按照索引或位置访问元素(at)
  • 在特定位置增加数组元素(add)

其实对于数组的基本操作,以上三点应该就够了。其他操作一般都是基本操作的组合和扩展,当然直接提供相关的功能可以大幅度简化实现代码。这些方法通常包括:

  • 构建子数组,数组分割(subarray, slice)
  • 数组合并(concat)
  • 查找特定元素在数组中的存在性和位置(contains, indexOf)

再考虑比较高级的特性和操作可以包括:

  • 使用指定分隔符将数组转换为字符串,或者从字符串转为数组(split, join)
  • 在一个数组中,支持不同类型的数据
  • 数组排序,并且可以定义排序规则(sort)
  • 随机化,打乱数组中的元素
  • 多维数组和多维数组展平(flat)

Postgre数组类型

可以想象,postgres对数组类型设置是比较严格的。在使用之前,比如定义表结构的时候,需要指定数组要使用的数据类型。而且,在进行相关操作的时候,要时刻注意相关的数组的操作,也是相同的数据类型,如果不一致,可能需要进行显示的数据类型转换。

pg的很多基本数据类型都支持数组,使用和转换的方式也比较简单。比如下面就可以为数据表增加一个字段,它是一个数组,基本类型为字符类型:

alter table tb_test add column flags char[];

Postgres数组操作

Postgres主要通过提供一系列函数来支撑数组数据的操作。完整的信息参见其官方手册:

www.postgresql.org/docs/16/fun...

下面我们简单列举其中一些比较重要的:

  • 构造数组和数组的值

在pg中,可以使用Array符号或者指定的格式来构造数组,如果编写SQL,也可以使用类似的方法。我们下面构造一个虚拟的临时表,后面的示例会用到:

sql 复制代码
with T(id, vals) as (values
(1, array[1,2,3,5]),
(2, array[1,2]),
(3, array[6,2,3]),
(4, '{5,1,3}'::int[])
)
  • 数组长度

使用array_length函数:

select 1, array_length(vals,1) from T;

注意有第二个参数1,这个方法是支持多维数组的,1表示取第一维的数组长度。另外如果数组长度为0,方法会返回null而不是0。

  • 检索数组元素

可以像一般的编程语言类似,使用方括号索引的方式,获得特定索引位置的元素:

select id, vals[0], vals[1], vals[9] from T;

注意,pg的数组索引是从1开始的,但如果超界,不会出错,而是返回Null。

前面是使用索引来查询元素的值,也可以使用array_position通过值来查找元素在数组中的位置:

select id, array_position(vals, 2) from T;

如果有多个位置,可以使用array_positions来查询。

  • 数组扩展

可以使用"||"运算符来扩展数组,并且选择扩展方向:

select id, 11 || vals || array[41,42] v2 from T;

也可以使用array_append,array_cat方法来扩展和合并数组。

  • 数组赋值

在update语句中,可以直接通过索引修改数组中某个元素的值:

update T set(vals[1,], vals[2]) = (vals[2], vals[1]);

还提供了array_replace方法,可以将数组中的值,替换成其他的值(支持重复值):

select array_replace(ARRAY[1,2,5,4,5], 5, 3);

  • 移除元素

可以使用array_remove方法,通过指定值,来从数组中移除元素:

select id, array_remove(vals, 2) from T;

此方法的返回结果是去除了特定值的新数组。

比如我们有一个需求是,要修改一些记录,在数组字段中增加一个值,但又不希望有重复值(原来的数组中可能已经包含了这个值)。这里有两个思路。一是在修改(通常用append)前使用值得存在性检查,过滤掉已包含值得记录;还有就是可以先从数组中移除然后扩展就可以了,如:

select array_remove(ARRAY[2,3,7], 3) || 3, array_remove(ARRAY[2,4,7], 3) || 3;

显然,前者的操作效率要高一些,但后者有个好处是修改的值总是在数组的最后。

  • 子数组

有趣的是,postgres没有按照惯例,提供类似array_subarray的函数。要获取一个数组的一部分,可以直接使用上下索引:

select id,vals[2:4], vals[ array_length(vals,1)-1 : array_length(vals,1)+1 ] from T;

注意方括号里面索引的分隔是冒号,第二个参数也是索引位置而非长度, 还可以使用计算量作为索引位置。

  • 存在性和包含性条件查询

这个经常用在Where子句的条件判断操作中。

sql 复制代码
-- 单一值可以使用any函数
select * from T where 1 = any(vals);
select * from T where not (1 = any(vals));

-- 多值可以使用包含符号<@, 包含所有值
select * from T where array[1] <@ vals;
select * from T where array[1,2] <@ vals;

-- 包含任意值
select * from T where array[1,2] && vals;

-- 判断两个数组完全相同
select array[1,2] = array[1,2], array[1,2] = array[2,1], array[1,2] = array[1,2,3];
  • 其他函数

下面的函数也是pg提供的,但感觉使用和场景比较少。

array_fill: 数组填充

array_dims, array_ndims : 数组维度信息

array_sample: 数组随机采样

array_shuffle: 数组随机化

cardinality: 多维数组元素总数

trim_array: 裁剪数组,去除后面n个元素

array_lower, array_upper: 数组上下边界

其他特别操作

作为数据库系统,pg提供了一些常规数组以外的和数据库相关的操作方式:

  • unnest (反嵌套)

使用unnest方法,可以将数组转换为记录:

select id, unnest(vals) from T;

unnest还可以通过传入多个数组作为参数,扩展成一个行的集合。

select * from unnest(ARRAY[1,2],ARRAY['foo','bar','baz']);

  • array_agg (数组聚合)

也可以将记录的值聚合后转换为数组:

select array_agg(id) idlist from T;

这种操作涉及到聚合计算,所以可能需要group by 关键字。

  • distinct(去重)

postgres好像没有直接提供数组去重的函数,可能需要使用组合unnest和disctinct记录操作等方式,达成类似的效果。

SELECT array_agg(DISTINCT x) FROM unnest(array[1,3,2,2,1]) as x;

  • array_to_string (数组转为字符串)

可以将数组转换为字符串(不需要聚合,无条件执行),可以指定连接字符:

select id, array_to_string(vals,',') from T;

  • string_to_array (字符串转换为数组)

字符串转换为数组操作,需要指定连接字符,可选指定替换为空的字符串:

select string_to_array('6,3,4,7,2',',');

应用场景

Postgres提供了数组类型,就提供了更多的数据操作的灵活性和可能性。原来一些不太容易实现的应用场景,可能基于数组,就可以简单而明了的进行处理了。通常是可以简化数据结构和表的设计,也简化相关信息的操作。

笔者在业务系统开发中,就将这个特性应用到了一些场景中:

  • 基于数组元素的多对多信息关联

笔者另有一篇博文,比较细致的讨论了这种使用方式。简单来讲,就是将关联的ID放在主表的数组里面,然后使用数组的any方法作为关联查询条件,这样可以省去那个关联中间表,简化管理和操作。

  • 时间标识

信息项目管理中,经常使用一个字段来记录某个操作的时间,如果需要记录多种操作或者状态修改的时间,可以使用数组,就不用增加字段。数组的类型还可以直接使用时间转换的整数,更加简单。

  • 属性标签

如可以使用字符,或者字符串数组,来保存记录的标签。这个标签数组是可以灵活扩展和管理的,就不需要增加额外的字段,或者专门的标签记录表了。

  • 固定的序列项目管理

如果有一些项目信息是一个固定序列的话,可以考虑采用数组形式。一个典型的例子就是学生的考试成绩可以使用数组记录,这样可以简化在不同的情况下如不同的考试科目,来修改成绩数据表的结构。

相关推荐
陈随易6 分钟前
Bun v1.2.16发布,内存优化,兼容提升,体验增强
前端·后端·程序员
GetcharZp8 分钟前
「Golang黑科技」RobotGo自动化神器,鼠标键盘控制、屏幕截图、全局监听全解析!
后端·go
程序员岳焱10 分钟前
Java 与 MySQL 性能优化:Linux服务器上MySQL性能指标解读与监控方法
linux·后端·mysql
Xy91010 分钟前
从代码角度拆解Apptrace的一键拉起
javascript·数据库
坚持学习永不言弃10 分钟前
【底层】Volatile的理解
后端
高级bug工程师11 分钟前
💡 从业务中抽象通用能力:我如何封装了一个实用的 Spring Boot Starter 框架
后端
武子康13 分钟前
大数据-12-Hive 基本介绍 下载安装配置 MariaDB安装 3台云服务Hadoop集群 架构图 对比SQL HQL
后端
chenquan13 分钟前
ArkFlow 流处理引擎 0.4.0-rc1 发布
人工智能·后端·github
易安说AI25 分钟前
教你在cursor中无限使用Claude3.7模型,手慢无!
后端