PostgreSQL技术问答21 - ORDINALITY

本文是《PostgreSQL技术问答》系列文章中的一篇。关于这个系列的由来,可以参阅开篇文章:

《PostgreSQL技术问答00 - Why Postgres》

文章的编号只是一个标识,在系列中没有明确的逻辑顺序和意义。读者进行阅读时,不用太关注这个方面。

本文主要讨论的内容是一个不太起眼,但有点意思的内容: ORDINALITY,即序数。

什么是Ordinality

Ordinality,中文意为"序数",就是一个序列数。 笔者看到这个东西,是在Postgres官方技术文档的Select的标准语法范式中:

sql 复制代码
where from_item can be one of:
   ...
    [ LATERAL ] ROWS FROM( function_name ( [ argument [, ...] ] ) [ AS ( column_definition [, ...] ) ] [, ...] )
    [ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
    ...

可以看到,WITH ORDINALITY是一个可选的"子句",它可以用于在输出结果记录集时,自动增加一个序号列,默认的列名称就是ordinality,但这是可以设置一个别名的。

这里顺便提一下,Postgres的技术文档中,有很多类似的东西,稍微深入研究一下,可能会有意想不到的收获。从另外一个方面,也可以看到Postgres系统的强大和灵活。

如何使用序数

看到有这个功能,笔者原来以为ordinality可以如row_number()一样使用,作为给记录集的一个编号列来使用。因为使用比较高级的窗口函数来实现这个需求,总是感觉有点"杀鸡用牛刀"。

但经过研究发现好像没那么理想。这个子句的使用的场景是非常受限的。就是只能和所谓的"表函数, table function"结合起来使用。下面是一个简单的例子:

sql 复制代码
SELECT * from unnest(ARRAY[2, 3, 5, 8, 13]) WITH ordinality alias (nfb, iorder);
 nfb | iorder 
-----+--------
   2 |      1
   3 |      2
   5 |      3
   8 |      4
  13 |      5
(5 rows)

// 错误写法
with O as( values (2),(3), (5))
SELECT * from O with ordinality;
ERROR:  syntax error at or near "with"
LINE 4: SELECT * from O with ordinality;

在示例中,这个with ordinality子句需要和unnest方法结合起来使用。表面可以在结果集上附带一个序号列。这里使用的unnest就是一种表函数。

除了表函数的结果之外,Ordinality不能直接用于普通的记录集如表、视图、CTE和子查询等等,代码中也可以看到使用CTE时,会报一个语法错误。这个限制,让Ordinality的使用场景就不够多了。笔者也不好想象这个功能,可以被合理而有效的用在什么地方,解决何种何类的问题。

什么是表函数 Table Function

虽然笔者对Orindality的研究结果有点失望,但这个过程中,还是涉及并发现了一个新的概念: Table Function(表函数)。

表函数就是一种函数,它能够返回一个结果集,就像一个逻辑表一样。Postgres内置有一个表函数,包括:

  • unnest: 用于将数组展开为一组行

  • generate_series(start, stop, step):生成一个数量,从start开始,到stop结束,步进为step(默认1)

  • generate_subscripts(array, [dimension]):生成一个包含数组下标的表。dimension是可选的指定维度

  • json_each(json):将JSON 对象展开为键/值对

  • json_array_elements(json):将 JSON 数组展开为一组行。

  • xmltable(xpath, columns):从 XML 文档中提取数据,并将其展示为表格形式。

有趣的是,在Postgres中,逻辑上表函数的计算结果是一个记录集,但它在很多情况下,也是可以直接写在字段表达式列表中的,如:

sql 复制代码
// 作为记录集
 select * from generate_series(1,100,2);

// 作为字段
select generate_series(1,100,2), name from users;

// 使用ordinality, 只能在逻辑源表中
select * from  generate_series(1, (select count(1) from gcode)) with ordinality;

其实,笔者理解,即使是作为字段的表现形式,处理方式仍然是一个表,只不过是和from中的原表做一个笛卡尔积而已。这里在应用中,经常用到的表函数,恐怕就只有unnest、generate_series,最多再加上json_array_elements了。

可以自定义表函数吗

前面我们看到了,ordinality的限制就是需要配合表函数使用,但PG内置的表函数就那么几个,如果没有合适的功能和表函数可以使用,如何处理呢?

答案是可以自己定义和使用表函数。下面是一个简单的例子:

sql 复制代码
-- 创建表函数
CREATE OR REPLACE FUNCTION get_stations(TEXT)
RETURNS TABLE(station TEXT, name TEXT, atime smallint) AS $$
BEGIN
        RETURN QUERY 
    with T as (select unnest(stations) scode, unnest(stimes) atime from tl_trains where tcode = $1) 
        select T.scode::TEXT, N.name::TEXT station, T.atime::smallint from T join tl_stations N using (scode);
END;
$$ LANGUAGE plpgsql;

-- 直接使用
select get_stations('K2618');
   get_stations   
------------------
 (LZJ,兰州,818)
 (WEJ,渭源,97)
 (HDJ,哈达铺,167)
 (INJ,陇南,249)
 (GYW,广元,360)
 (CDW,成都,678)
(6 rows)

-- 作为记录集,With Ordinality
select * from get_stations('K2618') With ordinality;

 station |  name  | atime | ordinality 
---------+--------+-------+------------
 LZJ     | 兰州   |   818 |          1
 WEJ     | 渭源   |    97 |          2
 HDJ     | 哈达铺 |   167 |          3
 INJ     | 陇南   |   249 |          4
 GYW     | 广元   |   360 |          5
 CDW     | 成都   |   678 |          6

这里面可以看到一些要点:

  • 定义表函数,可以使用Return Query,直接返回一个查询结果
  • 这个结果,并不是简单的记录集结构,而是一个行的列表
  • 可以带有和使用参数
  • 注意查询字段的数据类型

最后,定义好这个表函数,就可以使用With Ordinality来附带行号字段了。当然,这个过程还是有点复杂,在实际应用中,没有特别的原因和理由,应该不会有人这样实际操作。这里,只是从技术研讨的角度,提出一个解决和处理的方案,并且帮助理解相关的技术特性而已。

小结

本文探讨了PG查询中With Codinality子句相关的内容。包括基本概念和特性,子句的应用和限制,然后探讨了相关的表函数和自定义表函数方面的内容,可以帮助开发者扩展对于PostgreSQL的功能特性的理解和认知。

相关推荐
.周周19 分钟前
mysql数据表时间字段自动存时间
数据库·mysql
听忆.30 分钟前
RabbitMQ消息可靠性等机制详解(精细版三)
java·开发语言·spring boot·后端·spring·java-ee·rabbitmq
肖哥弹架构36 分钟前
策略模式(Strategy Pattern):电商平台的优惠券系统实战案例分析
前端·后端·程序员
泡芙冰淇淋ya40 分钟前
【Spring Boot】spring boot环境搭建
java·spring boot·后端
阳爱铭1 小时前
GitHub:现代软件开发的协作平台
驱动开发·后端·中间件·面试·架构·github·学习方法
哈哈,名字可以改1 小时前
权限表1111111
数据库
passion更好1 小时前
【Pyhton】读取寄存器数据到MySQL数据库
数据库
baozongwi1 小时前
ctfshow sqli-libs web561--web568
数据库·经验分享·python·sql·mysql·web安全
孤傲小二~阿沐1 小时前
PostgreSQL的学习心得和知识总结(一百四十七)|深入理解PostgreSQL数据库之transaction chain的使用和实现
数据库·postgresql
肖哥弹架构2 小时前
组合模式(Composite Pattern): 在线教育平台课程管理实战案例分析
前端·后端·程序员