ผลลัพธ์ของ PostgreSQL ถูกตั้งค่าเป็นอาร์เรย์ JSON หรือไม่


135

ฉันต้องการให้ PostgreSQL ส่งคืนผลลัพธ์ของแบบสอบถามเป็นอาร์เรย์ JSON เดียว ป.ร. ให้ไว้

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');

ฉันต้องการสิ่งที่คล้ายกับ

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

หรือ

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

(จริงๆแล้วมันจะมีประโยชน์มากกว่าถ้ารู้ทั้งสองอย่าง) ฉันได้ลองสิ่งต่างๆเช่น

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;

และฉันรู้สึกว่าฉันอยู่ใกล้ แต่ไม่มีจริงๆ ฉันควรดูเอกสารอื่น ๆ ยกเว้น9.15 ฟังก์ชัน JSON และตัวดำเนินการ ?

อย่างไรก็ตามฉันไม่แน่ใจเกี่ยวกับความคิดของฉัน นี่เป็นการตัดสินใจออกแบบตามปกติหรือไม่? ความคิดของฉันคือแน่นอนว่าฉันสามารถใช้ผลลัพธ์ (ตัวอย่าง) ของแบบสอบถามแรกจาก 3 ข้อข้างต้นและจัดการเล็กน้อยในแอปพลิเคชันก่อนที่จะให้บริการกับไคลเอนต์ แต่ถ้า PostgreSQL สามารถสร้างวัตถุ JSON สุดท้ายได้โดยตรง มันจะง่ายกว่านี้เพราะฉันยังไม่ได้รวมการพึ่งพาไลบรารี JSON ใด ๆ ในแอปพลิเคชันของฉัน


1
PG 9.4 พร้อมให้ใช้งานแล้วในรุ่นเบต้า 1 ได้ปรับปรุงการรองรับ JSON รวมถึงไบนารี I / O หากคุณอยู่ในเครื่องพัฒนาคุณอาจต้องการตรวจสอบ
Patrick

@Patrick: ขอบคุณดูเหมือนว่า json_object () เป็นฟังก์ชั่นใหม่ใน 9.4 และฉันจะลองบางอย่างเช่น SELECT json_object (array_agg (ta), array_agg (tb)) FROM t, if I have it
engineerX

คำตอบ:


266

TL; DR

SELECT json_agg(t) FROM t

สำหรับอาร์เรย์ของวัตถุ JSON และ

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

สำหรับออบเจ็กต์ JSON ของอาร์เรย์

รายชื่อวัตถุ

ส่วนนี้อธิบายถึงวิธีการสร้างอาร์เรย์ JSON ของออบเจ็กต์โดยแต่ละแถวจะถูกแปลงเป็นออบเจ็กต์เดียว ผลลัพธ์มีลักษณะดังนี้:

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

9.3 ขึ้นไป

json_aggฟังก์ชั่นการผลิตผลนี้ออกมาจากกล่อง โดยอัตโนมัติจะหาวิธีแปลงอินพุตเป็น JSON และรวมเข้าเป็นอาร์เรย์

SELECT json_agg(t) FROM t

ไม่มีjsonb(แนะนำในเวอร์ชัน 9.4) ของjson_agg. คุณสามารถรวมแถวเป็นอาร์เรย์แล้วแปลงได้:

SELECT to_jsonb(array_agg(t)) FROM t

หรือรวมjson_aggกับนักแสดง:

SELECT json_agg(t)::jsonb FROM t

การทดสอบของฉันชี้ให้เห็นว่าการรวมเข้าในอาร์เรย์ก่อนนั้นเร็วกว่าเล็กน้อย ฉันสงสัยว่านี่เป็นเพราะนักแสดงต้องแยกวิเคราะห์ผลลัพธ์ JSON ทั้งหมด

9.2

9.2 ไม่มีjson_aggหรือto_jsonฟังก์ชันดังนั้นคุณต้องใช้รุ่นเก่ากว่าarray_to_json:

SELECT array_to_json(array_agg(t)) FROM t

คุณสามารถเลือกที่จะรวมการrow_to_jsonโทรในแบบสอบถาม:

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

สิ่งนี้จะแปลงแต่ละแถวเป็นออบเจ็กต์ JSON รวมออบเจ็กต์ JSON เป็นอาร์เรย์จากนั้นแปลงอาร์เรย์เป็นอาร์เรย์ JSON

ฉันไม่สามารถแยกแยะความแตกต่างของประสิทธิภาพที่สำคัญระหว่างทั้งสองอย่างได้

วัตถุของรายการ

ส่วนนี้อธิบายวิธีการสร้างออบเจ็กต์ JSON โดยแต่ละคีย์เป็นคอลัมน์ในตารางและแต่ละค่าเป็นอาร์เรย์ของค่าของคอลัมน์ ผลลัพธ์จะเป็นดังนี้:

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

9.5 ขึ้นไป

เราสามารถใช้ประโยชน์จากjson_build_objectฟังก์ชัน:

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

คุณยังสามารถรวมคอลัมน์สร้างแถวเดียวจากนั้นแปลงเป็นวัตถุ:

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

โปรดทราบว่าการกำหนดนามแฝงอาร์เรย์เป็นสิ่งจำเป็นอย่างยิ่งเพื่อให้แน่ใจว่าออบเจ็กต์มีชื่อที่ต้องการ

อันไหนชัดเจนกว่าก็เป็นเรื่องของความเห็น หากใช้json_build_objectฟังก์ชันนี้ขอแนะนำให้วางคู่คีย์ / ค่าหนึ่งคู่ในบรรทัดเพื่อปรับปรุงความสามารถในการอ่าน

คุณยังสามารถใช้array_aggแทนได้json_aggแต่การทดสอบของฉันระบุว่าjson_aggเร็วกว่าเล็กน้อย

ไม่มีjsonbเวอร์ชันของjson_build_objectฟังก์ชัน คุณสามารถรวมเป็นแถวเดียวและแปลง:

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

ซึ่งแตกต่างจากการค้นหาอื่น ๆ สำหรับชนิดของผลนี้array_aggดูเหมือนว่าจะมีเล็ก ๆ น้อย ๆ to_jsonbได้เร็วขึ้นเมื่อใช้ ฉันสงสัยว่านี่เป็นเพราะการแยกวิเคราะห์ค่าใช้จ่ายและการตรวจสอบผลลัพธ์ JSON ของjson_agg.

หรือคุณสามารถใช้นักแสดงที่ชัดเจน:

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

to_jsonbรุ่นช่วยให้คุณหลีกเลี่ยงการหล่อและเป็นเร็วขึ้นตามการทดสอบของฉัน; อีกครั้งฉันสงสัยว่านี่เป็นเพราะค่าใช้จ่ายในการแยกวิเคราะห์และตรวจสอบผลลัพธ์

9.4 และ 9.3

json_build_objectฟังก์ชั่นใหม่ 9.5 เพื่อให้คุณมีการรวมและการแปลงไปยังวัตถุในรุ่นก่อนหน้านี้:

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

หรือ

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

ขึ้นอยู่กับว่าคุณต้องการหรือjsonjsonb

(9.3 ไม่มีjsonb)

9.2

ใน 9.2 ไม่มีto_jsonอยู่ด้วยซ้ำ คุณต้องใช้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

เอกสาร

ค้นหาเอกสารสำหรับฟังก์ชั่น JSON ในฟังก์ชั่น JSON

json_aggอยู่ในหน้าฟังก์ชันการรวม

ออกแบบ

หากประสิทธิภาพเป็นสิ่งสำคัญตรวจสอบให้แน่ใจว่าคุณเปรียบเทียบการสืบค้นของคุณกับสคีมาและข้อมูลของคุณเองแทนที่จะเชื่อการทดสอบของฉัน

ไม่ว่าจะเป็นการออกแบบที่ดีหรือไม่ขึ้นอยู่กับแอปพลิเคชันเฉพาะของคุณ ในแง่ของการบำรุงรักษาฉันไม่เห็นปัญหาใด ๆ ช่วยลดความซับซ้อนของรหัสแอปของคุณและหมายความว่ามีการดูแลรักษาน้อยกว่าในส่วนนั้นของแอป หาก PG สามารถให้ผลลัพธ์ที่คุณต้องการได้อย่างตรงจุดเหตุผลเดียวที่ฉันคิดว่าจะไม่ใช้ก็คือการพิจารณาประสิทธิภาพ อย่าสร้างล้อใหม่และทั้งหมด

nulls

โดยทั่วไปฟังก์ชันรวมจะคืนค่าNULLเมื่อทำงานเกินแถวศูนย์ หากเป็นไปได้คุณอาจต้องใช้COALESCEเพื่อหลีกเลี่ยง ตัวอย่างสองสามตัวอย่าง:

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

หรือ

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

ให้เครดิตHannes Landeholm ที่ชี้ให้เห็นสิ่งนี้


3
ขอบคุณสำหรับคำตอบ. คุณเป็นแรงบันดาลใจให้ฉันหาคำตอบสำหรับคำถามที่สอง SELECT row_to_json (row (array_agg (ta), array_agg (tb))) จาก t แม้ว่าผลลัพธ์จะมี "f1" และ "f2" เป็นป้ายกำกับแทนที่จะเป็น a และ b
engineerX

@engineerX ฉันได้ขยายคำตอบ
jpmc26

3
อาจเป็นสิ่งที่ไม่พึงปรารถนาในบางกรณีที่จะรับ NULL กลับแทนที่จะเป็นอาร์เรย์ JSON ที่ว่างเปล่าเมื่อการเลือกภายใน (จาก t) ส่งคืนแถวที่เป็นศูนย์ สิ่งนี้เกิดจากฟังก์ชันการรวมจะคืนค่า NULL เสมอเมื่อการเลือกไม่มีแถวและแก้ไขได้โดย coalesce: array_to_json (coalesce (array_agg (t), array [] :: record []))
Hannes Landeholm

3
คุณสามารถใช้ to_jsonแทนrow_to_jsonและarray_to_json
itsnikolay

ในการเลือกคอลัมน์เฉพาะ (หลายคอลัมน์) คุณต้องส่งผ่านเป็นอาร์กิวเมนต์เดียว - รายการวงเล็บกลมเช่นSELECT json_agg((column1, column2, ...)) FROM t - สังเกตวงเล็บพิเศษ สิ่งนี้อาจไม่ชัดเจน "นอกกรอบ"
jave.web

19

นอกจากนี้หากคุณต้องการฟิลด์ที่เลือกจากตารางและรวมเป็นอาร์เรย์

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

ผลลัพธ์จะมา

 [{'data_a':1,'data_b':'value1'}
  {'data_a':2,'data_b':'value2'}]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.