本文是《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的功能特性的理解和认知。