PostgreSQL 9.3 以后支持 json/jsonb 格式。看来 json 是稳坐交换格式第一把座椅了。 json 和 jsonb 的主要区别是 json 按文本格式存储,本质上是 text 格式加上语法检查而已。jsonb 是按二进制存储,速度更快(省去转换过程),功能更多(支持索引,查找等功能)。所以基本上 json 格式没什么特别的价值,这里我们主要研究 jsonb 格式。
读取操作
首先是读取子项(支持 Object 和 Array 的读取):
1
2
3
4
5
6
7
8
|
postgre=> select '{"foo":1, "bar":2}'::jsonb->'bar';
?column?
----------
2
postgre=> select '["foo", "bar"]'::jsonb->1;
?column?
----------
"bar"
|
然后是按 path 方式读取:
1
2
3
4
|
postgre=> select '{"foo":[1,2], "bar":2}'::jsonb#>'{foo,1}';
?column?
----------
2
|
比较操作
@> 比较操作符,用以比较左边的 object/array 是不是包含了右边所有的键和值 (右边的::jsonb可以省略,系统会自动转换):
1
2
3
4
|
select '[1, 2, 3]'::jsonb @> '[1, 2]'::jsonb; # true
select '[1, 2]'::jsonb @> '[1, 2, 3]'::jsonb; # false
select '{"foo": 1, "bar": 2}'::jsonb @> '{"foo": 3}'::jsonb; # false
select '{"foo": 1, "bar": 2}'::jsonb @> '{"foo": 1}'::jsonb; # true
|
** ? 检查存在操作符,对 object 检查 键 是否存在,对 array 检查 值 是否存在,对原生类型检查是否匹配:**
1
2
3
|
select '{"foo": 1, "bar": 2}'::jsonb ? 'foo'; # true
select '["foo", "bar"]'::jsonb ? 'foo'; # true
select '"foo"'::jsonb ? 'foo'; # true
|
关于索引
索引是查找速度的保证,由于 jsonb 是一种复合型结构,对它进行索引跟对原生类型有些区别。需要根据我们的业务逻辑中对查找方式去进行设计。
针对 @> 操作符,可以直接建立对列进行 gin 索引:
1
|
CREATE INDEX index_name ON table_name USING gin ( json_column_name );
|
这个索引会在我们使用 @> 操作时被使用:
1
|
SELECT * FROM table_name WHERE json_column_name @> 'josn_string';
|
针对需要对 json_column_name 下面某个子元素进行比较的情况:
1
|
CREATE INDEX index_name ON table_name USING gin ( json_column_name_and_path );
|
相应的,当执行以下查询的时候这个索引会被使用:
1
|
SELECT * FROM table_name WHERE json_column_name->sub_key ? 'key';
|
假如我们有个 table 叫 api,里面有个列叫 jdoc,jdoc 的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{
"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"
]
}
|
然后建立索引:
1
2
|
CREATE INDEX idx_jdoc ON api USING gin (jdoc);
CREATE INDEX idx_jdoc_tags ON api USING gin ((jdoc->'tags'));
|
执行查询:
1
2
3
|
SELECT * FROM api WHERE jdoc @> '{"company": "Magnafone"}'; # 这里会使用到 idx_jdoc 索引;
SELECT * FROM api WHERE jdoc->'tags' ? 'qui'; # 这里会使用到 idx_jdoc_tags 索引;
SELECT * FROM api WHERE jdoc @> '{"tags": ["qui"]}'; # 这里会使用到 idx_jdoc 索引;
|
我们注意到第三条SQL可以覆盖到 idx_jdoc_tags 索引的应用场景,但像这种简单的包含判断可能很多时候不够用, idx_jdoc_tags 还是可以有很多应用场景的。另外由于 idx_jdoc 会索引 json 对象所有的键值,生成的数据会比较多,速度相对也没有 idx_jodc_tags 快。idx_jdoc_tags 只索引 tags 部分的值。