如何更新Postgresql的Jsonb數組
假設你決定將數據以json或者jsonb的形式存儲在數據庫中,然後發現你剛剛給自己製造了新的問題,而這些問題是以前沒有的。
jsonb是一個強大的工具,但它也有一定的代價,因爲你需要調整查詢和處理數據的方式。
而且將整個jsonb對象加載到內存中,用你喜歡的編程語言進行轉換,然後將其保存回數據庫,這並不罕見。但是,你剛剛創造了另一個問題:性能瓶頸和資源浪費。
在這篇文章中,我們來看看如何通過一次查詢來更新數組內對象的特定值。
假設你正在實現一個爲每個客戶存儲動態聯繫人功能,那麼你就會想到將聯繫人存儲爲jsonb列,因爲他們是動態的,因此使用非關係型數據結構是有意義的。
然後創建一個帶有jsonb列聯繫人,並在其中插入一些數據。
create table customers (name varchar(256), contacts jsonb);
insert into customers (name, contacts) values (
'Jimi',
'[
{"type": "phone", "value": "+1-202-555-0105"},
{"type": "email", "value": "[email protected]"}
]'
);
insert into customers (name, contacts) values (
'Janis',
'[
{"type": "email", "value": "[email protected]"}
]'
);
看起來很簡單,但是如何更新特定的聯繫人信息?如何更改jimi的電子郵件或者janis的電話?
幸運的是,PostgreSQL提供了json_set
函數。函數原型:
jsonb_set(target jsonb, path text[], new_value jsonb[, create_missing boolean])
給定一個jsonb列,可以在指定的路徑上設置一個新值。
select jsonb_set(
'[{"type": "phone", "value": "+1-202-555-0105"},{"type": "email", "value": "[email protected]"}]',
'{1,value}',
'"[email protected]"',
false
);
select jsonb_set(
'[{"type": "email", "value": "[email protected]"}]',
'{0,value}',
'"[email protected]"',
false
);
jsonb_set
函數的第一個參數爲jsonb數據對象,第二個參數路徑,在上面的例子中,{1,value}
爲數組中索引爲1的元素,並且屬性是value
字段的,最後一個參數是對路徑中選擇的字段進行賦值。
根據這樣的規則,那麼第一個sql語句就是修改jimi的郵箱,而第二個sql語句就是修改janis的電子郵箱。
上面返回的結果是:
[{"type":"phone","value":"+1-202-555-0105"},{"type":"email","value":"[email protected]"}]
[{"type":"email","value":"[email protected]"}]
如果現在你認爲這樣就完事了,那就是你太Too young! Too simple!。
非關係型數據庫的問題在於它們是動態的,這也是使用jsonb的原因之一,但是這就帶來了一個問題,例如上面的案例,jimi的郵箱對象在數組中的索引是1,janis的郵箱對象在數組中索引是0,而另外的一條數據很可能是不同的數組,其索引也不一樣,那麼如何確定每個聯繫人的郵箱所在數組的索引?
答案 是對數組中的元素進行排序,並獲得索引。
select index-1 as index
from customers
,jsonb_array_elements(contacts) with ordinality arr(contact, index)
where contact->>'type' = 'email'
and name = 'Jimi';
該查詢會返回1,這是jimi聯繫人的電子郵件對象索引。
現在萬事俱備,只欠東風!我們把查詢和更改步驟合併。
with contact_email as (
select ('{'||index-1||',value}')::text[] as path
from customers
,jsonb_array_elements(contacts) with ordinality arr(contact, index)
where contact->>'type' = 'email'
and name = 'Jimi'
)
update customers
set contacts = jsonb_set(contacts, contact_email.path, '"[email protected]"', false)
from contact_email
where name = 'Jimi';
這個sql中最重要的部分就是with
,這是一個強大的命令,但對於這個例子來說,你可以把它看成是“儲存變量”功能,with最後的結果都儲存在contact_email變量內,其中就包含需要更新的路徑,也就是jimi的郵箱路徑。
下面再稍微詳細介紹一下:
(‘{‘||index-1||’,value}’)::text[] as path
這一段是在建立路徑{1, value}
,但是要轉換成text[]
類型,因爲jsonb_set函數需要這個類型。