9.16. JSON 函数和操作符 #
本节描述:
用于处理和创建 JSON 数据的函数和操作符
SQL/JSON 路径语言
为了在 SQL 环境中为 JSON 数据类型提供原生支持,PostgreSQL 实现了 SQL/JSON 数据模型。该模型由项序列组成。每个项可以保存 SQL 标量值,以及额外的 SQL/JSON null 值,还可以保存使用 JSON 数组和对象的复合数据结构。该模型是 JSON 规范 RFC 7159 中隐含数据模型的形式化表示。
SQL/JSON 允许你将 JSON 数据与常规 SQL 数据一同处理,并提供事务支持,包括:
将 JSON 数据上传到数据库,并将其作为字符或二进制字符串存储在常规 SQL 列中。
从关系数据生成 JSON 对象和数组。
使用 SQL/JSON 查询函数和 SQL/JSON 路径语言表达式查询 JSON 数据。
要了解有关 SQL/JSON 标准的更多信息,请参阅[sqltr-19075-6]。有关 PostgreSQL 中支持的 JSON 类型的详细信息,见第 8.14 节。
9.16.1. 处理和创建 JSON 数据 #
表 9.45 显示了可用于 JSON 数据类型的操作符(参见第 8.14 节)。此外,表 9.1 中给出的常规比较操作符也可用于 jsonb,但不适用于 json。比较操作符遵循 B-树操作的排序规则,详见第 8.14.4 节。另请参阅第 9.21 节,了解聚合函数 json_agg 如何将记录值聚合为 JSON,以及聚合函数 json_object_agg 如何将值对聚合为 JSON 对象,还有它们对应的 jsonb 函数,jsonb_agg 和 jsonb_object_agg。
表 9.45. json 和 jsonb 操作符
操作符 描述 示例 |
|---|
提取 JSON 数组的第
|
用给定的键提取 JSON 对象字段。
|
提取 JSON 数组的第
|
用给定的键提取 JSON 对象字段,作为
|
提取指定路径下的 JSON 子对象,路径元素可以是字段键或数组索引。
|
将指定路径上的 JSON 子对象提取为
|
注意
如果 JSON 输入没有匹配请求的正确结构,字段/元素/路径提取操作符会返回 NULL,而不是失败;例如,如果不存在这样的键或数组元素。
还有一些操作符仅适用于 jsonb,如表 9.46 所示。第 8.14.4 节介绍了如何使用这些操作符有效地搜索已建立索引的 jsonb 数据。
表 9.46. 附加的 jsonb 操作符
操作符 描述 示例 |
|---|
第一个 JSON 值是否包含第二个?(请参见第 8.14.3 节以了解包含的详细信息。)
|
第二个 JSON 中是否包含第一个 JSON 值?
|
文本字符串是否作为 JSON 值中的顶级键或数组元素存在?
|
text 数组中的字符串是否至少有一个作为顶级键或数组元素存在?
|
text 数组中的所有字符串都作为顶级键或数组元素存在吗?
|
连接两个
要将一个数组作为单个元素追加到另一个数组中,请先在它外面再包装一层数组,例如:
|
从 JSON 对象中删除键(以及它的值),或从 JSON 数组中删除匹配的字符串值。
|
从左操作数中删除所有匹配的键或数组元素。
|
删除具有指定索引的数组元素(负整数从末尾计数)。如果 JSON 值不是数组,则抛出错误。
|
删除指定路径上的字段或数组元素,路径元素可以是字段键或数组索引。
|
JSON 路径是否为指定的 JSON 值返回任何项?
|
返回指定 JSON 值的 JSON 路径谓词检查的结果。只考虑结果的第一项。如果结果不是布尔值,则返回
|
注意
jsonpath 操作符@?和@@会抑制以下错误:缺少对象字段或数组元素、JSON 项类型不符合预期,以及日期时间和数值错误。下文介绍的 jsonpath 相关函数也可以设置为抑制这些类型的错误。在搜索结构各异的 JSON 文档集合时,这一行为可能很有用。
表 9.47 列出了可用于构造 json 和 jsonb 值的函数。表中的某些函数具有 RETURNING 子句,用于指定返回的数据类型。该类型必须是 json、jsonb、bytea、字符串类型(text、char 或 varchar),或者可以转换为 json 的类型。默认返回 json 类型。
表 9.47. JSON 创建函数
函数 描述 示例 |
|---|
|
将任何 SQL 值转换为
|
将 SQL 数组转换为 JSON 数组。该行为与
|
从一系列
|
将 SQL 复合值转换为 JSON 对象。该行为与
|
根据可变参数列表构建可能异构类型的 JSON 数组。每个参数都按照
|
根据可变参数列表构建一个 JSON 对象。按照惯例,参数列表由交替的键和值组成。键参数会被强制转换为文本;值参数则按照
|
根据给定的所有键值对构造 JSON 对象;如果没有给出键值对,则构造空对象。
|
|
从 text 数组构造 JSON 对象。该数组必须是一维且包含偶数个成员,此时将成员按交替的键/值对处理;或者是二维数组,且每个内部数组恰好有两个元素,将这两个元素作为一个键/值对。所有值都转换为 JSON 字符串。
|
这种形式的
|
表 9.48 详细介绍了用于测试 JSON 的 SQL/JSON 功能。
表 9.48. SQL/JSON 测试函数
表 9.49 显示可用于处理 json 和 jsonb 值的函数。
表 9.49. JSON 处理函数
函数 描述 示例 |
|---|
将顶级 JSON 数组展开为一组 JSON 值。
value ----------- 1 true [2,false]
|
将顶级 JSON 数组展开为一组
value ----------- foo bar
|
返回顶层 JSON 数组中的元素数量。
|
将顶级 JSON 对象展开为一组键/值对。
key | value -----+------- a | "foo" b | "bar"
|
将顶级 JSON 对象展开为一组键/值对。返回的
key | value -----+------- a | foo b | bar
|
在指定路径下提取 JSON 子对象。(这在功能上相当于
|
将指定路径上的 JSON 子对象提取为
|
返回顶级 JSON 对象中的键集合。
json_object_keys ------------------ f1 f2
|
将顶级 JSON 对象展开为一行,其复合类型与 要将 JSON 值转换为输出列的 SQL 类型,需要依次应用以下规则:
虽然下面的示例使用的是常量 JSON 值,但典型用法是在查询的
a | b | c
---+-----------+-------------
1 | {2,"a b"} | (4,"a b c")
|
将由对象组成的顶级 JSON 数组展开为一组行,其复合类型与
a | b ---+--- 1 | 2 3 | 4
|
将顶级 JSON 对象展开为具有由
a | b | c | d | r
---+---------+---------+---+---------------
1 | [1,2,3] | {1,2,3} | | (123,"a b c")
|
将顶级 JSON 对象数组展开为一组由
a | b ---+----- 1 | foo 2 |
|
返回
|
如果
|
返回插入
|
递归删除给定 JSON 值中所有值为 null 的对象字段。不是对象字段的 null 值保持不变。
|
检查 JSON 路径是否返回指定 JSON 值的任何项。如果指定了
|
返回指定 JSON 值的 JSON 路径谓词检查的结果。只有结果的第一项被考虑在内。如果结果不是布尔值,则返回
|
为指定的 JSON 值返回由 JSON 路径返回的所有 JSON 项。可选的
jsonb_path_query ------------------ 2 3 4
|
以 JSON 数组的形式返回由 JSON 路径为指定的 JSON 值返回的所有 JSON 项。可选的
|
为指定的 JSON 值返回由 JSON 路径返回的第一个 JSON 项。如果没有结果则返回
|
这些函数的作用类似于上面描述的没有
|
|
将给定的 JSON 值转换为经过美化并带有缩进的文本。
[
{
"f1": 1,
"f2": null
},
2
]
|
|
以文本字符串形式返回顶级 JSON 值的类型。可能的类型有
|
9.16.2. SQL/JSON 路径语言 #
SQL/JSON 路径表达式指定了要从 JSON 数据中检索的项目,类似于 SQL 访问 XML 时使用的 XPath 表达式。在 PostgreSQL 中,路径表达式作为 jsonpath 数据类型实现,可以使用第 8.14.7 节中描述的任何元素。
JSON 查询函数和操作符会将给定的路径表达式传递给路径引擎进行求值。如果表达式与被查询的 JSON 数据匹配,则返回相应的 JSON 项或项集。路径表达式是用 SQL/JSON 路径语言编写的,也可以包括算术表达式和函数。
路径表达式由 jsonpath 数据类型允许的元素序列组成。路径表达式通常从左向右求值,但你可以使用圆括号来更改操作的顺序。如果计算成功,将生成一系列 JSON 项,并将计算结果返回到 JSON 查询函数,该函数将完成指定的计算。
要引用正在查询的 JSON 值(上下文项),请在路径表达式中使用$变量。它后面可以跟一个或多个访问操作符,沿 JSON 结构逐层向下获取上下文项的子项。每个后续操作符都处理上一步求值的结果。
例如,假设你有一些你想要解析的来自 GPS 跟踪器的 JSON 数据,例如:
{
"track": {
"segments": [
{
"location": [ 47.763, 13.4034 ],
"start time": "2018-10-14 10:05:14",
"HR": 73
},
{
"location": [ 47.706, 13.2635 ],
"start time": "2018-10-14 10:39:21",
"HR": 135
}
]
}
}
要获取可用的轨迹片段,需要使用. 访问操作符,逐层访问外围的 JSON 对象:key
$.track.segments
要检索数组的内容,通常使用[*] 操作符。例如,下面的路径将返回所有可用轨迹段的位置坐标:
$.track.segments[*].location
要仅返回第一个片段的坐标,可以在[] 访问操作符中指定相应下标。注意,JSON 数组下标从 0 开始:
$.track.segments[0].location
每一步路径求值的结果都可以使用一个或多个 jsonpath 操作符和方法处理,它们列于第 9.16.2.2 节。每个方法名之前都必须有一个点。例如,可以获取数组的大小:
$.track.segments.size()
有关在路径表达式中使用 jsonpath 操作符和方法的更多示例,见下文第 9.16.2.2 节。
定义路径时,还可以使用一个或多个过滤表达式,其作用类似于 SQL 中的 WHERE 子句。过滤表达式以问号开头,并在圆括号中提供条件:
? (condition)
过滤表达式必须在它们应该应用的路径求值步骤之后指定。该步骤的结果会经过过滤,只保留满足给定条件的项。SQL/JSON 定义了三值逻辑,因此条件可以是 true、false 或 unknown。unknown 值起到与 SQL NULL 相同的作用,可以使用 is unknown 谓词进行测试。进一步的路径求值步骤只使用过滤表达式返回 true 的那些项。
可以在过滤表达式中使用的函数和操作符罗列在表 9.51 中。在一个过滤表达式中,@变量表示被过滤的值(也就是说,前面路径步骤的一个结果)。你可以在 @后面写访问操作符来检索组件项。
例如,假设你想检索所有高于 130 的心率值。可以用下面的表达式实现:
$.track.segments[*].HR ? (@ > 130)
为了获得具有这些值的轨迹段的开始时间,必须先过滤掉不相关的轨迹段,再返回开始时间。因此,过滤表达式应用于上一步,条件中使用的路径也不同:
$.track.segments[*] ? (@.HR > 130)."start time"
如有需要,可以依次使用多个过滤表达式。例如,下面的表达式选择位置坐标符合要求且心率较高的所有轨迹段的开始时间:
$.track.segments[*] ? (@.location[1] < 13.4) ? (@.HR > 130)."start time"
也可以在不同嵌套层级使用过滤表达式。下面的示例先按位置筛选所有轨迹段,再返回这些轨迹段中的高心率值(如果存在):
$.track.segments[*] ? (@.location[1] < 13.4).HR ? (@ > 130)
还可以将过滤表达式相互嵌套:
$.track ? (exists(@.segments[*] ? (@.HR > 130))).segments.size()
如果轨迹包含任何具有高心率值的轨迹段,该表达式返回轨迹的大小,否则返回空序列。
PostgreSQL 对 SQL/JSON 路径语言的实现与 SQL/JSON 标准有以下偏差:
路径表达式可以是布尔谓词,尽管 SQL/JSON 标准只允许在过滤器中使用谓词。这对于实现
@@操作符是必要的。例如,下面的jsonpath表达式在 PostgreSQL 中是有效的:$.track.segments[*].HR < 70
对于
like_regex过滤器中使用的正则表达式模式,其解释方式存在一些细微差异,详见第 9.16.2.3 节。
9.16.2.1. 严格模式与宽松模式 #
当查询 JSON 数据时,路径表达式可能与实际的 JSON 数据结构不匹配。试图访问不存在的对象成员或数组元素会导致结构错误。SQL/JSON 路径表达式有两种处理结构错误的模式:
宽松模式(lax,默认)—路径引擎隐式地将查询的数据适配到指定的路径。任何剩余的结构错误都将被抑制并转换为空 SQL/JSON 序列。
严格模式(strict)— 如果发生结构错误,就会引发错误。
如果 JSON 数据不符合预期模式,宽松模式有助于使 JSON 文档结构与路径表达式相匹配。如果操作数不满足某个操作的要求,可以在执行该操作之前自动将其包装为 SQL/JSON 数组,或通过将其元素转换为 SQL/JSON 序列来解包。此外,在宽松模式下,比较操作符会自动解包其操作数,因此可以直接比较 SQL/JSON 数组。大小为 1 的数组被视为等于其唯一元素。以下情况不会自动解包:
路径表达式包含
type()或size()方法,它们分别返回类型和数组中的元素数量。查询的 JSON 数据包含嵌套的数组。在这种情况下,只有最外层的数组被解包,而所有内部数组保持不变。因此,隐式解包在每个路径求值步骤中只能向下进行一级。
例如,查询上面列出的 GPS 数据时,使用宽松模式可以不必关心它将轨迹段存储为数组这一细节:
lax $.track.segments.location
在严格模式下,指定路径必须与所查询 JSON 文档的结构完全匹配,才能返回 SQL/JSON 项,因此使用此路径表达式会导致错误。要得到与宽松模式相同的结果,必须显式解包 segments 数组:
strict $.track.segments[*].location
该.** 访问器在宽松模式下可能产生出人意料的结果。例如,下面的查询会选出每个 HR 值两次:
lax $.**.HR
这是因为.** 访问器既选择 segments 数组,又选择其每个元素,而.HR 访问器在宽松模式下会自动解包数组。为避免意外结果,建议将.** 访问器仅用于严格模式。下面的查询只选出每个 HR 值一次:
strict $.**.HR
9.16.2.2. SQL/JSON 路径操作符和方法 #
表 9.50 显示了 jsonpath 中可用的操作符和方法。请注意,虽然一元操作符和方法可以应用于由前一个路径步骤产生的多个值,二元操作符(加法等)只能应用于单个值。
表 9.50. jsonpath 操作符和方法
操作符/方法 描述 示例 |
|---|
加法
|
一元加号(无操作);与加法不同,这个可以迭代多个值
|
减法
|
取负;与减法不同,可以遍历多个值。
|
乘法
|
除法
|
取模(余数)
|
JSON 项的类型(参见
|
JSON 项的大小(数组元素的数量,如果不是数组则为 1)
|
从 JSON 数字或字符串转换过来的近似浮点数
|
大于或等于给定数字的最接近的整数
|
小于或等于给定数字的最近整数
|
给定数字的绝对值
|
从字符串转换过来的日期/时间值
|
使用指定的
|
对象的键值对,表示为包含三个字段的对象数组:
|
注意
datetime() 和 datetime( 方法的结果类型可以是 template)date、timetz、time、timestamptz 或 timestamp。这两个方法都动态地确定它们的结果类型。
datetime() 方法依次尝试将其输入字符串与 date、timetz、time、timestamptz 和 timestamp 的 ISO 格式进行匹配。它在第一个匹配格式时停止,并返回相应数据类型的值。
datetime( 方法根据所提供的模板字符串中使用的字段确定结果类型。
template)
datetime() 和 datetime( 方法使用与 template)to_timestamp SQL 函数相同的解析规则(参见第 9.8 节),但有三个例外。首先,这些方法不允许不匹配的模板模式。其次,模板字符串中只允许以下分隔符:减号、句点、斜杠、逗号、撇号、分号、冒号和空格。第三,模板字符串中的分隔符必须与输入字符串完全匹配。
如果需要比较不同的日期/时间类型,则应用隐式类型转换。date 值可以转换为 timestamp 或 timestamptz,timestamp 可以转换为 timestamptz,time 可以转换为 timetz。但是,除了第一个转换外,其他所有转换都依赖于当前 TimeZone 设置,因此只能在时区感知的 jsonpath 函数中执行。
表 9.51 显示了可用的过滤表达式元素。
表 9.51. jsonpath 过滤表达式元素
谓词/值 描述 示例 |
|---|
相等比较(这个,和其他比较操作符,适用于所有 JSON 标量值)
|
不相等比较
|
小于比较
|
小于或等于比较
|
大于比较
|
大于或等于比较
|
JSON 常量
|
JSON 常量
|
JSON 常量
|
布尔 AND
|
布尔 OR
|
布尔 NOT
|
测试布尔条件是否为
|
测试第一个操作数是否与第二个操作数给出的正则表达式匹配;可以用一串
|
测试第二个操作数是否为第一个操作数的初始子串。
|
测试路径表达式是否至少匹配一个 SQL/JSON 项。如果路径表达式会导致错误,则返回
|
9.16.2.3. SQL/JSON 正则表达式 #
SQL/JSON 路径表达式允许使用 like_regex 过滤器,将文本与正则表达式进行匹配。例如,以下 SQL/JSON 路径查询会以不区分大小写的方式,匹配数组中所有以英语元音字母开头的字符串:
$[*] ? (@ like_regex "^[aeiou]" flag "i")
可选的 flag 字符串可以包括一个或多个字符 i 用于不区分大小写的匹配,m 允许^和$在换行时匹配,s 允许.匹配换行符,q 将整个模式按字面量处理(将行为简化为一个简单的子字符串匹配)。
SQL/JSON 标准借用了来自 LIKE_REGEX 操作符的正则表达式定义,其使用了 XQuery 标准。PostgreSQL 目前不支持 LIKE_REGEX 操作符。因此,like_regex 过滤器是使用第 9.7.3 节中描述的 POSIX 正则表达式引擎来实现的。这导致了与标准 SQL/JSON 行为的各种细微差异,这些差异列在第 9.7.3.8 节中。但是请注意,这里描述的标志字母不兼容并不适用于 SQL/JSON,因为 SQL/JSON 会将 XQuery 标志字母转换为 POSIX 引擎所预期的形式。
请记住,like_regex 的模式参数是一个 JSON 路径字符串字面量,根据第 8.14.7 节给出的规则编写。这特别意味着在正则表达式中要使用的任何反斜杠都必须加倍。例如,匹配根文档中仅包含数字的字符串值:
$.* ? (@ like_regex "^\\d+$")
报告文档问题
阅读 上游文档. 通过 PostgreSQL 文档反馈表单.