實戰7:PostgreSQL JSON資料型別大探
1. 前言
在正式的小節學習之前,我們先來探討一個問題,你究竟是否有必要使用類似於MongoDB
這樣的文件性資料庫?
這些年,NoSQL
以及NewSQL
都颳起過一番浪潮,而SQL終究還是巋然不動,不僅沒有被打垮,反而變得更加大。PostgreSQL
號稱世界上最先進的關係資料庫,很早的時候便已經開始支援文件性資料型別了,而且在9.3
以後的每一個版本,都提供了更多的新特性。
PostgreSQL 最重要的文件性資料型別就是JSON
了,與 MongoDB 的BSON
相比較,PostgreSQL 或許更加強大,因為它能與原有的關係性正規化相容,給資料庫儲存與維護帶來了更多的可行性和便利性。
PostgreSQL 的JSON
2. JSON 資料型別
JSON 資料型別幾乎已經是現在Web
開發的標配了,MySQL
在5.7
以後也提供了它的支援,不過即使到現在,MySQL 對於 JSON 的支援也有限,而 PostgreSQL 對 JSON 的支援十分強大。
PostgreSQL 在 9.3 版本對 JSON 做了顯著功能增強外,在 9.4 引入了JSONB
接下來,就讓我們一起來學習 PostgreSQL 的 JSON 型別吧。
提示: 本文使用的 PostgreSQL 版本為
12.1
。
3. json 與 jsonb
PostgreSQL 支援兩種 JSON 資料型別:json
與jsonb
。二者在使用上幾乎無差異,主要區別是 json 儲存的是文字格式,而 jsonb 儲存的是二進位制格式。因此:
- json 在插入時不需要額外處理,而 jsonb 需要處理為二進位制,所以 json 的插入比 jsonb 要快;
- jsonb 以二進位制來儲存已經解析好的資料,在檢索的時候不需要再額外處理,因此檢索的效能比 json 要好;
- 另外 jsonb 支援索引,若無特殊需求,推薦使用 jsonb。
我們來實操一下二者的使用吧。
3.1 使用 json
首先,我們看一下 json:
SELECT '{"username":"pedro","age":23}'::json;
json
-------------------------------
{"username":"pedro","age":23}
在 PostgreSQL 中::
符號用於型別轉換,該語句將字串'{"username":"pedro","age":23}'
,通過型別轉換為json
,得到了 json 資料結果。
前面,我們談到 json 以文字格式儲存資料,且插入較快,那麼是不是真的如此了?
SELECT '{"username":"pedro", "age": 23}'::json;
json
----------------------------------------
{"username":"pedro", "age": 23}
Time: 0.221 ms
3.2 使用 jsonb
從結果可以看出 json 確實以文字格式儲存了資料,多餘的空格依舊存在,那麼再看 jsonb:
SELECT '{"username":"pedro", "age": 23}'::jsonb;
jsonb
----------------------------------
{"age": 23, "username": "pedro"}
Time: 0.265 ms
可以看到,jsonb 處理多餘的空格,因此消耗的時候多了那麼一點,在實際的測試中,json 的插入效能確實比 jsonb 要高。
4. JSON 型別增刪查改
由於 json 和 jsonb 的操作幾乎一致,但 jsonb 更為增大,支援更多的特性,因此我們以 jsonb 為例,來看一看它是如何進行增刪查改的。
首先,我們新建測試表:
CREATE TABLE movie (
id serial PRIMARY KEY,
info jsonb
);
在 movie 表中,id 是自增的主鍵,而 info 欄位是我們的主角,資料型別是jsonb。
4.1 增
由於 id 是 serial 型別,即自增,因此我們只需插入 info 資料即可:
INSERT INTO movie (info)
VALUES('{ "title": "我是路人甲", "rate": 7.4, "category": ["劇情","喜劇"]}'),
('{ "title": "鐵拳","rate": 7.1, "category": ["劇情","動作","運動"]}');
在資料插入的時候,資料庫會自動地將字串轉化為 jsonb 型別儲存,當然如果插入的資料不滿足 json 格式會報錯。
4.2 查
4.2.1 json 路徑操作符查詢
PostgreSQL 支援我們以 json 路徑的形式來查詢 json 資料,如查詢 info 下的 title 欄位:
SELECT info->'title' FROM movie;
?column?
--------------
"我是路人甲"
"鐵拳"
上面,我們使用了->
加上屬性名的方式,訪問到了title
,當然你也可以這樣訪問:
SELECT info->>'title' FROM movie;
?column?
------------
我是路人甲
鐵拳
->
與->>
二者是有區別的,->
返回的是 jsonb 型別,而->>
返回的是文字型別。
我們還可以通過下標來返回陣列物件:
SELECT info->'category'->0 from movie;
?column?
----------
"劇情"
"劇情"
4.2.2 json 路徑陣列查詢
PostgreSQL 還支援路徑陣列的形式來訪問資料:
SELECT info#>array['category','1'] from movie;
?column?
----------
"喜劇"
"動作"
4.3 改
4.3.1 新增 json 欄位
我們也可以通過 Update 指令,來新增 json 欄位:
UPDATE movie SET info = info || '{"showtime": 2015.0}'::jsonb WHERE id = 1;
id | info
----+----------------------------------------------------------------------------------------
1 | {"rate": 7.4, "title": "我是路人甲", "category": ["劇情", "喜劇"], "showtime": 2015.0}
jsonb 支援||
操作符來合併 jsonb 欄位,但json
型別由於是文字格式,所以不支援這種方式,你只能重新 SET 新的文字。
4.3.2 刪除 json 欄位
通過-
我們可以刪除 jsonb 中的某個欄位:
UPDATE movie SET info = info - 'showtime'WHERE id = 1;
id | info
----+--------------------------------------------------------------------
1 | {"rate": 7.4, "title": "我是路人甲", "category": ["劇情", "喜劇"]}
4.4 刪
我們可以直接通過 Delete 指令來刪除記錄,但是一般不能刪除所有記錄,因此我們需要搭配 Where 來刪除。
那麼 Where 如何來過濾jsonb
欄位裡面的值了?
4.4.1 jsonb 匹配運算子
jsonb
支援多種匹配運算子,常見的有:
匹配運算子 | 作用 | 說明 |
---|---|---|
= | 等值比較 | 比較兩個 json 是否相等 |
@> | 包含關係判定符 | 判斷 json 中是有含有某些欄位 |
<@ | 被包含關係判定符 | 判斷 json 是否被另一個 json 包含 |
? | 鍵值存在判定符 | 判斷 json 中是否存在某個鍵 |
4.4.2 使用匹配運算子
如我們可以使用@>
來查詢名稱為我是路人甲
的電影評分:
SELECT info->'rate' FROM movie WHERE info @> '{"title":"我是路人甲"}';
?column?
----------
7.4
因此,我們也可以使用這樣的方式來刪除:
DELETE FROM movie WHERE info @> '{"title":"我是路人甲"}';
注意: PostgreSQL 的 JSON 資料型別操作實則很複雜,需要大量的篇章來介紹,我們無法在一個實戰小節來覆蓋,如果你感興趣,可以閱讀官方文件,或者 PostgreSQL中文網。
5. jsonb 索引
前面我們說到,與 json 型別相比,jsonb 額外支援索引,這也是為什麼推薦你使用 jsonb 的原因,因為資料量一旦大起來,沒有索引
的查詢會十分緩慢。
5.1 建立 jsonb 索引
jsonb 建立索引也十分簡單,以上面的 movie 表為例:
CREATE INDEX movie_info_gin_index ON movie USING gin(info);
movie_info_gin_index
是索引名稱,gin(info)
括號裡面的 info 表示使用 movie 表中的 info 欄位建立索引。
5.2 索引操作
jsonb 上的 gin 索引操作有一定的限制,它支援以下幾個操作符:
- 包含關係判定符
@>
:判斷 json 中是有含有某些欄位 - 鍵值存在判定符
?
:判斷 json 中是否存在某個鍵 - 一組鍵值均存在判定符
?&
:判斷 json 中是否存在一組鍵 - 一組鍵值任意一個存在判定符
?|
:判斷 json 中是否存在一組鍵中的任意一個鍵
5.3 使用索引
例如以下查詢將會使用索引:
查詢包含 title 為 鐵拳的記錄。
SELECT info FROM movie WHERE info @> '{"title":"鐵拳"}';
查詢包含 title 的記錄。
SELECT info FROM movie WHERE info ? 'title';
查詢包含 title 或 category 的記錄。
SELECT info FROM movie WHERE info ?| array['title','category'];
5.4 額外索引
但是如果你使用->>
操作符,則不會走索引。
SELECT info FROM movie WHERE info->>'title' = '鐵拳';
若要支援->>
索引,你必須為它也建立單獨的索引,如下:
CREATE INDEX movie_info_title_index ON movie USING btree((info ->> 'title'));
6. 小結
關於 PostgreSQL JSON 的介紹到這裡也將告一段落了,我們總結一下:
- jsonb 的支援明顯優於 json,推薦你在第一位上選擇
jsonb
。 - PostgreSQL 在 json 上的支援完全能夠媲美 MongoDB 等 NoSQL 資料庫,你完全可以嘗試一下。
- PostgreSQL JSON 的知識點真的很多,本小節介紹了常用的,如果你有興趣,可以查閱一番官網。