Cách thực hiện các hoạt động cập nhật trên các cột kiểu JSONB trong Postgres 9.4


131

Xem qua tài liệu về JSONB kiểu dữ liệu Postgres 9.4, tôi không rõ ràng ngay lập tức về cách thực hiện cập nhật trên các cột JSONB.

Tài liệu cho các loại và hàm JSONB:

http://www.postgresql.org/docs/9.4/static/fifts-json.html http://www.postgresql.org/docs/9.4/static/datatype-json.html

Để làm ví dụ, tôi có cấu trúc bảng cơ bản này:

CREATE TABLE test(id serial, data jsonb);

Chèn rất dễ dàng, như trong:

INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Bây giờ, làm cách nào để cập nhật cột 'dữ liệu'? Đây là cú pháp không hợp lệ:

UPDATE test SET data->'name' = 'my-other-name' WHERE id = 1;

Đây có phải là tài liệu rõ ràng mà tôi bỏ lỡ? Cảm ơn.

Câu trả lời:


31

Lý tưởng nhất là bạn không sử dụng các tài liệu JSON cho dữ liệu thông thường có cấu trúc mà bạn muốn thao tác bên trong cơ sở dữ liệu quan hệ. Sử dụng một thiết kế quan hệ chuẩn hóa thay thế.

JSON chủ yếu nhằm lưu trữ toàn bộ tài liệu không cần phải thao tác bên trong RDBMS. Liên quan:

Cập nhật một hàng trong Postgres luôn viết một phiên bản mới của toàn bộ hàng. Đó là nguyên tắc cơ bản của mô hình MVCC của Postgres . Từ góc độ hiệu năng, hầu như không có vấn đề gì nếu bạn thay đổi một phần dữ liệu bên trong một đối tượng JSON hoặc tất cả dữ liệu: một phiên bản mới của hàng phải được viết.

Do đó, lời khuyên trong hướng dẫn :

Dữ liệu JSON phải tuân theo các cân nhắc kiểm soát đồng thời giống như bất kỳ loại dữ liệu nào khác khi được lưu trữ trong một bảng. Mặc dù lưu trữ các tài liệu lớn là có thể thực hiện được, hãy nhớ rằng bất kỳ bản cập nhật nào cũng có được khóa cấp hàng trên toàn bộ hàng. Xem xét việc giới hạn các tài liệu JSON ở kích thước có thể quản lý để giảm sự tranh chấp khóa giữa các giao dịch cập nhật. Một cách lý tưởng, mỗi tài liệu JSON phải đại diện cho một mốc dữ liệu nguyên tử mà các quy tắc kinh doanh ra lệnh không thể được chia nhỏ một cách hợp lý thành các mốc nhỏ hơn có thể được sửa đổi một cách độc lập.

Ý chính của nó: để sửa đổi bất cứ điều gì bên trong một đối tượng JSON, bạn phải gán một đối tượng đã sửa đổi cho cột. Postgres cung cấp các phương tiện hạn chế để xây dựng và thao tác jsondữ liệu ngoài khả năng lưu trữ của nó. Kho vũ khí của các công cụ đã tăng lên đáng kể với mỗi bản phát hành mới kể từ phiên bản 9.2. Nhưng hiệu trưởng vẫn còn: Bạn luôn phải gán một đối tượng đã sửa đổi hoàn chỉnh cho cột và Postgres luôn viết một phiên bản hàng mới cho bất kỳ bản cập nhật nào.

Một số kỹ thuật làm việc với các công cụ của Postgres 9.3 trở lên:

Câu trả lời này đã thu hút khoảng nhiều lượt tải xuống như tất cả các câu trả lời khác của tôi về SO cùng nhau . Mọi người dường như không thích ý tưởng: một thiết kế chuẩn hóa là ưu việt cho dữ liệu không động. Bài viết blog tuyệt vời này của Craig Ringer giải thích chi tiết hơn:


6
Câu trả lời này chỉ liên quan đến loại JSON và bỏ qua JSONB.
fiatjaf

7
@fiatjaf: Câu trả lời này hoàn toàn có thể áp dụng cho các loại dữ liệu jsonjsonbnhư nhau. Cả hai đều lưu trữ dữ liệu JSON, jsonbnó ở dạng nhị phân chuẩn hóa có một số ưu điểm (và một vài nhược điểm). stackoverflow.com/a/10560761/939860 Cả hai loại dữ liệu đều không tốt cho việc thao túng nhiều bên trong cơ sở dữ liệu. Không có loại tài liệu là. Chà, thật tốt cho các tài liệu JSON nhỏ, hầu như không có cấu trúc. Nhưng các tài liệu lớn, lồng nhau sẽ là một sự điên rồ theo cách đó.
Erwin Brandstetter

7
"Hướng dẫn cách làm việc với các công cụ của Postgres 9.3" thực sự là điều đầu tiên trong câu trả lời của bạn khi nó trả lời câu hỏi được hỏi .. đôi khi việc cập nhật json để thay đổi lược đồ là điều hợp lý và lý do không cập nhật json don 'thực sự áp dụng
Michael Wasser

22
Câu trả lời này không hữu ích, xin lỗi. @jvous, bạn không muốn chấp nhận câu trả lời của Jimothy, vì nó thực sự trả lời câu hỏi của bạn?
Bastian Voigt

10
Trả lời câu hỏi trước khi thêm nhận xét / ý kiến ​​/ thảo luận của riêng bạn.
Ppp

330

Nếu bạn có thể nâng cấp lên Postgresql 9.5, jsonb_setlệnh sẽ khả dụng, như những người khác đã đề cập.

Trong mỗi câu lệnh SQL sau đây, tôi đã bỏ qua wheremệnh đề cho ngắn gọn; rõ ràng, bạn muốn thêm lại.

Cập nhật tên:

UPDATE test SET data = jsonb_set(data, '{name}', '"my-other-name"');

Thay thế các thẻ (trái ngược với việc thêm hoặc xóa thẻ):

UPDATE test SET data = jsonb_set(data, '{tags}', '["tag3", "tag4"]');

Thay thế thẻ thứ hai (0-index):

UPDATE test SET data = jsonb_set(data, '{tags,1}', '"tag5"');

Nối thẻ ( điều này sẽ hoạt động miễn là có ít hơn 999 thẻ; thay đổi đối số 999 thành 1000 trở lên sẽ gây ra lỗi . Điều này dường như không còn xảy ra trong Postgres 9.5.3; chỉ mục lớn hơn nhiều có thể được sử dụng) :

UPDATE test SET data = jsonb_set(data, '{tags,999999999}', '"tag6"', true);

Xóa thẻ cuối cùng:

UPDATE test SET data = data #- '{tags,-1}'

Cập nhật phức tạp (xóa thẻ cuối cùng, chèn thẻ mới và thay đổi tên):

UPDATE test SET data = jsonb_set(
    jsonb_set(data #- '{tags,-1}', '{tags,999999999}', '"tag3"', true), 
    '{name}', '"my-other-name"');

Điều quan trọng cần lưu ý là trong mỗi ví dụ này, bạn không thực sự cập nhật một trường duy nhất của dữ liệu JSON. Thay vào đó, bạn đang tạo một phiên bản dữ liệu tạm thời, đã sửa đổi và gán phiên bản đã sửa đổi đó trở lại cột. Trong thực tế, kết quả nên giống nhau, nhưng ghi nhớ điều này sẽ tạo ra các cập nhật phức tạp, như ví dụ cuối cùng, dễ hiểu hơn.

Trong ví dụ phức tạp, có ba biến đổi và ba phiên bản tạm thời: Đầu tiên, thẻ cuối cùng bị xóa. Sau đó, phiên bản đó được chuyển đổi bằng cách thêm một thẻ mới. Tiếp theo, phiên bản thứ hai được chuyển đổi bằng cách thay đổi nametrường. Giá trị trong datacột được thay thế bằng phiên bản cuối cùng.


42
bạn nhận được điểm thưởng khi hiển thị cách cập nhật một cột trong bảng theo yêu cầu của OP
chadrik

1
@chadrik: Tôi đã thêm một ví dụ phức tạp hơn. Nó không làm chính xác những gì bạn yêu cầu, nhưng nó sẽ cho bạn một ý tưởng. Lưu ý rằng đầu vào của jsonb_setcuộc gọi bên ngoài là đầu ra từ cuộc gọi bên trong và đầu vào của cuộc gọi bên trong đó là kết quả của data #- '{tags,-1}'. Tức là, dữ liệu gốc với thẻ cuối cùng bị xóa.
Jimothy

1
@PranaySoni: Vì mục đích đó, tôi có thể sử dụng một quy trình được lưu trữ hoặc, nếu chi phí không phải là vấn đề đáng lo ngại, hãy mang dữ liệu đó trở lại, thao tác bằng ngôn ngữ của ứng dụng, sau đó viết lại. Điều này nghe có vẻ nặng nề, nhưng hãy nhớ, trong tất cả các ví dụ tôi đã đưa ra, bạn vẫn không cập nhật một trường nào trong JSON (B): dù sao bạn cũng sẽ ghi đè lên toàn bộ cột. Vì vậy, một Proc lưu trữ thực sự không khác.
Jimothy

1
@Alex: Vâng, một chút hack. Nếu tôi nói {tags,0}, điều đó có nghĩa là "phần tử đầu tiên của mảng tags", cho phép tôi đưa ra một giá trị mới cho phần tử đó. Bằng cách sử dụng một số lớn thay vì 0, thay vì nó thay thế một phần tử hiện có trong mảng, nó sẽ thêm một phần tử mới vào mảng. Tuy nhiên, nếu mảng thực sự có hơn 999.999.999 phần tử trong đó, phần tử này sẽ thay thế phần tử cuối cùng thay vì thêm phần tử mới.
Jimothy

1
Nếu trường chứa null thì sao? có vẻ không hoạt động. Ví dụ: trường thông tin jsonb là null: "CẬP NHẬT người tổ chức CẬP NHẬT không có thay đổi nào trên db
stackdave


18

Đối với những người gặp phải vấn đề này và muốn khắc phục rất nhanh (và bị kẹt vào ngày 9.4.5 trở về trước), đây là những gì tôi đã làm:

Tạo bảng thử nghiệm

CREATE TABLE test(id serial, data jsonb);
INSERT INTO test(data) values ('{"name": "my-name", "tags": ["tag1", "tag2"]}');

Cập nhật tuyên bố để thay đổi tên của tài sản jsonb

UPDATE test 
SET data = replace(data::TEXT,'"name":','"my-other-name":')::jsonb 
WHERE id = 1;

Cuối cùng, câu trả lời được chấp nhận là chính xác ở chỗ bạn không thể sửa đổi một phần riêng lẻ của một đối tượng jsonb (trong phiên bản 9.4.5 trở về trước); tuy nhiên, bạn có thể truyền đối tượng jsonb thành một chuỗi (:: TEXT) và sau đó thao tác chuỗi và chuyển trở lại đối tượng jsonb (:: jsonb).

Có hai cảnh báo quan trọng

  1. điều này sẽ thay thế tất cả các thuộc tính được gọi là "tên" trong json (trong trường hợp bạn có nhiều thuộc tính có cùng tên)
  2. điều này không hiệu quả như jsonb_set nếu bạn đang sử dụng 9.5

Như đã nói, tôi đã gặp một tình huống mà tôi phải cập nhật lược đồ cho nội dung trong các đối tượng jsonb và đây là cách đơn giản nhất để thực hiện chính xác những gì người đăng ban đầu đang yêu cầu.


1
Chúa ơi, tôi đã tìm kiếm cách cập nhật lên jsonb trong hai giờ để tôi có thể thay thế tất cả các \u0000ký tự null, ví dụ cho thấy bức tranh hoàn chỉnh. Cảm ơn vì điều đó!
Joshua Robinson

3
có vẻ tốt btw đối số thứ hai để thay thế trong ví dụ của bạn bao gồm dấu hai chấm và đối số thứ ba thì không. Có vẻ như cuộc gọi của bạn phải làreplace(data::TEXT, '"name":', '"my-other-name":')::jsonb
davidicus

Cảm ơn bạn @davidicus! Xin lỗi vì cập nhật rất chậm, nhưng tôi đánh giá cao việc bạn chia sẻ cho người khác!
Chad Capra

12

Câu hỏi này đã được hỏi trong bối cảnh của postgres 9.4, tuy nhiên những người xem mới đến với câu hỏi này nên biết rằng trong postgres 9.5, các hoạt động tạo / cập nhật / xóa tài liệu phụ trên các trường JSONB được cơ sở dữ liệu hỗ trợ, mà không cần mở rộng chức năng.

Xem: Các toán tử và hàm sửa đổi JSONB


7

cập nhật thuộc tính 'name':

UPDATE test SET data=data||'{"name":"my-other-name"}' WHERE id = 1;

và nếu bạn muốn xóa ví dụ như các thuộc tính 'name' và 'tags':

UPDATE test SET data=data-'{"name","tags"}'::text[] WHERE id = 1;

5

Tôi đã viết hàm nhỏ cho chính mình mà hoạt động đệ quy trong Postgres 9.4. Tôi đã có một vấn đề tương tự (tốt là họ đã giải quyết được một số vấn đề đau đầu này trong Postgres 9.5). Dù sao đây là chức năng (tôi hy vọng nó hoạt động tốt cho bạn):

CREATE OR REPLACE FUNCTION jsonb_update(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    v RECORD;
BEGIN
    IF jsonb_typeof(val2) = 'null'
    THEN 
        RETURN val1;
    END IF;

    result = val1;

    FOR v IN SELECT key, value FROM jsonb_each(val2) LOOP

        IF jsonb_typeof(val2->v.key) = 'object'
            THEN
                result = result || jsonb_build_object(v.key, jsonb_update(val1->v.key, val2->v.key));
            ELSE
                result = result || jsonb_build_object(v.key, v.value);
        END IF;
    END LOOP;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

Đây là mẫu sử dụng:

select jsonb_update('{"a":{"b":{"c":{"d":5,"dd":6},"cc":1}},"aaa":5}'::jsonb, '{"a":{"b":{"c":{"d":15}}},"aa":9}'::jsonb);
                            jsonb_update                             
---------------------------------------------------------------------
 {"a": {"b": {"c": {"d": 15, "dd": 6}, "cc": 1}}, "aa": 9, "aaa": 5}
(1 row)

Như bạn có thể thấy nó phân tích sâu và cập nhật / thêm giá trị khi cần thiết.


Điều này không hoạt động trong 9,4, vì jsonb_build_objectđã được giới thiệu vào 9,5
Greg

@Greg Bạn nói đúng, tôi vừa kiểm tra và tôi đang chạy PostgreSQL 9.5 ngay bây giờ - đây là lý do tại sao nó hoạt động. Cảm ơn đã chỉ ra rằng - giải pháp của tôi sẽ không hoạt động trong 9.4.
J. Raczkiewicz

4

Có thể: CẬP NHẬT thử nghiệm dữ liệu SET = '"tên tôi khác"' :: json WHERE id = 1;

Nó hoạt động với trường hợp của tôi, trong đó dữ liệu là loại json


1
Làm việc cho tôi cũng vậy, trên postgresql 9.4.5. Toàn bộ hồ sơ được viết lại để người ta không thể cập nhật một trường atm.
kometen

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.