1. 程式人生 > SQL入門教學 >實戰7:PostgreSQL JSON資料型別大探

實戰7:PostgreSQL JSON資料型別大探

1. 前言

在正式的小節學習之前,我們先來探討一個問題,你究竟是否有必要使用類似於MongoDB這樣的文件性資料庫?

這些年,NoSQL以及NewSQL都颳起過一番浪潮,而SQL終究還是巋然不動,不僅沒有被打垮,反而變得更加大。PostgreSQL號稱世界上最先進的關係資料庫,很早的時候便已經開始支援文件性資料型別了,而且在9.3以後的每一個版本,都提供了更多的新特性。

PostgreSQL 最重要的文件性資料型別就是JSON了,與 MongoDB 的BSON相比較,PostgreSQL 或許更加強大,因為它能與原有的關係性正規化相容,給資料庫儲存與維護帶來了更多的可行性和便利性。

PostgreSQL 的JSON

型別功能十分強大,不僅支援基本的增刪查改,屬性判斷,還支援索引和搜尋;當然 MongoDB 也有無可替代的特性,不僅功能強大,而且天然分散式。如果你對文件資料儲存的特性並沒有太高的要求,且需要與原來的關係資料庫相容,那麼 PostgreSQL 或許是你更好的選擇。

2. JSON 資料型別

JSON 資料型別幾乎已經是現在Web開發的標配了,MySQL5.7以後也提供了它的支援,不過即使到現在,MySQL 對於 JSON 的支援也有限,而 PostgreSQL 對 JSON 的支援十分強大。

PostgreSQL 在 9.3 版本對 JSON 做了顯著功能增強外,在 9.4 引入了JSONB

型別,JSONB 型別是 JSON 型別的二進位制版,不僅儲存空間更小,效能更好,而且還支援索引,在 12 這個大版本中,直接引入了 JSON path 特性來方便的操作 JSON 資料,讓 JSON 的操作更加方便和有效。

接下來,就讓我們一起來學習 PostgreSQL 的 JSON 型別吧。

提示: 本文使用的 PostgreSQL 版本為12.1

3. json 與 jsonb

PostgreSQL 支援兩種 JSON 資料型別:jsonjsonb。二者在使用上幾乎無差異,主要區別是 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 的知識點真的很多,本小節介紹了常用的,如果你有興趣,可以查閱一番官網