9.16. JSON 函数和操作符 #
本节描述:
用于处理和创建 JSON 数据的函数和操作符
SQL/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 值返回任何项?(这只适用于 SQL 标准 JSON 路径表达式,不适用于谓词检查表达式,因为后者总是返回一个值。)
|
返回对指定 JSON 值进行 JSON 路径谓词检查的结果。(这只适用于谓词检查表达式,不适用于 SQL 标准 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 字符串。
|
这种形式的
|
|
将给定的
|
|
将给定的 SQL 标量值转换为 JSON 标量值。如果输入为 NULL,则返回 SQL 空值。如果输入是数字或布尔值,则返回相应的 JSON 数字或布尔值。对于其他值,则返回 JSON 字符串。
|
|
将 SQL/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")
|
用于测试
jsonb_populate_record_valid ----------------------------- f (1 row)
ERROR: value too long for type character(2)
jsonb_populate_record_valid ----------------------------- t (1 row)
a ---- aa (1 row)
|
将由对象组成的顶级 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 值返回任何项。(这只对 SQL 标准 JSON 路径表达式有用,而不适用于谓词检查表达式,因为后者总是返回一个值。)如果指定了
|
返回指定 JSON 值的 JSON 路径谓词检查的 SQL 布尔结果。(这只对谓词检查表达式有用,而不适用于 SQL 标准 JSON 路径表达式,因为如果路径结果不是单个布尔值,它将失败或返回
|
为指定的 JSON 值返回由 JSON 路径返回的所有 JSON 项。对于 SQL 标准 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 数据中检索的项,类似于访问 XML 内容时使用的 XPath 表达式。在 PostgreSQL 中,路径表达式作为 jsonpath 数据类型实现,可以使用第 8.14.7 节中描述的任何元素。
JSON 查询函数和操作符会将给定的路径表达式传递给路径引擎进行求值。如果表达式与被查询的 JSON 数据匹配,则返回相应的 JSON 项或项集。如果没有匹配项,则根据所用函数返回 NULL、false 或抛出错误。路径表达式是用 SQL/JSON 路径语言编写的,也可以包括算术表达式和函数。
路径表达式由 jsonpath 数据类型允许的元素序列组成。路径表达式通常从左向右求值,但你可以使用圆括号来更改操作的顺序。如果计算成功,将生成一系列 JSON 项,并将计算结果返回到 JSON 查询函数,该函数将完成指定的计算。
要引用正在查询的 JSON 值(上下文项),请在路径表达式中使用$变量。路径的第一个元素必须始终是$。它后面可以跟一个或多个访问操作符,沿 JSON 结构逐层向下获取上下文项的子项。每个访问操作符都处理上一步求值的结果,为每个输入项产生零个、一个或多个输出项。
例如,假设你有一些来自 GPS 跟踪器、想要解析的 JSON 数据,如下所示:
SELECT '{
"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
}
]
}
}' AS json \gset
(可以将上述示例复制粘贴到 psql 中,为后续示例做好准备。此后,psql 会将:'json' 展开为一个包含该 JSON 值、且已正确加上引号的字符串常量。)
要获取可用的轨迹段,需要使用. 访问操作符,逐层访问外围的 JSON 对象:
key
=>select jsonb_path_query(:'json', '$.track.segments');jsonb_path_query ------------------------------------------------------------------------------------------------------------------------------------------------------------------- [{"HR": 73, "location": [47.763, 13.4034], "start time": "2018-10-14 10:05:14"}, {"HR": 135, "location": [47.706, 13.2635], "start time": "2018-10-14 10:39:21"}]
要检索数组的内容,通常使用[*] 操作符。例如,下面的路径将返回所有可用轨迹段的位置坐标:
=>select jsonb_path_query(:'json', '$.track.segments[*].location');jsonb_path_query ------------------- [47.763, 13.4034] [47.706, 13.2635]
这里从整个 JSON 输入值($)开始,随后 .track 访问器选择与对象键 "track" 关联的 JSON 对象,.segments 访问器再选择该对象中与键 "segments" 关联的 JSON 数组,[*] 访问器接着选择该数组的每个元素(产生一系列项),最后 .location 访问器选择这些对象中分别与键 "location" 关联的 JSON 数组。在这个示例中,每个对象都有一个 "location" 键;如果某个对象没有这个键,.location 访问器就不会为该输入项产生任何输出。
要只返回第一个段的坐标,可以在[] 访问操作符中指定相应的下标。请记住,JSON 数组索引从 0 开始:
=>select jsonb_path_query(:'json', '$.track.segments[0].location');jsonb_path_query ------------------- [47.763, 13.4034]
每个路径求值步骤的结果可以由第 9.16.2.3 节中列出的一个或多个 jsonpath 操作符和方法来处理。每个方法名之前必须有一个点。例如,你可以得到一个数组的大小:
=>select jsonb_path_query(:'json', '$.track.segments.size()');jsonb_path_query ------------------ 2
在路径表达式中使用 jsonpath 操作符和方法的更多示例见下面第 9.16.2.3 节。
路径还可以包含过滤表达式,其作用类似于 SQL 中的 WHERE 子句。过滤表达式以问号开头,并在圆括号中提供条件:
? (condition)
过滤表达式必须在它们应该应用的路径求值步骤之后指定。该步骤的结果会经过过滤,只保留满足给定条件的项。SQL/JSON 定义了三值逻辑,因此条件可以是 true、false 或 unknown。unknown 值起到与 SQL NULL 相同的作用,可以使用 is unknown 谓词进行测试。进一步的路径求值步骤只使用过滤表达式返回 true 的那些项。
可以在过滤表达式中使用的函数和操作符罗列在表 9.51 中。在一个过滤表达式中,@变量表示被过滤的值(也就是说,前面路径步骤的一个结果)。你可以在 @后面写访问操作符来检索组件项。
例如,假设你想要检索所有高于 130 的心率值。你可以使用下面的表达式来实现这一点:
=>select jsonb_path_query(:'json', '$.track.segments[*].HR ? (@ > 130)');jsonb_path_query ------------------ 135
为了获得具有这些值的轨迹段的开始时间,必须先过滤掉不相关的轨迹段,再返回开始时间。因此,过滤表达式应用于上一步,条件中使用的路径也不同:
=>select jsonb_path_query(:'json', '$.track.segments[*] ? (@.HR > 130)."start time"');jsonb_path_query ----------------------- "2018-10-14 10:39:21"
如有需要,可以依次使用多个过滤表达式。例如,下面的表达式选择位置坐标符合要求且心率较高的所有轨迹段的开始时间:
=>select jsonb_path_query(:'json', '$.track.segments[*] ? (@.location[1] < 13.4) ? (@.HR > 130)."start time"');jsonb_path_query ----------------------- "2018-10-14 10:39:21"
也可以在不同嵌套层级使用过滤表达式。下面的示例先按位置筛选所有轨迹段,再返回这些轨迹段中的高心率值(如果存在):
=>select jsonb_path_query(:'json', '$.track.segments[*] ? (@.location[1] < 13.4).HR ? (@ > 130)');jsonb_path_query ------------------ 135
还可以将过滤表达式相互嵌套。如果轨迹包含任何具有高心率值的轨迹段,下面的示例返回轨迹的大小,否则返回空序列:
=>select jsonb_path_query(:'json', '$.track ? (exists(@.segments[*] ? (@.HR > 130))).segments.size()');jsonb_path_query ------------------ 2
9.16.2.1. 与 SQL 标准的偏差 #
PostgreSQL 对 SQL/JSON 路径语言的实现与 SQL/JSON 标准有以下偏差。
9.16.2.1.1. 布尔谓词检查表达式 #
作为对 SQL 标准的扩展,PostgreSQL 的路径表达式可以是布尔谓词,而 SQL 标准只允许在过滤器内部使用谓词。SQL 标准路径表达式返回被查询 JSON 值中的相关元素,而谓词检查表达式返回该谓词的单个三值 jsonb 结果:true、false 或 null。例如,下面是一个符合 SQL 标准的过滤表达式:
=>select jsonb_path_query(:'json', '$.track.segments ?(@[*].HR > 130)');jsonb_path_query --------------------------------------------------------------------------------- {"HR": 135, "location": [47.706, 13.2635], "start time": "2018-10-14 10:39:21"}
类似的谓词检查表达式则会直接返回 true,表示存在匹配项:
=>select jsonb_path_query(:'json', '$.track.segments[*].HR > 130');jsonb_path_query ------------------ true
注意
谓词检查表达式是 @@ 操作符(以及 jsonb_path_match 函数)所必需的,不应与 @? 操作符(或 jsonb_path_exists 函数)一起使用。
9.16.2.1.2. 正则表达式解释 #
对于 like_regex 过滤器中使用的正则表达式模式,其解释方式存在一些细微差异,详见第 9.16.2.4 节。
9.16.2.2. 严格模式与宽松模式 #
当查询 JSON 数据时,路径表达式可能与实际的 JSON 数据结构不匹配。试图访问不存在的对象成员或数组元素会导致结构错误。SQL/JSON 路径表达式有两种处理结构错误的模式:
宽松模式(lax,默认)—路径引擎隐式地将被查询的数据适配到指定路径。凡是无法按下文所述方式修复的结构错误都会被抑制,产生的结果是不匹配。
严格模式(strict)— 如果发生结构错误,就会引发错误。
如果 JSON 数据不符合预期模式,宽松模式有助于使 JSON 文档结构与路径表达式相匹配。如果操作数不满足某个操作的要求,可以在执行该操作之前自动将其包装为 SQL/JSON 数组,或通过将其元素转换为 SQL/JSON 序列来解包。此外,在宽松模式下,比较操作符会自动解包其操作数,因此可以直接比较 SQL/JSON 数组。大小为 1 的数组被视为等于其唯一元素。以下情况不会自动解包:
路径表达式包含
type()或size()方法,它们分别返回类型和数组中的元素数量。查询的 JSON 数据包含嵌套的数组。在这种情况下,只有最外层的数组被解包,而所有内部数组保持不变。因此,隐式解包在每个路径求值步骤中只能向下进行一级。
例如,查询上面列出的 GPS 数据时,使用宽松模式就无需关注轨迹段存储在数组中这一细节:
=>select jsonb_path_query(:'json', 'lax $.track.segments.location');jsonb_path_query ------------------- [47.763, 13.4034] [47.706, 13.2635]
在严格模式下,指定的路径必须与查询的 JSON 文档的结构完全匹配,因此使用该路径表达式会导致错误。
=>select jsonb_path_query(:'json', 'strict $.track.segments.location');ERROR: jsonpath member accessor can only be applied to an object
要得到与宽松模式相同的结果,你必须显式地解包 segments 数组:
=>select jsonb_path_query(:'json', 'strict $.track.segments[*].location');jsonb_path_query ------------------- [47.763, 13.4034] [47.706, 13.2635]
.** 访问器在宽松模式下可能产生意外结果。例如,下面的查询选择每个 HR 值两次:
=>select jsonb_path_query(:'json', 'lax $.**.HR');jsonb_path_query ------------------ 73 135 73 135
这是因为.** 访问器既选择 segments 数组,又选择它的每个元素。而在宽松模式下,.HR 访问器会自动解包数组。为了避免意外的结果,我们建议仅在严格模式下使用.** 访问器。下面的查询选择每个 HR 值仅一次:
=>select jsonb_path_query(:'json', 'strict $.**.HR');jsonb_path_query ------------------ 73 135
数组解包也可能产生意外结果。请看下面这个选取所有 location 数组的示例:
=>select jsonb_path_query(:'json', 'lax $.track.segments[*].location');jsonb_path_query ------------------- [47.763, 13.4034] [47.706, 13.2635] (2 rows)
它会如预期那样返回完整数组。但应用过滤表达式时,数组会被解包,以便对每个元素求值,最终只返回匹配该表达式的元素:
=>select jsonb_path_query(:'json', 'lax $.track.segments[*].location ?(@[*] > 15)');jsonb_path_query ------------------ 47.763 47.706 (2 rows)
尽管路径表达式选取的是完整数组,结果仍然如此。使用严格模式可以恢复选取数组的行为:
=>select jsonb_path_query(:'json', 'strict $.track.segments[*].location ?(@[*] > 15)');jsonb_path_query ------------------- [47.763, 13.4034] [47.706, 13.2635] (2 rows)
9.16.2.3. SQL/JSON 路径操作符和方法 #
表 9.50 列出了 jsonpath 中可用的操作符和方法。请注意,一元操作符和方法可以应用于前一个路径步骤产生的多个值,而二元操作符(加法等)只能应用于单个值。在宽松模式下,对数组应用方法时,会对数组中的每个值执行该方法。例外是.type() 和.size(),它们作用于数组本身。
表 9.50. jsonpath 操作符和方法
操作符/方法 描述 示例 |
|---|
加法
|
一元加号(无操作);与加法不同,这个可以迭代多个值
|
减法
|
取负;与减法不同,可以遍历多个值。
|
乘法
|
除法
|
取模(余数)
|
JSON 项的类型(参见
|
JSON 项的大小(数组元素的数量,如果不是数组则为 1)
|
将 JSON 布尔值、数字或字符串转换为布尔值。
|
将 JSON 布尔值、数字、字符串或日期时间转换为字符串值。
|
从 JSON 数字或字符串转换过来的近似浮点数
|
大于或等于给定数字的最接近的整数
|
小于或等于给定数字的最近整数
|
给定数字的绝对值
|
将 JSON 数字或字符串转换为大整数值。
|
将 JSON 数字或字符串转换为经过舍入的十进制数值(
|
将 JSON 数字或字符串转换为整数值。
|
将 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 函数中执行。类似地,其他将字符串转换为日期/时间类型的日期/时间相关方法也会进行这种转换,这可能涉及当前的 TimeZone 设置。因此,这些转换同样只能在时区感知的 jsonpath 函数中执行。
表 9.51 显示了可用的过滤表达式元素。
表 9.51. jsonpath 过滤表达式元素
谓词/值 描述 示例 |
|---|
相等比较(这个,和其他比较操作符,适用于所有 JSON 标量值)
|
不相等比较
|
小于比较
|
小于或等于比较
|
大于比较
|
大于或等于比较
|
JSON 常量
|
JSON 常量
|
JSON 常量
|
布尔 AND
|
布尔 OR
|
布尔 NOT
|
测试布尔条件是否为
|
测试第一个操作数是否与第二个操作数给出的正则表达式匹配;可以用一串
|
测试第二个操作数是否为第一个操作数的初始子串。
|
测试路径表达式是否至少匹配一个 SQL/JSON 项。如果路径表达式会导致错误,则返回
|
9.16.2.4. 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+$")
9.16.3. SQL/JSON 查询函数 #
表 9.52 中描述的 SQL/JSON 函数 JSON_EXISTS()、JSON_QUERY() 和 JSON_VALUE()
可用于查询 JSON 文档。每个这样的函数都会将
path_expression(一个 SQL/JSON 路径查询)应用到
context_item(该文档)上。关于
path_expression 可以包含哪些内容的更多细节,请参见第 9.16.2 节。path_expression 还可以引用变量,这些变量的值通过各函数所支持的 PASSING 子句,以各自的名称指定。context_item 可以是一个 jsonb 值,也可以是一个能够成功强制转换为 jsonb 的字符串。
表 9.52. SQL/JSON 查询函数
函数签名 描述 示例 |
|---|
示例:
ERROR: jsonpath array subscript is out of bounds
|
示例:
ERROR: malformed array literal: "[1, 2]" DETAIL: Missing "]" after array dimensions.
|
示例:
|
注意
如果 context_item 表达式的类型还不是
jsonb,则会通过隐式强制转换将其转换为 jsonb。但请注意,在该转换期间发生的任何解析错误都会无条件抛出,也就是说,不会按照(显式指定或隐含的)ON ERROR
子句来处理。
注意
如果 path_expression 返回 JSON
null,则 JSON_VALUE() 返回 SQL NULL,而 JSON_QUERY() 则按原样返回 JSON null。
9.16.4. JSON_TABLE #
JSON_TABLE 是一个 SQL/JSON 函数,用于查询 JSON 数据,并将结果表示为关系视图,从而可以像访问常规 SQL 表一样访问它。你可以在 SELECT、UPDATE 或
DELETE 的 FROM 子句中使用
JSON_TABLE,也可以在 MERGE
语句中将其用作数据源。
JSON_TABLE 以 JSON 数据作为输入,使用 JSON 路径表达式从给定数据中提取一部分,将其用作所构造视图的行模式。行模式给出的每个 SQL/JSON
值都作为所构造视图中单独一行的来源。
为了将行模式拆分为列,JSON_TABLE
提供了定义所创建视图结构的 COLUMNS 子句。对于每一列,都可以指定一个单独的 JSON 路径表达式,相对于行模式进行计算,以得到一个 SQL/JSON 值,该值将成为给定输出行中指定列的值。
存储在行模式嵌套层级中的 JSON 数据可以通过
NESTED PATH 子句提取。每个
NESTED PATH 子句都可以利用行模式某个嵌套层级中的数据生成一个或多个列。这些列可以通过一个看起来与顶层 COLUMNS 子句类似的
COLUMNS 子句来指定。由
NESTED COLUMNS 构造的行称为子行,它们会与父
COLUMNS 子句中指定的列所构造的行连接,从而得到最终视图中的行。子列自身也可以包含
NESTED PATH 说明,因此可以提取位于任意嵌套层级中的数据。在同一层级上由多个 NESTED PATH 生成的列彼此视为兄弟,它们各自生成的行在与父行连接后,通过 UNION 进行组合。
由 JSON_TABLE 生成的行会与生成它们的行进行横向连接,因此你无需显式地将构造出来的视图与保存 JSON
数据的原始表进行连接。
语法如下:
JSON_TABLE (
context_item, path_expression [ AS json_path_name ] [ PASSING { value AS varname } [, ...] ]
COLUMNS ( json_table_column [, ...] )
[ { ERROR | EMPTY [ARRAY]} ON ERROR ]
)
其中 json_table_column 为:
name FOR ORDINALITY
| name type
[ FORMAT JSON [ENCODING UTF8]]
[ PATH path_expression ]
[ { WITHOUT | WITH { CONDITIONAL | [UNCONDITIONAL] } } [ ARRAY ] WRAPPER ]
[ { KEEP | OMIT } QUOTES [ ON SCALAR STRING ] ]
[ { ERROR | NULL | EMPTY { [ARRAY] | OBJECT } | DEFAULT expression } ON EMPTY ]
[ { ERROR | NULL | EMPTY { [ARRAY] | OBJECT } | DEFAULT expression } ON ERROR ]
| name type EXISTS [ PATH path_expression ]
[ { ERROR | TRUE | FALSE | UNKNOWN } ON ERROR ]
| NESTED [ PATH ] path_expression [ AS json_path_name ] COLUMNS ( json_table_column [, ...] )
下面更详细地说明每个语法元素。
-
context_item,path_expression[ASjson_path_name] [PASSING{valueASvarname} [, ...]] context_item指定要查询的输入文档,path_expression是定义查询的 SQL/JSON 路径表达式,而json_path_name是path_expression的可选名称。可选的PASSING子句为path_expression中提到的变量提供数据值。使用上述元素对输入数据求值得到的结果称为行模式,它被用作构造视图中各行取值的来源。-
COLUMNS(json_table_column[, ...] ) 定义构造视图结构的
COLUMNS子句。在该子句中,你可以指定每一列使用将 JSON 路径表达式应用于行模式所获得的 SQL/JSON 值来填充。json_table_column有以下几种变体:-
nameFOR ORDINALITY 增加一个序号列,提供从 1 开始的连续行编号。每个
NESTED PATH(见下文)都会为其中任何嵌套的序号列维护自己的计数器。-
nametype[FORMAT JSON[ENCODINGUTF8]] [PATHpath_expression] 将通过把
path_expression应用于行模式而得到的 SQL/JSON 值,在强制转换为指定的type之后,插入到视图的输出行中。指定
FORMAT JSON可以显式表明你期望该值是一个合法的json对象。只有当type是bpchar、bytea、character varying、name、json、jsonb、text之一,或者是这些类型上的域时,指定FORMAT JSON才有意义。你还可以选择指定
WRAPPER和QUOTES子句来格式化输出。请注意,如果也指定了FORMAT JSON,那么指定OMIT QUOTES会覆盖它,因为不带引号的字面量不构成合法的json值。你还可以选择使用
ON EMPTY和ON ERROR子句来分别指定:当 JSON 路径求值结果为空时,以及当 JSON 路径求值期间发生错误,或将 SQL/JSON 值强制转换为指定类型时发生错误时,是抛出错误还是返回指定的值。这两者的默认行为都是返回NULL值。注意
该子句在内部会被转换为
JSON_VALUE或JSON_QUERY,并具有相同的语义。如果指定的类型不是标量类型,或者出现了FORMAT JSON、WRAPPER或QUOTES子句中的任意一个,则会转换为后者。-
nametypeEXISTS[PATHpath_expression] 将通过把
path_expression应用于行模式而得到的布尔值,在强制转换为指定的type之后,插入到视图的输出行中。该值对应于将
PATH表达式应用到行模式后是否产生任何值。应当存在从
boolean到指定type的类型转换。你还可以选择使用
ON ERROR来指定:当 JSON 路径求值期间发生错误,或者将 SQL/JSON 值强制转换为指定类型时发生错误时,是抛出错误还是返回指定的值。默认返回布尔值FALSE。注意
该子句在内部会被转换为
JSON_EXISTS,并具有相同的语义。-
NESTED [ PATH ]path_expression[ASjson_path_name]COLUMNS(json_table_column[, ...] ) 从行模式的嵌套层级中提取 SQL/JSON 值,按照
COLUMNS子子句的定义生成一个或多个列,并将提取出的 SQL/JSON 值插入这些列中。COLUMNS子子句中的json_table_column表达式使用与父级COLUMNS子句相同的语法。NESTED PATH的语法是递归的,因此你可以通过相互嵌套地指定多个NESTED PATH子子句来向下进入多个嵌套层级。这使得你可以在一次函数调用中展开 JSON 对象和数组的层次结构,而不必在 SQL 语句中串联多个JSON_TABLE表达式。
注意
对于上面描述的每一种
json_table_column变体,如果省略了PATH子句,则使用路径表达式$.,其中namename是提供的列名。-
-
ASjson_path_name 可选的
json_path_name用作所提供的path_expression的标识符。该名称必须唯一,并且不得与列名相同。-
{
ERROR|EMPTY}ON ERROR 可选的
ON ERROR可用于指定在计算顶层path_expression时如何处理错误。如果你希望错误被抛出,请使用ERROR;使用EMPTY则返回一个空表,也就是一个包含 0 行的表。请注意,该子句不会影响计算列时发生的错误;对于列中的错误,其行为取决于对应列上是否指定了ON ERROR子句。
示例
在下面的示例中,将使用下表,其中包含 JSON 数据:
CREATE TABLE my_films ( js jsonb );
INSERT INTO my_films VALUES (
'{ "favorites" : [
{ "kind" : "comedy", "films" : [
{ "title" : "Bananas",
"director" : "Woody Allen"},
{ "title" : "The Dinner Game",
"director" : "Francis Veber" } ] },
{ "kind" : "horror", "films" : [
{ "title" : "Psycho",
"director" : "Alfred Hitchcock" } ] },
{ "kind" : "thriller", "films" : [
{ "title" : "Vertigo",
"director" : "Alfred Hitchcock" } ] },
{ "kind" : "drama", "films" : [
{ "title" : "Yojimbo",
"director" : "Akira Kurosawa" } ] }
] }');
下面的查询展示了如何使用 JSON_TABLE
将 my_films 表中的 JSON 对象转换为一个视图,其中包含原始 JSON 中的键 kind、title 和 director 对应的列,以及一个序号列:
SELECT jt.* FROM my_films, JSON_TABLE (js, '$.favorites[*]' COLUMNS ( id FOR ORDINALITY, kind text PATH '$.kind', title text PATH '$.films[*].title' WITH WRAPPER, director text PATH '$.films[*].director' WITH WRAPPER)) AS jt;
id | kind | title | director ----+----------+--------------------------------+---------------------------------- 1 | comedy | ["Bananas", "The Dinner Game"] | ["Woody Allen", "Francis Veber"] 2 | horror | ["Psycho"] | ["Alfred Hitchcock"] 3 | thriller | ["Vertigo"] | ["Alfred Hitchcock"] 4 | drama | ["Yojimbo"] | ["Akira Kurosawa"] (4 rows)
下面是在上述查询基础上的修改版本,用来展示顶层 JSON 路径表达式中指定的过滤条件里如何使用 PASSING 参数,以及各个列的不同选项:
SELECT jt.* FROM
my_films,
JSON_TABLE (js, '$.favorites[*] ? (@.films[*].director == $filter)'
PASSING 'Alfred Hitchcock' AS filter
COLUMNS (
id FOR ORDINALITY,
kind text PATH '$.kind',
title text FORMAT JSON PATH '$.films[*].title' OMIT QUOTES,
director text PATH '$.films[*].director' KEEP QUOTES)) AS jt;
id | kind | title | director ----+----------+---------+-------------------- 1 | horror | Psycho | "Alfred Hitchcock" 2 | thriller | Vertigo | "Alfred Hitchcock" (2 rows)
下面是在上述查询基础上的修改版本,用来展示如何使用
NESTED PATH 填充 title 和 director
列,并说明它们如何与父列 id 和 kind 连接:
SELECT jt.* FROM
my_films,
JSON_TABLE ( js, '$.favorites[*] ? (@.films[*].director == $filter)'
PASSING 'Alfred Hitchcock' AS filter
COLUMNS (
id FOR ORDINALITY,
kind text PATH '$.kind',
NESTED PATH '$.films[*]' COLUMNS (
title text FORMAT JSON PATH '$.title' OMIT QUOTES,
director text PATH '$.director' KEEP QUOTES))) AS jt;
id | kind | title | director ----+----------+---------+-------------------- 1 | horror | Psycho | "Alfred Hitchcock" 2 | thriller | Vertigo | "Alfred Hitchcock" (2 rows)
下面是同一个查询,但去掉了根路径中的过滤条件:
SELECT jt.* FROM
my_films,
JSON_TABLE ( js, '$.favorites[*]'
COLUMNS (
id FOR ORDINALITY,
kind text PATH '$.kind',
NESTED PATH '$.films[*]' COLUMNS (
title text FORMAT JSON PATH '$.title' OMIT QUOTES,
director text PATH '$.director' KEEP QUOTES))) AS jt;
id | kind | title | director ----+----------+-----------------+-------------------- 1 | comedy | Bananas | "Woody Allen" 1 | comedy | The Dinner Game | "Francis Veber" 2 | horror | Psycho | "Alfred Hitchcock" 3 | thriller | Vertigo | "Alfred Hitchcock" 4 | drama | Yojimbo | "Akira Kurosawa" (5 rows)
下面展示了另一个以不同 JSON
对象作为输入的查询。它展示了 NESTED
路径 $.movies[*] 和
$.books[*] 之间通过 UNION 实现的“兄弟连接”,以及在 NESTED 层级上使用
FOR ORDINALITY 列(列 movie_id、book_id
和 author_id):
SELECT * FROM JSON_TABLE (
'{"favorites":
[{"movies":
[{"name": "One", "director": "John Doe"},
{"name": "Two", "director": "Don Joe"}],
"books":
[{"name": "Mystery", "authors": [{"name": "Brown Dan"}]},
{"name": "Wonder", "authors": [{"name": "Jun Murakami"}, {"name":"Craig Doe"}]}]
}]}'::json, '$.favorites[*]'
COLUMNS (
user_id FOR ORDINALITY,
NESTED '$.movies[*]'
COLUMNS (
movie_id FOR ORDINALITY,
mname text PATH '$.name',
director text),
NESTED '$.books[*]'
COLUMNS (
book_id FOR ORDINALITY,
bname text PATH '$.name',
NESTED '$.authors[*]'
COLUMNS (
author_id FOR ORDINALITY,
author_name text PATH '$.name'))));
user_id | movie_id | mname | director | book_id | bname | author_id | author_name
---------+----------+-------+----------+---------+---------+-----------+--------------
1 | 1 | One | John Doe | | | |
1 | 2 | Two | Don Joe | | | |
1 | | | | 1 | Mystery | 1 | Brown Dan
1 | | | | 2 | Wonder | 1 | Jun Murakami
1 | | | | 2 | Wonder | 2 | Craig Doe
(5 rows)
报告文档问题
阅读 上游文档. 通过 PostgreSQL 文档反馈表单.