Skip to main content

JSON in PostgreSQL

· 5 min read

PostgreSQL 有许多存储、处理 JSON 数据的功能。本文将讲述:

  • JSON 是什么,为什么值得使用。
  • 创建一张 table 来储存 JSON。
  • JSON 数据的增删改查
  • 性能、校验、处理 JSON 数据的一些建议。

JSON 是什么​

JSON 是 JavaScript Object Notation 的缩写,是一种格式化和储存数据的方式。与 HTMl 或 XML 类似,使用一种适合人类阅读的特定格式来展现数据,最初是为了程序能够使用数据来设计的。

为什么使用 jSON​

因为需要一种灵活的结构。
一个规范化的数据库结构,包含数据表、列、关系,适用于大多数情况。最近开发实践的改进,也意味着改变一个表并不像过去那样重要,所以一旦你的数据库投入生产,调整它是可能的。
但如果你的需求,要求数据比较灵活,JSON 字段比较适合。
一个典型的例子是,用户可能添加自定义属性。如果是规范化的数据库,这边需要修改表,或者创建一个实体属性(Entity Attribute),或其他一些方法。
如果使用 JSON 字段,增加、维护这些自定义属性将会变得十分容易。
JSON 数据同样可以被 ORM 和代码处理。所以数据库不需要其他额外工作。

JSON 数据的样式​

对象:

{
"id": "1",
"username": "jsmith",
"location": "United States"
}

包含数组:

{
"username": "jsmith",
"location": "United States",
"posts": [
{
"id":"1",
"title":"Welcome"
},
{
"id":"4",
"title":"What started it all"
}
]
}

创建 JSON 字段​

两种类型​

PostgreSQL 中有两种 JSON 的数据类型:JSON 和 JSONB。

  • JSON 是常规 JSON 类型,在 9.2 版本中加入。
  • JSONB 用来存储二进制 JSON,9.4 版本加入。

区别主要在于数据如何存储。JSONB 数据以二进制格式存储,更容易被处理。
PostgreSQL 推荐在大多数情况下使用 JSONB 格式。

FeatureJSONJSONB
存储保持原状以分解的二进制格式存储
支持全文索引NoYes
保留空格YseNo
保留 key 的顺序YseNo
保留重复的 keyYseNo(保留最后一个 key)
优于存储结构的原因,JSONB 数据存储较慢(需要转换),但处理较快。
本文将使用 JSONB 数据类型。大部分功能也同样适用于 JSON 类型。

创建 JSON 字段​

CREATE TABLE product (
id INT,
product_name CHARACTER VARYING(200),
attributes JSONB
);

attributes 是 JSONB 类型,所以要求传入的必须是有效的 JSON 数据。其他数据库需要校验,而 postgreSQL 不需要。

Insert​

INSERT INTO product (id, product_name, attributes)
VALUES (1, 'Chair','{"color":"brown", "material":"wood", "height":"60cm"}');

Insert Array​

INSERT INTO product (id, product_name, attributes)
VALUES (
2,
'Desk',
'{"color":"black", "material":"metal", "drawers":[{"side":"left", "height":"30cm"}, {"side":"left", "height":"40cm"}]}'
);

JSON 函数​

JSONB_BUILD_OBJECT 函数接收一系列值,以二进制格式创建 JSON 对象。

JSONB_BUILD_OBJECT('color', 'black', 'material', 'plastic')

结果:

{"color": "black", "material": "plastic"}

使用 JSONB_BUILD_OBJECT 函数的好处是无需担心括号、冒号,引号、逗号等等。

INSERT INTO product (id, product_name, attributes)
VALUES (4, 'Small Table', JSONB_BUILD_OBJECT('color', 'black', 'material', 'plastic'));

其他一些帮助 insert JSON 的函数:

  • to_json and to_jsonb
  • array_to_json
  • row_to_json
  • json_build_array and jsonb_build_array
  • json_object and jsonb_object

Read and Filter​

select 一个 JSON 字段很简单:

SELECT id,
product_name,
attributes
FROM product;

Select 单个属性​

通过 -> 来获取单个属性,->> 仅获取 value,无引号。如果没有对应字段,返回 null。

SELECT id,
product_name,
attributes -> 'color' AS color_key,
attributes ->> 'color' AS color_value
FROM product;

Select Array​

-> [index] 获取指定元素,#>> 是另一种语法

SELECT id,
product_name,
attributes ->> 'drawers' AS drawers,
attributes -> 'drawers' -> 1 AS drawer_value,
attributes #> '{drawers, 1}' AS drawers_element,
attributes #>> '{drawers, 1}' AS drawers_text
FROM product;

Filtering​

LIKE 不能使用。

SELECT id,
product_name,
attributes
FROM product
WHERE attributes = '{
"color": "brown", "material": "wood", "height": "60cm"}';

使用 ->> 来对字符串进行筛选

SELECT id,
product_name,
attributes
FROM product
WHERE attributes ->> 'color' = 'brown';

拆分 JSON 到多行​

JSONB_EACH 会把key value 对展示为元组,每个 key 占据一行。虽然这并没有什么卵用。

SELECT id,
product_name,
JSONB_EACH(attributes)
FROM product;

JSON_OBJECT_KEYS 用以获取 JSON 字段的 key,也会拆分多行:

SELECT id,
product_name,
JSONB_OBJECT_KEYS(attributes)
FROM product;

如果想要找到 包含指定 key 的行,或者是想统计 key 的数量,会很有帮助。

SELECT JSONB_OBJECT_KEYS(attributes) AS attr_key,
COUNT(*)
FROM product
GROUP BY JSONB_OBJECT_KEYS(attributes)
ORDER BY COUNT(*) DESC;

检查 key 是否存在​

? 可以用来检查 JSON 是否含有指定 key

SELECT id,
product_name,
attributes
FROM product
WHERE attributes ? 'drawers' = true;

Update​

Insert new key​

UPDATE product
SET attributes = attributes || '{"width":"100cm"}'
WHERE id = 1;

JSONB_SET:更新已有 value​

UPDATE product
SET attributes = JSONB_SET(attributes, '{height}', '"75cm"')
WHERE id = 1;

Delete​

删除指定行​

DELETE FROM product
WHERE attributes ->> 'color' = 'brown';

删除 JSON 字段的指定属性​

UPDATE product
SET attributes = attributes - 'height'
WHERE id = 1;

Performance​

GIN 全文索引​

GIN: Generalised Inverted Index.

CREATE INDEX idx_prod_json ON product USING GIN(attributes);

Tips​

  1. 不要因为可以,便使用。
  2. 将 JSON 字段视为 black box。
  3. 使用 PK 或其他字段来检索。

Ref:​

https://www.databasestar.com/postgresql-json/