1. 程式人生 > >pgsql json,jsonb

pgsql json,jsonb

關於pgsql 的json 和jsonb 的資料處理筆記

  1. json 和jsonb 區別
    兩者從使用者操作的角度來說沒有區別,區別主要是儲存和讀取的系統處理(預處理)和耗時方面有區別。json寫入快,讀取慢,jsonb寫入慢,讀取快。

  2. 常用的操作符

操作符:

-> // 右邊傳入整數(針對純陣列),獲取陣列的第n個元素,n從0開始算,返回值為json

示例: select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2 // 輸出 {"c":"baz"}

-> // 右邊傳入鍵值(針對關聯陣列),獲取陣列的第n個元素,n從0開始算,返回值為json

示例: select '{"a": {"b":"foo"}, "c":{"a": "aaa"}}'::json->'a' // 輸出 {"b":"foo"}

->> // 右邊傳入整數(針對純陣列),獲取陣列的第n個元素,n從0開始算,返回值為文字

示例: select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->>2 // 輸出 {"c":"baz"}

->> // 右邊傳入鍵值(針對關聯陣列),獲取陣列的第n個元素,n從0開始算,返回值為文字

示例:select '{"a": {"b":"foo"}, "c":{"a": "aaa"}}'::json->>'a' // 輸出 {"b":"foo"}

// 獲取json子物件,傳入陣列,返回json

示例: select '{"a": {"b":{"c": "foo"}}}'::json#> '{a,b}' // 輸出 {"c": "foo"}

// 獲取json子物件並轉換為文字,傳入陣列,返回文字

示例: select '{"a": {"b":{"c": "foo"}}}'::json#>> '{a,b}' // 輸出 {"c": "foo"}

  1. 操作函式
    目前pgsql版本提供了兩套函式分別處理,可以通用,名稱也差不多,比如 json_each 和 jsonb_each , json_array_elements 和 jsonb_array_elements 。

json相關的處理函式比較多,常用的有如下三個,這三個基本夠用了

json_object_keys // 返回json的鍵(多層只返回第一層),該函式不能用於純陣列.

json_array_elements // 提取轉換純陣列元素

json_extract_path // 返回JSON值所指向的某個鍵元素(相當於 #> 操作符),該函式不能直接操作純陣列。

需要注意的是如果你建立欄位用的是json就用json相關函式,如果建立欄位用的是jsonb就用jsonb相關函式。

json_object_keys 函式示例:

select json_object_keys ('
{
"goods":
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"}
], 
"quantity": "10"
}
')

輸出:

json_object_keys 函式示例:

select json_object_keys ('
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"}
], 
')

輸出:

ERROR: cannot call json_object_keys on an array // 不能用於陣列

json_array_elements 函式 示例:

select json_array_elements ('
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"},
{"id": "111a13d3-0225-4431-b858-678c3cfea999", "weight": "3", "quantity": "11"}
]
')

我們看到json資料被分離成三條記錄,這時我們就可以對其進行查詢操作,

比如查詢是否包含了weight=3的資料。

select * from json_array_elements ('
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"},
{"id": "111a13d3-0225-4431-b858-678c3cfea999", "weight": "3", "quantity": "11"}
]
') as jae
where jae::jsonb->>'weight' = '3' 

輸出:

我們看到這樣就可以到對json資料內部進行查詢了。

json_extract_path 函式示例:

比如要獲取鍵 ‘goods’ 的值:

{
"goods":
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"}
], 
"quantity": {"max": "150", "min": "2"}
}

select json_extract_path   ('
{
"goods":
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"}
], 
"quantity": {"max": "150", "min": "2"}
}
' , 'goods' )  ;   // 第二個引數表示獲取鍵為goods的值

輸出:

json_extract_path 函式和 pgsql 提供的操作符 #> 是一樣的。

select ('
{
"goods":
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"}
], 
"quantity": {"max": "150", "min": "2"}
}
') ::json #> '{goods}'

兩者的輸出是一致的。

同樣我們要輸出 鍵quantity 下鍵max 的值:

select json_extract_path   ('
{
"goods":
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"}
], 
"quantity": {"max": "150", "min": "2"}
}
' , 'quantity','max' ) ;   

-- 或

select ('
{
"goods":
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"}
], 
"quantity": {"max": "150", "min": "2"}
}
') ::json #> '{quantity, max}

兩者輸出是一樣的。

這幾個函式是可以聯合使用的。

比如我們要查詢 鍵“goods” 下weight =2 的id 和quantity 值,語句如下:

select jae::json->>'id' as id, jae::json->>'quantity' as quantity from json_array_elements (
json_extract_path ('
{
"goods":
[ 
{"id": "676a13d3-0225-4431-b858-678c3cfeab74", "weight": "1", "quantity": "9999999"},
{"id": "111a13d3-0225-4431-b858-678c3cfeab75", "weight": "2", "quantity": "33"}
], 
"quantity": {"max": "150", "min": "2"}
}
' , 'goods' ) ) as jae where jae::json->> 'weight' = '2'

輸出:

上述的json語句我們可以當做欄位來使用,就相當於對錶記錄進行操作了。

接下來我們同個一個例子講解json在表中的用法:

示例:查詢表中jsonb_msg欄位中goods下id=1003和1002的記錄,表中輸入如下圖:

sql查詢語句:

select name,* from upgrade_test.test1 test
where 
( select count(*) from jsonb_array_elements (
jsonb_extract_path(test.json_msg , 'goods' ) ) as jae where jae::json->> 'id' in ('1001','1003') ) > 0 ; 

輸出:

效率還行:

Total query runtime: 11 msec
檢索到 2 行。

官方文件頁:

PostgreSQL9.4 新增 JSONB 資料型別, JSONB 同時屬於 JSON (JavaScript Object Notation) 資料型別,jsonb 和 json 的輸入資料幾乎完全通用,最大的差別體現在效率上,json 儲存的資料幾乎和輸入資料一樣,儲存的是未解析的資料,呼叫函式時使用效率較低; 而 jsonb 儲存的是分解的 binary 格式資料,使用時不需要再解析了,因此使用上效率較高; 另一方面 json 在寫入時較快,而 jsonb 寫入時由於需要轉換導致寫入較慢。下面通過些簡單的例子瞭解兩者的差異。

–1 這個例子兩者沒啥差異
francs=> SELECT '[1, 2, "foo", null]'::json;

json

[1, 2, “foo”, null]
(1 row)

francs=> SELECT '[1, 2, "foo", null]'::jsonb;

jsonb

[1, 2, “foo”, null]
(1 row)
備註: json 型別輸出的內容和寫入的內容一樣,不會對輸出的結果改變,而 jsonb不一樣,看下面的例子。

–2 jsonb 輸出內容順序不一樣
francs=> SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::json;

json

{“bar”: “baz”, “balance”: 7.77, “active”:false}
(1 row)

francs=> SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb;

jsonb

{“bar”: “baz”, “active”: false, “balance”: 7.77}
(1 row)

–3 jsonb: 整數型別輸出不一樣
francs=> SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
json | jsonb
———————–+————————-
{“reading”: 1.230e-5} | {“reading”: 0.00001230}
(1 row)

–4 jsonb: 去掉了空格
francs=> select ' {"id":1,
"name":"francs",
"remark":"a good guy!"
}'::json;

json

{“id”:1, +
“name”:”francs”, +
“remark”:”a good guy!”+
}
(1 row)

francs=> select ' {"id":1,
"name":"francs",
"remark":"a good guy!"
}'::jsonb;

jsonb

{“id”: 1, “name”: “francs”, “remark”: “a good guy!”}
(1 row)

–5 jsonb: 重複的元素值僅保留最後一個
francs=> select ' {"id":1,
"name":"francs",
"remark":"a good guy!",
"name":"test"
}'::jsonb;

jsonb

{“id”: 1, “name”: “test”, “remark”: “a good guy!”}
(1 row)
備註: json 型別的輸出和輸入一樣,會保留所有重複的元素,而 jsonb 對於重複的元素僅保留最後出現的重複元素。

–6 關於索引

GIN 索引支援 jsonb 型別,支援大的 jsonb 表中基於 keys 或者 key/values 模式的檢索。
預設的 GIN 索引模式支援帶有 @>, ?, ?& 和 ?| 操作的查詢,關於這些操作符的含義參考本文的附錄。

假如有一個文件:

{
    "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,建立如下索引

CREATE INDEX idx_gin_api_jdoc ON api USING gin (jdoc);

那麼如下的查詢可以使用索引

-- Find documents in which the key "company" has value "Magnafone"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"company": "Magnafone"}';

備註:上面這個例子來自手冊。

7 附 Additional jsonb Operators

Operator Right Operand Type Description Example
= jsonb Are the two JSON values equal? ‘[1,2,3]’::jsonb = ‘[1,2,3]’::jsonb
@> jsonb Does the left JSON value contain within it the right value? ‘{“a”:1, “b”:2}’::jsonb @> ‘{“b”:2}’::jsonb
<@ jsonb Is the left JSON value contained within the right value? ‘{“b”:2}’::jsonb <@ ‘{“a”:1, “b”:2}’::jsonb
? text Does the key/element string exist within the JSON value? ‘{“a”:1, “b”:2}’::jsonb ? ‘b’
? text[] Do any of these key/element strings exist?
?& text[] Do all of these key/element strings exist? ‘[“a”, “b”]’::jsonb ?& array[‘a’, ‘b’]

8 參考
WAITING FOR 9.4 – INTRODUCE JSONB, A STRUCTURED FORMAT FOR STORING JSON
PostgreSQL 9.4 Beta 1釋出,支援JSONB
PostgreSQL 9.4 new data type jsonb - do not need to reparser when used
JSON Types