JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它基于JavaScript的一个子集,采用键值对的形式来表示数据。JSON被广泛应用于Web应用程序中,用于数据的传输和存储。
JSON的特点包括:
简洁和易读:JSON使用简洁的文本格式,易于人类阅读和编写。
轻量级:JSON的数据格式较小,传输和解析效率高。
跨平台:JSON可以被多种编程语言读取和解析,可以在不同的平台间进行数据交换。
JSON 数据类型用于存储 RFC 7159 中指定的 JSON(JavaScript 对象表示法)数据。此类数据也可以存储为 ,但 JSON 数据类型的优点是根据 JSON 规则强制每个存储的值都有效。还有各种特定于 JSON 的函数和运算符可用于存储在这些数据类型中的数据;
和数据类型接受几乎相同的值集作为输入。主要的实际区别在于效率。数据类型存储输入文本的精确副本,处理函数必须在每次执行时重新解析该文本;虽然数据以分解的二进制格式存储,但由于增加了转换开销,因此输入速度略慢,但处理速度明显更快,因为不需要重新解析。 还支持索引,这可能是一个显着的优势。
json类型存储输入文本的精确副本,因此它将保留标记之间语义上无关紧要的空格,以及 JSON 对象中键的顺序。此外,如果值中的 JSON 对象多次包含同一键,则会保留所有键/值对。(处理函数将最后一个值视为有效值。相比之下,jsonb不保留空格,不保留对象键的顺序,也不保留重复的对象键。如果在输入中指定了重复的键,则仅保留最后一个值。
通常,大多数应用程序应更愿意将 JSON 数据存储为jsonb ,除非有非常特殊的需求,例如关于对象键排序的传统假设。
RFC 7159 指定 JSON 字符串应以 UTF8 编码。因此,除非数据库编码为 UTF8,否则 JSON 类型不可能严格符合 JSON 规范。尝试直接包含无法在数据库编码中表示的字符将失败;相反,允许使用数据库编码但不能用 UTF8 表示的字符。
RFC 7159 允许 JSON 字符串包含用 表示的 Unicode 转义序列。在类型的输入函数中,无论数据库编码如何,都允许使用 Unicode 转义,并且仅检查语法正确性(即后面有四个十六进制数字)。但是,的输入函数更严格:它不允许对无法在数据库编码中表示的字符进行 Unicode 转义。该类型也拒绝(因为这不能在 PostgreSQL 的类型中表示),并且它坚持认为任何使用 Unicode 代理项对来指定 Unicode 基本多语言平面之外的字符都是正确的。有效的 Unicode 转义将转换为等效的单个字符进行存储;这包括将代理项对折叠成一个字符。
将文本 JSON 输入转换为 时,RFC 7159 描述的原始类型会有效地映射到本机 PostgreSQL 类型,如下表所示。因此,对于有效数据的构成,存在一些小的附加约束,这些约束不适用于类型,也不适用于抽象的 JSON,对应于基础数据类型可以表示的内容的限制。值得注意的是,将拒绝超出 PostgreSQL 数据类型范围的数字,而不会。RFC 7159 允许此类实现定义的限制。然而,在实践中,此类问题更有可能发生在其他实现中,因为通常将 JSON 的原始类型表示为 IEEE 754 双精度浮点(RFC 7159 明确预测并允许)。当使用JSON作为与此类系统的交换格式时,应考虑与PostgreSQL最初存储的数据相比失去数字精度的危险。jsonbjsonbjsonjsonbnumericjsonnumber
相反,如表中所述,对 JSON 基元类型的输入格式有一些小限制,这些限制不适用于相应的 PostgreSQL 类型。
|JSON 基元类型|PostgreSQL 类型|笔记|
|string|text|\u0000不允许,表示数据库编码中不可用的字符的 Unicode 转义也是如此|
|number|numeric|NaN和值是不允许的infinity|
|boolean|boolean |只接受小写字母和拼写truefalse|
|null |(无)|SQL是一个不同的概念NULL|
JSON常见的数据类型:
- 字符串(String):用于表示文本数据,使用双引号包裹起来。 数字(Number):用于表示数值,可以是整数或小数。
- 布尔值(Boolean):表示真或假,只有两个取值:true和false。
- 数组(Array):表示一组有序的数据,使用方括号包裹起来,每个元素用逗号分隔。
- 对象(Object):表示一组键值对的集合,使用大括号包裹起来,每个键值对使用冒号分隔,键名为字符串类型。
例如,以下是一个JSON对象的示例:
sql
{
"name": "John",
"age": 30,
"isStudent": false,
"hobbies": ["reading", "running", "coding"],
"address": {
"street": "123 Main St",
"city": "New York"
}
}
以上示例中,name、age和isStudent是对象的属性,hobbies是数组类型的属性,address是对象类型的属性。
PostgreSQL 提供了两种类型来存储 JSON 数据:json和jsonb.为了实现这些数据类型的高效查询机制,PostgreSQL 还提供了jsonpath数据类型。
JSON输入和输出语法
JSON 数据类型的输入/输出语法在 RFC 7159 中指定。
以下是所有有效(或)表达式:
sql
-- Simple scalar/primitive value
-- Primitive values can be numbers, quoted strings, true, false, or null
SELECT '5'::json;
-- Array of zero or more elements (elements need not be of same type)
SELECT '[1, 2, "foo", null]'::json;
-- Object containing pairs of keys and values
-- Note that object keys must always be quoted strings
SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json;
-- Arrays and objects can be nested arbitrarily
SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json;
如前所述,当输入一个 JSON 值,然后在没有任何额外处理的情况下打印时,输出的文本与输入的文本相同,同时不保留语义上无关紧要的细节,例如空格。例如jsonb,请注意此处的差异:
sql
SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::json;
json
-------------------------------------------------
{"bar": "baz", "balance": 7.77, "active":false}
(1 row)
SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb;
jsonb
--------------------------------------------------
{"bar": "baz", "active": false, "balance": 7.77}
(1 row)
一个值得注意的语义上无关紧要的细节是,在jsonb中,数字将根据基础类型的行为进行打印。在实践中,这意味着使用符号输入的数字将不打印,例如:
sql
SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
json | jsonb
-----------------------+-------------------------
{"reading": 1.230e-5} | {"reading": 0.00001230}
(1 row)
但是,将保留尾随的小数零,如本示例所示,即使这些零在语义上对于相等性检查等目的无关紧要。
检查jsonb
该类型没有并行的设施集。包含测试一个文档中是否包含另一个文档。除非另有说明,否则这些示例返回 true
sql
-- Simple scalar/primitive values contain only the identical value:
SELECT '"foo"'::jsonb @> '"foo"'::jsonb;
-- The array on the right side is contained within the one on the left:
SELECT '[1, 2, 3]'::jsonb @> '[1, 3]'::jsonb;
-- Order of array elements is not significant, so this is also true:
SELECT '[1, 2, 3]'::jsonb @> '[3, 1]'::jsonb;
-- Duplicate array elements don't matter either:
SELECT '[1, 2, 3]'::jsonb @> '[1, 2, 2]'::jsonb;
-- The object with a single pair on the right side is contained
-- within the object on the left side:
SELECT '{"product": "PostgreSQL", "version": 9.4, "jsonb": true}'::jsonb @> '{"version": 9.4}'::jsonb;
-- The array on the right side is not considered contained within the
-- array on the left, even though a similar array is nested within it:
SELECT '[1, 2, [1, 3]]'::jsonb @> '[1, 3]'::jsonb; -- yields false
-- But with a layer of nesting, it is contained:
SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb;
-- Similarly, containment is not reported here:
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"bar": "baz"}'::jsonb; -- yields false
-- A top-level key and an empty object is contained:
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"foo": {}}'::jsonb;
一般原则是,包含的对象必须在结构和数据内容方面与包含对象匹配,可能是在从包含对象中丢弃一些不匹配的数组元素或对象键/值对之后。但请记住,在进行包含匹配时,数组元素的顺序并不重要,并且重复的数组元素实际上只考虑一次。
作为结构必须匹配的一般原则的一个特殊例外,数组可以包含一个基元值:
sql
-- This array contains the primitive string value:
SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb;
-- This exception is not reciprocal -- non-containment is reported here:
SELECT '"bar"'::jsonb @> '["bar"]'::jsonb; -- yields false
jsonb还有一个存在运算符,它是包含主题的变体:它测试字符串(作为值给出)是否在值的顶层显示为对象键或数组元素。除非另有说明,否则这些示例返回 true:
sql
-- String exists as array element:
SELECT '["foo", "bar", "baz"]'::jsonb ? 'bar';
-- String exists as object key:
SELECT '{"foo": "bar"}'::jsonb ? 'foo';
-- Object values are not considered:
SELECT '{"foo": "bar"}'::jsonb ? 'bar'; -- yields false
-- As with containment, existence must match at the top level:
SELECT '{"foo": {"bar": "baz"}}'::jsonb ? 'bar'; -- yields false
-- A string is considered to exist if it matches a primitive JSON string:
SELECT '"foo"'::jsonb ? 'foo';
当涉及许多键或元素时,JSON 对象比数组更适合测试包含或存在,因为与数组不同,它们内部针对搜索进行了优化,并且不需要线性搜索。
jsonb索引
GIN 索引可用于有效地搜索大量文档(数据)中出现的键或键/值对。提供了两个 GIN"运算符类",提供了不同的性能和灵活性权衡。
默认的 GIN 运算符类支持使用 key-exists 运算符 、 和 、 包含运算符 、 匹配运算符 和 进行查询。(有关这些运算符实现的语义的详细信息,请参阅表 9.46。使用此运算符类创建索引的示例如下:jsonb??|?&@>jsonpath@?@@
sql
CREATE INDEX idxgin ON api USING GIN (jdoc);
非默认 GIN 运算符类不支持 key-exists 运算符,但支持 、 和 。使用此运算符类创建索引的示例如下:jsonb_path_ops@>@?@@
sql
CREATE INDEX idxginp ON api USING GIN (jdoc jsonb_path_ops);
请考虑一个表示例,该表存储从第三方 Web 服务检索到的 JSON 文档,并记录了架构定义。典型的文档是:
sql
{
"guid": "9c36adc1-7fb5-4d5b-83b4-90356a46061a",
"name": "Angela Barton",
"is_active": true,
"company": "Magnafone",
"address": "178 Howard Place, Gulf, Washington, 702",
"registered": "2009-11-07T08:53:22 +08:00",
"latitude": 19.793713,
"longitude": 86.513373,
"tags": [
"enim",
"aliquip",
"qui"
]
}
我们将这些文档存储在名为api 的表中,jsonb在名为jdoc的列中。如果在此列上创建了 GIN 索引,则如下所示的查询可以使用该索引:
sql
-- Find documents in which the key "company" has value "Magnafone"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"company": "Magnafone"}';
但是,索引不能用于如下所示的查询,因为尽管运算符是可索引的,但它不直接应用于索引列:
sql
-- Find documents in which the key "tags" contains key or array element "qui"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc -> 'tags' ? 'qui';
不过,通过适当使用表达式索引,上述查询可以使用索引。如果查询键中的特定项很常见,则定义这样的索引可能是值得的:"tags"
sql
CREATE INDEX idxgintags ON api USING GIN ((jdoc -> 'tags'));
现在,该子句将被识别为可索引运算符对索引表达式的应用。
sql
另一种查询方法是利用包含,例如:
-- Find documents in which the key "tags" contains array element "qui"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qui"]}';
列上的简单 GIN 索引可以支持此查询。但请注意,这样的索引将存储列中每个键和值的副本,而上一个示例的表达式索引仅存储在键下找到的数据。虽然简单索引方法要灵活得多(因为它支持对任何键的查询),但与简单索引相比,目标表达式索引可能更小,搜索速度更快。
GIN 索引还支持执行匹配的 and 运算符。例如:@?@@jsonpath
sql
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';
对于这些运算符,GIN 索引从模式中提取表单子句,并根据这些子句中提到的键和值执行索引搜索。访问器链可以包括 、 和 访问器。operator 类还支持 和访问器,但 operator 类不支持。accessors_chain = constantjsonpath.key[][index]jsonb_ops..**jsonb_path_ops
尽管运算符类仅支持使用 和 运算符的查询,但与默认运算符类相比,它具有显着的性能优势。索引通常比相同数据的索引小得多,并且搜索的特异性更好,尤其是当查询包含数据中频繁出现的键时。因此,搜索操作通常比使用默认运算符类的性能更好。jsonb_path_ops@>@?@@jsonb_opsjsonb_path_opsjsonb_ops
GIN索引和GIN索引之间的技术区别在于,前者为数据中的每个键和值创建独立的索引项,而后者仅为数据中的每个值创建索引项。jsonb_opsjsonb_path_ops[7]基本上,每个索引项都是值和导致它的键的哈希值;例如,要索引,将创建一个索引项,将 、 和 这三个项合并到哈希值中。因此,查找此结构的包含查询将导致极其具体的索引搜索;但是根本没有办法确定是否显示为密钥。另一方面,索引将创建三个索引项,分别表示 、 和 ;然后,要执行包含查询,它将查找包含所有这三个项的行。虽然 GIN 索引可以相当有效地执行此类 AND 搜索,但它仍然不如等效搜索具体且速度慢,尤其是在有大量行包含三个索引项中的任何一个时。jsonb_path_ops{"foo": {"bar": "baz"}}foobarbazfoojsonb_opsfoobarbazjsonb_path_ops
该方法的缺点是,它不会为不包含任何值的 JSON 结构生成索引条目,例如 .如果请求搜索包含此类结构的文档,则需要进行全索引扫描,这非常慢。 因此,不适合经常执行此类搜索的应用程序。jsonb_path_ops{"a": {}}jsonb_path_ops
jsonb还支持和索引。仅当检查完整 JSON 文档的相等性很重要时,这些通常才有用。基准的排序很少引起人们的极大兴趣,但为了完整起见,它是:
btreehashbtreejsonb
sql
Object > Array > Boolean > Number > String > Null
Object with n pairs > object with n - 1 pairs
Array with n elements > array with n - 1 elements
具有相等数量的对的对象按以下顺序进行比较:
sql
key-1, value-1, key-2 ...
请注意,对象键是按其存储顺序进行比较的;特别是,由于较短的密钥先于较长的密钥存储,这可能会导致可能不直观的结果,例如:
sql
{ "aa": 1, "c": 1} > {"b": 1, "d": 1}
同样,具有相同元素数的数组按以下顺序进行比较:
sql
element-1, element-2 ...
使用与基础 PostgreSQL 数据类型相同的比较规则来比较原始 JSON 值。使用默认数据库排序规则比较字符串。
数据类型支持数组样式的下标表达式来提取和修改元素。嵌套值可以通过链接下标表达式来指示,遵循与函数中参数相同的规则。如果值是数组,则数字下标从零开始,负整数从数组的最后一个元素向后计数。不支持切片表达式。下标表达式的结果始终是 jsonb 数据类型。jsonbpathjsonb_setjsonb
UPDATE语句可以在子句中使用下标来修改值。对于所有受影响的值,只要它们存在,下标路径必须是可遍历的。例如,路径可以一直遍历到 if 每个 , , 和一个对象。如果有或未定义,它将被创建为空对象,并根据需要填充。但是,如果任何本身或中间值之一被定义为非对象(如字符串、数字或),则遍历无法继续,因此会引发错误并中止事务。SETjsonbval['a']['b']['c']cvalval['a']val['a']['b']val['a']val['a']['b']valjsonbnull
下标语法示例:
sql
-- Extract object value by key
SELECT ('{"a": 1}'::jsonb)['a'];
-- Extract nested object value by key path
SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c'];
-- Extract array element by index
SELECT ('[1, "2", null]'::jsonb)[1];
-- Update object value by key. Note the quotes around '1': the assigned
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
-- This will raise an error if any record's jsonb_field['a']['b'] is something
-- other than an object. For example, the value {"a": 1} has a numeric value
-- of the key 'a'.
UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';
jsonb通过下标分配处理一些边缘情况的方式与 不同。当源值为 时,通过下标进行赋值将继续进行,就好像它是下标键所隐含的类型(对象或数组)的空 JSON 值一样:jsonb_setjsonbNULL
sql
-- Where jsonb_field was NULL, it is now {"a": 1}
UPDATE table_name SET jsonb_field['a'] = '1';
-- Where jsonb_field was NULL, it is now [1]
UPDATE table_name SET jsonb_field[0] = '1';
如果为包含太少元素的数组指定了索引,则将追加元素,直到可访问索引并可以设置值。
sql
-- Where jsonb_field was [], it is now [null, null, 2];
-- where jsonb_field was [0], it is now [0, null, 2]
UPDATE table_name SET jsonb_field[2] = '2';
只要要遍历的最后一个现有元素是对象或数组,值就会接受对不存在的下标路径的赋值,正如相应的下标所暗示的那样(路径中最后一个下标指示的元素不会遍历,可以是任何东西)。将创建嵌套数组和对象结构,在前一种情况下为 -padded,由下标路径指定,直到可以放置分配的值。
sql
-- Where jsonb_field was {}, it is now {"a": [{"b": 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
-- Where jsonb_field was [], it is now [null, {"a": 1}]
UPDATE table_name SET jsonb_field[1]['a'] = '1';
转换
sql
可以使用其他扩展来实现不同过程语言的类型的转换。jsonb
PL/Perl 的扩展称为 和 。如果使用它们,则值将根据需要映射到 Perl 数组、哈希和标量。jsonb_plperljsonb_plperlujsonb
PL/Python 的扩展称为 .如果使用它,则值将根据需要映射到 Python 字典、列表和标量。jsonb_plpython3ujsonb
在这些扩展中,被认为是"受信任的",也就是说,它可以由对当前数据库具有权限的非超级用户安装。其余的需要超级用户权限才能安装。jsonb_plperlCREATE
jsonpath类型
该类型在 PostgreSQL 中实现了对 SQL/JSON 路径语言的支持,以有效地查询 JSON 数据。它提供了已解析的 SQL/JSON 路径表达式的二进制表示形式,该表达式指定路径引擎要从 JSON 数据中检索的项目,以便使用 SQL/JSON 查询函数进行进一步处理。jsonpath
SQL/JSON 路径谓词和运算符的语义通常遵循 SQL。同时,为了提供一种自然的方式来处理 JSON 数据,SQL/JSON 路径语法使用了一些 JavaScript 约定:
点 () 用于成员访问。.
方括号 () 用于数组访问。[]
SQL/JSON 数组是相对于 0 的,这与从 1 开始的常规 SQL 数组不同。
SQL/JSON 路径表达式中的数值文字遵循 JavaScript 规则,这些规则在一些小细节上与 SQL 和 JSON 不同。例如,SQL/JSON 路径允许 和 ,这在 JSON 中无效。支持非十进制整数文字和下划线分隔符,例如 、 、 、 。在 SQL/JSON 路径中(在 JavaScript 中,但在 SQL 中则不然),基数前缀后面不得有下划线分隔符。.11.1_000_0000x1EEE_FFFF0o2730b100101
SQL/JSON 路径表达式通常以 SQL 字符串文字的形式写入 SQL 查询,因此它必须用单引号括起来,并且值中所需的任何单引号都必须加倍(参见第 4.1.2.1 节)。某些形式的路径表达式需要其中的字符串文字。这些嵌入的字符串文字遵循 JavaScript/ECMAScript 约定:它们必须用双引号括起来,并且可以在其中使用反斜杠转义来表示其他难以键入的字符。特别是,在嵌入的字符串文字中编写双引号的方法是 ,而要编写反斜杠本身,必须编写 。其他特殊的反斜杠序列包括 JSON 字符串中识别的反斜杠序列:、、、反斜杠语法还包括 JSON 不允许的两种情况:仅用两个十六进制数字编写的字符代码,以及用 1 到 6 个十六进制数字编写的字符代码。"\\b\f\n\r\t\v\uNNNN\xNN\u{N...}
路径表达式由一系列路径元素组成,这些元素可以是以下任意元素:
- JSON 基元类型的路径文本:Unicode 文本、数字、true、false 或 null。
- 表 8.24 中列出的路径变量。
- 表 8.25 中列出的访问器运算符。
- jsonpath第 9.16.2.2 节中列出的运算符和方法。
括号,可用于提供筛选器表达式或定义路径计算的顺序。
有关将表达式用于 SQL/JSON 查询函数的详细信息,请参见Section 9.16.2。jsonpath
表 8.24.JSONPash路径变量
变量 | 描述 |
---|---|
$ | 一个变量,表示正在查询的 JSON 值(上下文项)。 |
$varname | 命名变量。它的值可以通过几个JSON处理函数的参数vars来设置; |
@ | 一个变量,表示筛选器表达式中的路径计算结果。 |
访问器运算符 | 描述 |
---|---|
.key ."$varname" | 返回具有指定键的对象成员的成员的成员。如果键名称与某个以 JavaScript 开头的变量匹配或不符合标识符的 JavaScript 规则,则必须将其括在双引号中,以使其成为字符串文本。$ |
.* | 通配符成员访问器,返回位于当前对象顶层的所有成员的值。 |
.** | 递归通配符成员访问器,用于处理当前对象的 JSON 层次结构的所有级别,并返回所有成员值,而不考虑其嵌套级别。这是 SQL/JSON 标准的 PostgreSQL 扩展。 |
.{level} .{start_level to end_level} | 与 类似,但仅选择 JSON 层次结构的指定级别。嵌套级别指定为整数。级别零对应于当前对象。若要访问最低嵌套级别,可以使用关键字。这是 SQL/JSON 标准的 PostgreSQL 扩展。.**last |
[subscript, ...] | 数组元素访问器。 可以采用两种形式给出:或 .第一种形式通过其索引返回单个数组元素。第二种形式按索引范围返回数组切片,包括与提供的start_index和end_index对应的元素。subscriptindexstart_index to end_index,指定的索引可以是整数,也可以是返回单个数值的表达式,该值会自动转换为整数。索引零对应于第一个数组元素。还可以使用关键字来表示最后一个数组元素,这对于处理未知长度的数组很有用。last |
[*] | 返回所有数组元素的通配符数组元素访问器。 |
为此,术语"值"包括数组元素,尽管 JSON 术语有时将数组元素与对象中的值不同。