JSON 文档存储详解

JSON(JavaScript Object Notation、JavaScript 对象表示法)是一种轻量级的数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。JSON 易于阅读和编写,同时也方便机器解析和生成,并且能够有效地提升网络传输效率。在网络数据传输领域,JSON 已成为了 XML 强有力的替代者。

JSON 数据类型
PostgreSQL 提供了两种 JSON 数据类型:JSON 以及 JSONB。这两种类型主要的区别在于数据存储格式,JSONB 使用二进制格式存储数据,更易于处理。

PostgreSQL 推荐优先选择 JSONB 数据类型。
在这里插入图片描述
由于存储格式的不同,JSONB 输入时稍微慢一些(需要转换),但是查询时快很多。

接下来的内容主要使用 JSONB 数据类型,但是大部分功能也可以使用 JSON 数据类型。
定义 JSON 字段
首先创建一个产品表 product:

CREATE TABLE product (id INTEGER NOT NULL PRIMARY KEY,product_name VARCHAR(100),attributes JSONB
);

产品表 product 中包含一个 JSONB 类型的字段 attributes,用于存储产品的属性。

JSON 字段赋值
我们可以直接使用字符串为 JSON 字段赋值,但是要求数据必须是有效的 JSON 格式,否则将会返回错误。

执行以下语句插入一条产品记录:

INSERT INTO product (id, product_name, attributes)
VALUES (1, '椅子','{"color":"棕色", "material":"实木", "height":"60cm"}');

接下来我们插入一条不符合 JSON 格式的数据:

INSERT INTO product (id, product_name, attributes)
VALUES (2, '沙发椅', '"color":"白色:50cm}');SQL 错误 [22P02]: ERROR: invalid input syntax for type json详细:Expected end of input, but found ":".位置:71在位置:JSON data, line 1: "color":...Error position: line: 2 pos: 70

以下语句插入了一条包含 JSON 数组的产品信息:

INSERT INTO product (id, product_name, attributes)
VALUES (2, '桌子','{"color":"黑色", "material":"金属", "drawers":[{"side":"左侧", "height":"30cm"}, {"side":"右侧", "height":"40cm"}]}'
);

我们使用同样的方法再创建一条记录:

INSERT INTO product (id, product_name, attributes)
VALUES (3, '茶几','{"color":"棕色", "material":["金属", "实木"]}'
);

以上方法虽然使用简单,但是输入比较麻烦。为此,PostgreSQL 提供了一些方便生产 JSON 数据的函数。

jsonb_build_object 函数可以通过一系列输入创建二进制的 JSON 对象,例如:

SELECT jsonb_build_object('color', '黑色', 'material', '塑料');jsonb_build_object               |
---------------------------------+
{"color": "黑色", "material": "塑料"}|

我们可以利用该函数插入数据,而不需要手动输入方括号、逗号、冒号等 JSON 符号。例如:

INSERT INTO product (id, product_name, attributes)
VALUES (4, '小型桌子', JSONB_BUILD_OBJECT('color', '黑色', 'material', '塑料'));

其他常用的构建 JSON 数据的函数如下:

json_build_object
to_json 以及 to_jsonb
array_to_json
row_to_json
json_build_array 以及 jsonb_build_array
json_object 以及 jsonb_object

查询 JSON 字段数据
JSON 字段的查询和普通字段没有什么区别,例如:

SELECT id, product_name, attributes
FROM product;id|product_name|attributes                                                                                                        |
--+------------+------------------------------------------------------------------------------------------------------------------+1|椅子          |{"color": "棕色", "height": "60cm", "material": "实木"}                                                               |2|桌子          |{"color": "黑色", "drawers": [{"side": "左侧", "height": "30cm"}, {"side": "右侧", "height": "40cm"}], "material": "金属"}|3|茶几          |{"color": "棕色", "material": ["金属", "实木"]}                                                                         |4|小型桌子      |{"color": "黑色", "material": "塑料"}       

获取单个属性
我们不仅可以查询整个 JSON 字段,也可以提取 JSON 数据中指定节点的属性值。例如:

SELECT id, product_name, attributes -> 'color' AS color
FROM product;id|product_name|color|
--+------------+-----+1|椅子          |"棕色" |2|桌子          |"黑色" |3|茶几          |"棕色" |4|小型桌子      |"黑色" |

运算符 -> 可以通过指定节点的键获取相应的数据。这种方法返回的数据仍然是 JSON 类型,使用双引号包含。

如果想要以字符串形式返回节点中的数据值,可以使用运算符 ->>。例如:

SELECT id, product_name, attributes -> 'color' AS color
FROM product;id|product_name|color|
--+------------+-----+1|椅子          |棕色   |2|桌子          |黑色   |3|茶几          |棕色   |4|小型桌子      |黑色   |

果查询的 JSON 节点不存在,将会返回空值:

SELECT id, product_name, attributes ->> 'height' AS height
FROM product;id|product_name|height|
--+------------+------+1|椅子          |60cm  |2|桌子          |      |3|茶几          |      |4|小型桌子      |      |

获取数组属性
属性 drawers 是一个 JSON 数组,我们同样可以查询它的内容:

SELECT id, product_name, attributes ->> 'drawers' AS drawers
FROM product
WHERE id = 2;id|product_name|drawers                                                             |
--+------------+--------------------------------------------------------------------+2|桌子        |[{"side": "左侧", "height": "30cm"}, {"side": "右侧", "height": "40cm"}]|

属性 drawers 包含了 2 个元素,每个元素代表一个抽屉,每个抽屉都拥有 2 个属性。

如果我们想要查看属性 drawers 中的第一个元素,可以多次使用 -> 运算符:


SELECT id, product_name, attributes -> 'drawers' -> 0 AS drawer1
FROM product
WHERE id = 2;id|product_name|drawer1                         |
--+------------+--------------------------------+2|桌子        |{"side": "左侧", "height": "30cm"}|

第一个 -> 运算符返回了 drawers 属性,第二个 -> 运算符返回了该属性中的第 1 个数组元素(数组下标从 0 开始)。

我们也可以使用另外两个运算符获取嵌套的属性,例如:

SELECT id, product_name, attributes #> '{drawers, 1}' AS drawer1, attributes #>> '{drawers, 1}' AS drawer1_text
FROM product
WHERE id = 2;id|product_name|drawer1                         |drawer1_text                    |
--+------------+--------------------------------+--------------------------------+2|桌子        |{"side": "右侧", "height": "40cm"}|{"side": "右侧", "height": "40cm"}|

运算符 #> 以及 #>> 可以通过指定 JSON 节点的路径获取嵌套属性,路径可以包含键的名称或者数组元素下标,返回类型分别为 JSON 和字符串。

基于 JSON 数据的过滤
如果我们想要查看颜色为棕色、材料为实木、高度为 60cm 的椅子,可以尝试使用以下查询语句:

SELECT id, product_name, attributes
FROM product
WHERE attributes = '{"color":"棕色", "material":"实木", "height":"60cm"}';id|product_name|attributes                                         |
--+------------+---------------------------------------------------+1|椅子        |{"color": "棕色", "height": "60cm", "material": "实木"}|

查询返回了我们期望的结果,因为查询条件中的字符串和 attributes 字段完全匹配,我们提供了完整的属性信息。

如果我们只想要基于某个属性(例如颜色)查找产品,这种方法就无法返回正确的数据了。例如:

SELECT id, product_name, attributes
FROM product
WHERE attributes = '{"color":"棕色"}';id|product_name|attributes|
--+------------+----------+
这种情况下我们可以使用前文查询属性的方法,例如:SELECT id, product_name, attributes
FROM product
WHERE attributes ->> 'color' = '棕色';id|product_name|attributes                                         |
--+------------+---------------------------------------------------+1|椅子        |{"color": "棕色", "height": "60cm", "material": "实木"}|3|茶几        |{"color": "棕色", "material": ["金属", "实木"]}          |
我们使用了 ->> 运算符,而不是 -> 运算符,因为前者返回的是字符串类型,后者返回的则是 JSON 数据类型。

JSON 转换为数据行
PostgreSQL 支持将 JSON 字段转换为数据行格式。例如,jsonb_each 函数可以将每个键值对转换为一个记录:

SELECT id, product_name, jsonb_each(attributes)
FROM product;id|product_name|jsonb_each                                                                                      |
--+------------+------------------------------------------------------------------------------------------------+1|椅子          |(color,"""棕色""")                                                                                |1|椅子          |(height,"""60cm""")                                                                             |1|椅子          |(material,"""实木""")                                                                             |2|桌子          |(color,"""黑色""")                                                                                |2|桌子          |(drawers,"[{""side"": ""左侧"", ""height"": ""30cm""}, {""side"": ""右侧"", ""height"": ""40cm""}]")|2|桌子          |(material,"""金属""")                                                                             |3|茶几          |(color,"""棕色""")                                                                                |3|茶几          |(material,"[""金属"", ""实木""]")                                                                   |4|小型桌子      |(color,"""黑色""")                                                                                |4|小型桌子      |(material,"""塑料""")                           

与此类似的函数还有:

jsonb_each_text
json_each 以及 json_each_text
json_array_elements 以及 json_array_elements_text
jsonb_array_elements 以及 jsonb_array_elements_text
我们还可以使用 json_object_keys 或者 jsonb_object_keys 函数获取 JSON 字段中的所有键的名称:

SELECT id, product_name, jsonb_object_keys(attributes)
FROM product;id|product_name|jsonb_object_keys|
--+------------+-----------------+1|椅子          |color            |1|椅子          |height           |1|椅子          |material         |2|桌子          |color            |2|桌子          |drawers          |2|桌子          |material         |3|茶几          |color            |3|茶几          |material         |4|小型桌子      |color            |4|小型桌子      |material         |

判断属性是否存在
PostgreSQL 还提供了一些用于判断 JSON 属性是否存在的运算符,例如 ? 运算符。

以下语句可以查找拥有 drawers 属性的产品:

SELECT id, product_name, attributes
FROM product
WHERE attributes ? 'drawers' = true;id|product_name|attributes                                                                                                        |
--+------------+------------------------------------------------------------------------------------------------------------------+2|桌子        |{"color": "黑色", "drawers": [{"side": "左侧", "height": "30cm"}, {"side": "右侧", "height": "40cm"}], "material": "金属"}|

更新 JSON 字段数据
使用 UPDATE 语句更新 JSON 字段时,可以通过 || 运算符将新的键值增加到已有 JSON 数据。例如:

UPDATE product
SET attributes = attributes || '{"width":"100cm"}'
WHERE id = 1;SELECT *
FROM product
WHERE id = 1;id|product_name|attributes                                                           |
--+------------+---------------------------------------------------------------------+1|椅子        |{"color": "棕色", "width": "100cm", "height": "60cm", "material": "实木"}|

新的属性 width 被添加到了数据的中间,因为 JSONB 数据类型不会保留键的顺序。

另外一种方法就是利用 jsonb_insert 方法,例如:

UPDATE product
SET attributes = jsonb_insert(attributes, '{"weight"}', '"1kg"')
WHERE id = 3;SELECT *
FROM product
WHERE id = 3;id|product_name|attributes                                                |
--+------------+----------------------------------------------------------+3|茶几        |{"color": "棕色", "weight": "1kg", "material": ["金属", "实木"]}|

如果想要更新已有键的数值,可以使用 jsonb_set 函数。例如:

UPDATE product
SET attributes = jsonb_set(attributes, '{height}', '"75cm"')
WHERE id = 1;SELECT *
FROM product
WHERE id = 1;id|product_name|attributes                                                           |
--+------------+---------------------------------------------------------------------+1|椅子        |{"color": "棕色", "width": "100cm", "height": "75cm", "material": "实木"}|

以上语句将 id 等于 1 的产品的属性 height 修改为 75cm。

删除 JSON 字段数据
删除整个 JSON 字段数据可以简单地将其设置为 NULL,例如:

UPDATE product
SET attributes = NULL
WHERE id = 4;SELECT *
FROM product
WHERE id = 4;id|product_name|attributes|
--+------------+----------+4|小型桌子      |          |

删除 JSON 字段中的某个属性可以使用 - 运算符,例如:

UPDATE product
SET attributes = attributes - 'height'
WHERE id = 1;SELECT *
FROM product
WHERE id = 1;id|product_name|attributes                                         |
--+------------+---------------------------------------------------+1|椅子        |{"color": "棕色", "width": "100cm", "material": "实木"}|

产品 1 中的 height 属性已经被删除了。

另一种删除 JSON 属性的方法是利用 jsonb_set_lax 函数,例如

UPDATE product
SET attributes = jsonb_set_lax(attributes, '{"width"}', null, false, 'delete_key')
WHERE id = 1;SELECT *
FROM product
WHERE id = 1;id|product_name|attributes                       |
--+------------+---------------------------------+1|椅子        |{"color": "棕色", "material": "实木"}|

函数中的第三个参数表示将 width 属性设置为空,第四个参数表示属性不存在时不创建新的属性,第五个参数表示删除被设置为空的属性。
全文索引

当我们基于 JSON 属性查询数据时,可能会存在性能问题。我们生成一批产品数据:

WITH RECURSIVE t AS (SELECT 10 n, '产品'||10 product_name, '{"color": "棕色", "height": "60cm", "material": "实木"}' attrUNION ALLSELECT n+1, '产品'||n+1, '{"color": "棕色", "height": "60cm", "material": "实木"}'FROM t WHERE t.n<10000
)
INSERT INTO product 
SELECT n, product_name, attr::jsonb FROM t;

然后查看以下语句的执行计划:

EXPLAIN
SELECT id, product_name, attributes
FROM product
WHERE attributes @> '{"color":"黑色"}';QUERY PLAN                                              |
--------------------------------------------------------+
Seq Scan on product  (cost=0.00..258.93 rows=1 width=78)|Filter: (attributes @> '{"color": "黑色"}'::jsonb)      |

以上语句表示查找拥有黑色的产品。执行计划显示使用了全表顺序扫描,数据量很小的时候没有问题。但是对于数据量大的表,查询速度可能会很慢。

为此,PostgreSQL 提供了支持 JSON 字段的全文索引,可以优化查询的性能。这种索引的类型为 GIN(通用倒排索引),通常用于搜索引擎。

我们可以基于 JSON 字段创建一个全文索引:

CREATE INDEX idx_product_attributes ON product USING GIN(attributes);

关键字 USING GIN 用于指定索引类型。

再次查看执行计划:

EXPLAIN
SELECT id, product_name, attributes
FROM product
WHERE attributes @> '{"color":"黑色"}';QUERY PLAN                                                                          |
------------------------------------------------------------------------------------+
Bitmap Heap Scan on product  (cost=20.00..24.01 rows=1 width=78)                    |Recheck Cond: (attributes @> '{"color": "黑色"}'::jsonb)                            |->  Bitmap Index Scan on idx_product_attributes  (cost=0.00..20.00 rows=1 width=0)|Index Cond: (attributes @> '{"color": "黑色"}'::jsonb)                        |

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/577704.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于Oracle VM VirtualBox无法查询IP地址的原因

1.如下&#xff0c;输入ifconfig却没有显示我框住的显示IP。 2.原因有可能&#xff1a; &#xff08;1&#xff09;主机没连上网络。 &#xff08;2&#xff09;虚拟机网络设置不正确。

跳跃游戏-java

题目描述: 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 解题思想: …

scGRN:人与鼠的GRN平台

基因调控网络GRN是包含转录因子TFs与其下游靶基因之间的调控相互作用的可解释图模型。了解GRN的拓扑结构和动力学是解释疾病病因机制和将相应发现转化为新疗法的基础。单细胞多组学技术的最新进展促使从单细胞转录组学和表观基因组学数据中以前所未有的分辨率推断GRN。在这里&a…

HTX Ventures:为什么BounceBit可能成为新的BTC生态解决方案?

随着BTC现货ETF的通过&#xff0c;全球各大机构和个人都在不断加码对BTC的持仓&#xff0c;BTC价格也随之上升&#xff0c;目前已上升至全球市值排名前十的资产。在本轮市场周期中&#xff0c;BTC铭文和BTC扩容是两个被市场高度关注的细分赛道。BTC生态资产的多元化收益探索正在…

【Godot4自学手册】第三十一节使用WorldEnvironment为地宫入口粒子系统添加辉光

本节&#xff0c;首先我将使用WorldEnvironment节点为地宫入口的例子系统添加辉光&#xff0c;让游戏看上去效果更加灿烂。其次加入相应提示信息&#xff0c;白天到达地宫附近、未杀死怪物进入地宫&#xff0c;都有提示信息&#xff0c;达到条件后地宫方可进入。先看一下效果&a…

关于Kubernetes-v1.23.6-资源调度-StatefulSet-OnDelete当删除的时候才更新

前面提到的普通的滚动更新&#xff0c;都是修改完sts立即就会发生更新操作 而还有一种更新的策略为&#xff0c; OnDelete&#xff0c;即只有在 pod 被删除时会进行更新操作 还是先看一下web这个sts的当前更新策略如下&#xff1a; 这里我们修改&#xff0c;更新策略&#xf…

Rsyslog 日志集中管理实验

1.使用 进行日志集中管理 C/S 架构&#xff1a;客户端将其日志上传到服务器端&#xff0c;通过对服务器端日志的查询&#xff0c;来实现对其他客户端的日志进行集中管理 2.两台机器&#xff1a; &#xff08;server&#xff09;host-5(192.168.1.2)<------------>(192…

Android14之深入理解sp模板类(二百零二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

realsense标定

简介 参考&#xff1a; camera_calibration/Tutorials/MonocularCalibration - ROS Wiki 结果如下&#xff1a; 具体操作 安装 ros安装 先是安装ros 使用鱼香ros一键安装 推荐换源&#xff0c;哪怕有魔法 小鱼的一键安装系列 | 鱼香ROS wget http://fishros.com/inst…

<QT基础(4)>QLabel使用笔记

Label 前面的文章里面把QLabel批量引入ScrollArea作为预览窗口&#xff0c;这篇把图像填充到QLable的PixelMap展示指定图像。 参数设置 设置QLabel的大小格式 QWidget* widget new QWidget; widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); widget->…

第二百三十回

文章目录 概念介绍添加方法示例代码指示器联动 我们在前面章回中介绍了PageView这个Widget,本章回中将介绍如何给PageView添加指示器.闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 我们在这里说的指示器表示PageView底部的小圆圈&#xff0c;它用来指示当前哪个页…

集合,排序查找算法,可变参数

文章目录 集合Set集合TreeSet集合 Map集合概述特点子类及其底层数据结构常用方法遍历 数据结构常见的数据结构二叉树 可变参数介绍格式注意 Collections工具类方法 排序查找算法冒泡排序介绍原理注意代码 选择排序介绍原理规律代码 二分查找前提介绍原理注意代码 集合 Set集合 …