Kết quả trả về của PostgreSQL được đặt thành mảng JSON?


134

Tôi muốn PostgreSQL trả về kết quả của một truy vấn dưới dạng một mảng JSON. Được

create table t (a int primary key, b text);

insert into t values (1, 'value1');
insert into t values (2, 'value2');
insert into t values (3, 'value3');

Tôi muốn một cái gì đó tương tự như

[{"a":1,"b":"value1"},{"a":2,"b":"value2"},{"a":3,"b":"value3"}]

hoặc là

{"a":[1,2,3], "b":["value1","value2","value3"]}

(thực sự sẽ hữu ích hơn khi biết cả hai). Tôi đã thử một số thứ như

select row_to_json(row) from (select * from t) row;
select array_agg(row) from (select * from t) row;
select array_to_string(array_agg(row), '') from (select * from t) row;

Và tôi cảm thấy tôi gần gũi, nhưng thực sự không có. Tôi có nên xem các tài liệu khác ngoại trừ 9.15. Hàm và toán tử JSON ?

Nhân tiện, tôi không chắc về ý tưởng của mình. Đây có phải là một quyết định thiết kế thông thường? Tất nhiên, tôi nghĩ rằng tôi có thể lấy kết quả (ví dụ) của 3 truy vấn đầu tiên ở trên và thao tác một chút trong ứng dụng trước khi phục vụ nó cho máy khách, nhưng nếu PostgreQuery có thể trực tiếp tạo đối tượng JSON cuối cùng, nó sẽ đơn giản hơn, vì tôi vẫn chưa bao gồm bất kỳ sự phụ thuộc nào vào bất kỳ thư viện JSON nào trong ứng dụng của mình.


1
PG 9.4, hiện có sẵn trong phiên bản beta 1, đã cải thiện hỗ trợ cho JSON, bao gồm cả I / O nhị phân. Nếu bạn đang ở trên một máy phát triển, bạn có thể muốn kiểm tra nó.
Patrick

@Patrick: cảm ơn bạn, nó trông giống như JSON_OBJECT () là một hàm mới trong 9.4 và tôi sẽ cố gắng một cái gì đó như SELECT JSON_OBJECT (array_agg (ta), array_agg (tb)) TỪ t, nếu tôi đã có nó
engineerX

Câu trả lời:


264

TL; DR

SELECT json_agg(t) FROM t

cho một mảng JSON của các đối tượng và

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )
FROM t

cho một đối tượng JSON của mảng.

Danh sách các đối tượng

Phần này mô tả cách tạo một mảng các đối tượng JSON, với mỗi hàng được chuyển đổi thành một đối tượng. Kết quả trông như thế này:

[{"a":1,"b":"value1"},{"a":2,"b":"value2"},{"a":3,"b":"value3"}]

9.3 trở lên

Các json_aggchức năng tạo ra kết quả này ra khỏi hộp. Nó tự động tìm ra cách chuyển đổi đầu vào của nó thành JSON và tổng hợp nó thành một mảng.

SELECT json_agg(t) FROM t

Không có jsonb(giới thiệu trong phiên bản 9.4) của json_agg. Bạn có thể tổng hợp các hàng thành một mảng và sau đó chuyển đổi chúng:

SELECT to_jsonb(array_agg(t)) FROM t

hoặc kết hợp json_aggvới dàn diễn viên:

SELECT json_agg(t)::jsonb FROM t

Thử nghiệm của tôi cho thấy rằng tổng hợp chúng thành một mảng đầu tiên nhanh hơn một chút. Tôi nghi ngờ rằng điều này là do các diễn viên phải phân tích toàn bộ kết quả JSON.

9,2

9.2 không có json_agghoặc các to_jsonchức năng, vì vậy bạn cần sử dụng cũ hơn array_to_json:

SELECT array_to_json(array_agg(t)) FROM t

Bạn có thể tùy chọn bao gồm một row_to_jsoncuộc gọi trong truy vấn:

SELECT array_to_json(array_agg(row_to_json(t))) FROM t

Điều này chuyển đổi mỗi hàng thành một đối tượng JSON, tổng hợp các đối tượng JSON thành một mảng và sau đó chuyển đổi mảng thành một mảng JSON.

Tôi không thể nhận ra bất kỳ sự khác biệt đáng kể nào về hiệu suất giữa hai người.

Đối tượng của danh sách

Phần này mô tả cách tạo một đối tượng JSON, với mỗi khóa là một cột trong bảng và mỗi giá trị là một mảng các giá trị của cột. Đó là kết quả trông như thế này:

{"a":[1,2,3], "b":["value1","value2","value3"]}

9,5 trở lên

Chúng ta có thể tận dụng json_build_objectchức năng:

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )
FROM t

Bạn cũng có thể tổng hợp các cột, tạo một hàng đơn, sau đó chuyển đổi nó thành một đối tượng:

SELECT to_json(r)
FROM (
    SELECT
        json_agg(t.a) AS a,
        json_agg(t.b) AS b
    FROM t
) r

Lưu ý rằng bí danh các mảng là hoàn toàn cần thiết để đảm bảo rằng đối tượng có tên mong muốn.

Cái nào rõ ràng hơn là vấn đề quan điểm. Nếu sử dụng json_build_objecthàm, tôi khuyên bạn nên đặt một cặp khóa / giá trị trên một dòng để cải thiện khả năng đọc.

Bạn cũng có thể sử dụng array_aggthay thế json_agg, nhưng thử nghiệm của tôi cho thấy rằng json_aggnó nhanh hơn một chút.

Không có jsonbphiên bản của json_build_objectchức năng. Bạn có thể tổng hợp thành một hàng duy nhất và chuyển đổi:

SELECT to_jsonb(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

Không giống như các truy vấn khác cho loại kết quả này, array_aggdường như nhanh hơn một chút khi sử dụng to_jsonb. Tôi nghi ngờ điều này là do phân tích cú pháp trên cao và xác thực kết quả JSON của json_agg.

Hoặc bạn có thể sử dụng một diễn viên rõ ràng:

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )::jsonb
FROM t

Các to_jsonbphiên bản cho phép bạn để tránh các diễn viên và vận hành nhanh, theo thử nghiệm của tôi; một lần nữa, tôi nghi ngờ điều này là do chi phí phân tích cú pháp và xác nhận kết quả.

9,4 và 9,3

Các json_build_objectchức năng còn mới đến 9.5, vì vậy bạn phải tổng hợp và chuyển đổi sang một đối tượng trong các phiên bản trước:

SELECT to_json(r)
FROM (
    SELECT
        json_agg(t.a) AS a,
        json_agg(t.b) AS b
    FROM t
) r

hoặc là

SELECT to_jsonb(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

tùy thuộc vào việc bạn muốn jsonhay jsonb.

(9.3 không có jsonb.)

9,2

Trong 9.2, thậm chí không to_jsontồn tại. Bạn phải sử dụng row_to_json:

SELECT row_to_json(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

Tài liệu

Tìm tài liệu cho các hàm JSON trong các hàm JSON .

json_agglà trên trang chức năng tổng hợp .

Thiết kế

Nếu hiệu suất là quan trọng, hãy đảm bảo bạn đánh giá các truy vấn của bạn dựa trên lược đồ và dữ liệu của riêng bạn, thay vì tin tưởng vào thử nghiệm của tôi.

Cho dù đó là một thiết kế tốt hay không thực sự phụ thuộc vào ứng dụng cụ thể của bạn. Về khả năng bảo trì, tôi không thấy bất kỳ vấn đề cụ thể nào. Nó đơn giản hóa mã ứng dụng của bạn và có nghĩa là có ít hơn để duy trì trong phần đó của ứng dụng. Nếu PG có thể cung cấp cho bạn chính xác kết quả bạn cần ra khỏi hộp, lý do duy nhất tôi có thể nghĩ đến để không sử dụng nó sẽ là những cân nhắc về hiệu suất. Đừng phát minh lại bánh xe và tất cả.

Không có gì

Các hàm tổng hợp thường trả lại NULLkhi chúng hoạt động trên các hàng không. Nếu đây là một khả năng, bạn có thể muốn sử dụng COALESCEđể tránh chúng. Một vài ví dụ:

SELECT COALESCE(json_agg(t), '[]'::json) FROM t

Hoặc là

SELECT to_jsonb(COALESCE(array_agg(t), ARRAY[]::t[])) FROM t

Tín dụng cho Hannes Landeholm vì đã chỉ ra điều này


3
Cảm ơn bạn vì câu trả lời. Bạn đã truyền cảm hứng cho tôi để tìm câu trả lời cho câu hỏi thứ hai của mình, CHỌN row_to_json (row (Array_agg (ta), Array_agg (tb))) TỪ, mặc dù kết quả có "f1" và "f2" là nhãn thay vì a và b.
kỹ

@engineerX Tôi đã mở rộng câu trả lời của mình.
jpmc26

3
Trong một số trường hợp, việc lấy lại NULL thay vì một mảng JSON trống khi lựa chọn bên trong (từ t) trả về các hàng không. Điều này được gây ra bởi các hàm tổng hợp luôn trả về NULL khi chọn không có hàng và có thể giải được bằng coalesce: Array_to_json (coalesce (mảng_agg (t), mảng [] :: record [])).
Hannes Landeholm

3
bạn có thể sử dụng to_jsonthay vì row_to_jsonarray_to_json
itnikolay

Để chọn (nhiều) cột cụ thể, bạn phải chuyển chúng dưới dạng một đối số duy nhất - một danh sách SELECT json_agg((column1, column2, ...)) FROM t dấu ngoặc tròn giống như - chú ý các dấu ngoặc phụ. Điều này có thể không rõ ràng "ra khỏi hộp".
jave.web

19

Ngoài ra nếu bạn muốn trường được chọn từ bảng và tổng hợp thì dưới dạng mảng.

SELECT json_agg(json_build_object('data_a',a,
                                  'data_b',b,
))  from t;

Kết quả sẽ đến.

 [{'data_a':1,'data_b':'value1'}
  {'data_a':2,'data_b':'value2'}]
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.