實戰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 的知識點真的很多,本小節介紹了常用的,如果你有興趣,可以查閱一番官網。
