如何更新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": "jimi@gmail.com"}]'
);insert into customers (name, contacts) values ('Janis','[{"type": "email", "value": "janis@gmail.com"}]'
);
看起來很簡單,但是如何更新特定的聯系人信息?如何更改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": "jimi@gmail.com"}]','{1,value}','"jimi.hendrix@gmail.com"',false
);select jsonb_set('[{"type": "email", "value": "janis@gmail.com"}]','{0,value}','"janis.joplin@gmail.com"',false
);
jsonb_set
函數的第一個參數為jsonb數據對象,第二個參數路徑,在上面的例子中,{1,value}
為數組中索引為1的元素,并且屬性是value
字段的,最后一個參數是對路徑中選擇的字段進行賦值。
根據這樣的規則,那么第一個sql語句就是修改jimi的郵箱,而第二個sql語句就是修改janis的電子郵箱。
上面返回的結果是:
[{"type":"phone","value":"+1-202-555-0105"},{"type":"email","value":"jimi.hendrix@gmail.com"}]
[{"type":"email","value":"janis.joplin@gmail.com"}]
如果現在你認為這樣就完事了,那就是你太Too young! Too simple!。
非關系型數據庫的問題在于它們是動態的,這也是使用jsonb的原因之一,但是這就帶來了一個問題,例如上面的案例,jimi的郵箱對象在數組中的索引是1,janis的郵箱對象在數組中索引是0,而另外的一條數據很可能是不同的數組,其索引也不一樣,那么如何確定每個聯系人的郵箱所在數組的索引?
答案 是對數組中的元素進行排序,并獲得索引。
select index-1 as indexfrom 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 pathfrom customers,jsonb_array_elements(contacts) with ordinality arr(contact, index)where contact->>'type' = 'email'and name = 'Jimi'
)
update customersset contacts = jsonb_set(contacts, contact_email.path, '"jimi.hendrix@gmail.com"', false)from contact_emailwhere name = 'Jimi';
這個sql中最重要的部分就是with
,這是一個強大的命令,但對于這個例子來說,你可以把它看成是“儲存變量”功能,with最后的結果都儲存在contact_email變量內,其中就包含需要更新的路徑,也就是jimi的郵箱路徑。
下面再稍微詳細介紹一下:
(‘{‘||index-1||’,value}’)::text[] as path
這一段是在建立路徑{1, value}
,但是要轉換成text[]
類型,因為jsonb_set函數需要這個類型。