วิธีการเปลี่ยนอาร์เรย์ json เป็นอาร์เรย์ postgres?


69

ฉันมีคอลัมน์dataที่ถือjsonเอกสารคร่าวๆเช่นนี้:

{
    "name": "foo",
    "tags": ["foo", "bar"]
}

ฉันต้องการเปลี่ยนtagsอาร์เรย์ที่ซ้อนกันเป็นสตริงที่ต่อกัน ( foo, bar) ที่จะเป็นไปได้อย่างง่ายดายด้วยarray_to_string()ฟังก์ชั่นในทางทฤษฎี อย่างไรก็ตามฟังก์ชั่นนี้ไม่ได้ทำหน้าที่ในjsonอาร์เรย์ ดังนั้นฉันสงสัยว่าจะเปลี่ยนjsonอาร์เรย์นี้เป็น Postgres ได้arrayอย่างไร


คือjson_extract_path_text(your_column, 'tags') สิ่งที่คุณกำลังมองหา?
a_horse_with_no_name

1
@a_horse_with_no_name: ปัญหาที่เหลืออยู่: องค์ประกอบของอาร์เรย์ยังคงยกมาสำหรับรูปแบบ JSON ไม่สามารถแยกข้อความได้อย่างถูกต้อง ...
Erwin Brandstetter

คำตอบ:


94

Postgres 9.4 หรือใหม่กว่า

เห็นได้ชัดว่าได้แรงบันดาลใจจากโพสต์นี้ Postgres 9.4 ได้เพิ่มฟังก์ชั่นที่หายไป:
ขอบคุณ Laurence Rowe สำหรับแพทช์และ Andrew Dunstan ที่ทำหน้าที่!

ในการเลิกใช้อาร์เรย์ JSON จากนั้นใช้array_agg()หรือตัวสร้าง ARRAYเพื่อสร้างอาร์เรย์ Postgres จากมัน หรือstring_agg()การสร้างสตริงtext

รวมองค์ประกอบที่ไม่ได้ทดสอบต่อแถวในLATERALแบบสอบถามย่อยที่มีความสัมพันธ์หรือ แล้วคำสั่งเดิมจะถูกรักษาไว้และที่เราไม่จำเป็นต้องORDER BY, GROUP BYหรือแม้กระทั่งคีย์ไม่ซ้ำกันในแบบสอบถามด้านนอก ดู:

แทนที่ 'json' ด้วย 'jsonb' สำหรับjsonbในรหัส SQL ต่อไปนี้ทั้งหมด

SELECT t.tbl_id, d.list
FROM   tbl t
CROSS  JOIN LATERAL (
   SELECT string_agg(d.elem::text, ', ') AS list
   FROM   json_array_elements_text(t.data->'tags') AS d(elem)
   ) d;

ไวยากรณ์สั้น:

SELECT t.tbl_id, d.list
FROM   tbl t, LATERAL (
   SELECT string_agg(value::text, ', ') AS list
   FROM   json_array_elements_text(t.data->'tags')  -- col name default: "value"
   ) d;

ที่เกี่ยวข้อง:

ตัวสร้าง ARRAY ในแบบสอบถามย่อยที่มีความสัมพันธ์:

SELECT tbl_id, ARRAY(SELECT json_array_elements_text(t.data->'tags')) AS txt_arr
FROM   tbl t;

ที่เกี่ยวข้อง:

เมตตาความแตกต่าง : nullองค์ประกอบถูกเก็บไว้ในที่เกิดขึ้นจริงอาร์เรย์ สิ่งนี้เป็นไปไม่ได้ในข้อความค้นหาด้านบนที่สร้างtextสตริงซึ่งไม่สามารถมีnullค่าได้ การเป็นตัวแทนที่แท้จริงคืออาร์เรย์

ฟังก์ชั่นกระดาษห่อ

สำหรับการใช้งานซ้ำ ๆ เพื่อทำให้สิ่งนี้ง่ายยิ่งขึ้น encapsulate ลอจิกในฟังก์ชัน:

CREATE OR REPLACE FUNCTION json_arr2text_arr(_js json)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT json_array_elements_text(_js))';

ทำให้มันเป็นฟังก์ชั่น SQLดังนั้นมันสามารถถูกแทรกในเคียวรีที่ใหญ่กว่า
ทำให้เป็นIMMUTABLE(เพราะเป็น) เพื่อหลีกเลี่ยงการประเมินซ้ำในแบบสอบถามที่ใหญ่กว่าและอนุญาตให้แสดงในดัชนี

โทร:

SELECT tbl_id, json_arr2text_arr(data->'tags')
FROM   tbl;

db <> ซอที่นี่


โพสต์ 9.3 หรือเก่ากว่า

json_array_elements()ใช้ฟังก์ชั่น แต่เราได้สตริงที่ยกมาสองเท่าจากมัน

แบบสอบถามทางเลือกที่มีการรวมในแบบสอบถามด้านนอก CROSS JOINลบแถวที่มีอาร์เรย์ที่หายไปหรือว่างเปล่า อาจเป็นประโยชน์สำหรับองค์ประกอบการประมวลผล เราต้องการรหัสที่ไม่ซ้ำเพื่อรวม:

SELECT t.tbl_id, string_agg(d.elem::text, ', ') AS list
FROM   tbl t
CROSS  JOIN LATERAL json_array_elements(t.data->'tags') AS d(elem)
GROUP  BY t.tbl_id;

ตัวสร้าง ARRAY ยังคงมีสตริงที่เสนอ:

SELECT tbl_id, ARRAY(SELECT json_array_elements(t.data->'tags')) AS quoted_txt_arr
FROM   tbl t;

โปรดทราบว่าnullจะถูกแปลงเป็นค่าข้อความ "null" ไม่เหมือนข้างบน ไม่ถูกต้องพูดอย่างเคร่งครัดและอาจคลุมเครือ

คนจนไม่น่าสงสัยด้วยtrim():

SELECT t.tbl_id, string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM   tbl t, json_array_elements(t.data->'tags') d(elem)
GROUP  BY 1;

ดึงแถวเดียวจาก tbl:

SELECT string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM   tbl t, json_array_elements(t.data->'tags') d(elem)
WHERE  t.tbl_id = 1;

สตริงย่อยฟอร์มแบบสอบถามที่สัมพันธ์กัน:

SELECT tbl_id, (SELECT string_agg(trim(value::text, '"'), ', ')
                FROM   json_array_elements(t.data->'tags')) AS list
FROM   tbl t;

ตัวสร้าง ARRAY:

SELECT tbl_id, ARRAY(SELECT trim(value::text, '"')
                     FROM   json_array_elements(t.data->'tags')) AS txt_arr
FROM   tbl t;

ต้นฉบับ (เก่า) SQL ซอ
db <> ซอที่นี่

ที่เกี่ยวข้อง:

หมายเหตุ (ล้าสมัยตั้งแต่ pg 9.4)

เราต้องการ a json_array_elements_text(json), คู่ของjson_array_elements(json)เพื่อส่งคืนtextค่าที่เหมาะสมจากอาร์เรย์ JSON แต่ที่ดูเหมือนว่าจะหายไปจากคลังแสงที่มีให้ฟังก์ชั่น JSON หรือฟังก์ชันอื่นเพื่อแยกtextค่าจากJSONค่าสเกลาร์ ฉันก็ดูเหมือนจะขาดหายไปเช่นกัน
ดังนั้นฉันชั่วคราวด้วยtrim()แต่จะล้มเหลวสำหรับกรณีที่ไม่สำคัญ ...


โพสต์ที่ดีเช่นเคย แต่ด้วยความรู้ของคุณเกี่ยวกับ internals ทำไมจึงไม่ใช่นักแสดงจากอาเรย์ -> jsonb ที่นั่น ฉันสามารถเข้าใจได้ว่าไม่ได้ติดตั้งตัวละครอื่นเพราะ sql-array นั้นถูกพิมพ์ออกมามาก เป็นเพราะ PostgreSQL นั้นไม่ชอบการสร้างโค้ดอัตโนมัติในการส่ง (int [], bigint [], text []) เป็นต้น
Evan Carroll

3
@Evan: คุณต้องการใช้to_jsonb()สำหรับการแปลง array-> jsonb
Erwin Brandstetter

ไม่SELECT ARRAY(SELECT json_array_elements_text(_js))รับประกันจริง ๆ ว่าการเรียงลำดับของอาร์เรย์จะได้รับการเก็บรักษาไว้หรือไม่? เครื่องมือเพิ่มประสิทธิภาพไม่อนุญาตให้เปลี่ยนลำดับของแถวที่มาจาก json_array_elements_text ตามหลักวิชาใช่หรือไม่
Felix Geisendörfer

@ เฟลิกซ์: ไม่มีการรับประกันอย่างเป็นทางการในมาตรฐาน SQL (จากนั้นอีกครั้งการตั้งค่าฟังก์ชั่นส่งคืนจะไม่ได้รับอนุญาตแม้แต่ในรายการ SELECT ใน SQL มาตรฐานเพื่อเริ่มต้นด้วย) แต่มีการยืนยันอย่างไม่เป็นทางการในคู่มือ Postgres โปรดดูที่: dba.stackexchange.com/a/185862/3684เพื่อให้ชัดเจน - ค่าใช้จ่ายของการลงโทษ perfomance เล็ก ๆ น้อย ๆ - ดู: dba.stackexchange.com/a/27287/3684 โดยส่วนตัวฉันมั่นใจ 100% ว่านิพจน์นี้จะทำงานได้ตามที่คาดหวังใน Postgres เวอร์ชันปัจจุบันและอนาคตทุกรุ่นตั้งแต่ 9.4
Erwin Brandstetter

@ErwinBrandstetter ขอบคุณมากสำหรับการยืนยันเรื่องนี้! ฉันกำลังทำการค้นคว้าบทความที่สรุปการรับประกันอย่างเป็นทางการและไม่เป็นทางการการสั่งซื้อการค้ำประกันที่ PostgreSQL ให้ไว้และคำตอบของคุณมีประโยชน์อย่างไม่น่าเชื่อ! หากคุณสนใจที่จะตรวจสอบบทความแจ้งให้เราทราบ แต่ไม่ต้องกังวลถ้าไม่ ฉันรู้สึกขอบคุณอย่างมากต่อการมีส่วนร่วมของ StackOverflow และเรียนรู้มากมายจากคุณในช่วงหลายปีที่ผ่านมา!
Felix Geisendörfer

16

PG 9.4+

คำตอบที่ได้รับการยอมรับนั้นเป็นสิ่งที่คุณต้องการ แต่เพื่อความเรียบง่ายนี่คือผู้ช่วยที่ฉันใช้สำหรับสิ่งนี้:

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array(
  p_input jsonb
) RETURNS TEXT[] AS $BODY$

DECLARE v_output text[];

BEGIN

  SELECT array_agg(ary)::text[]
  INTO v_output
  FROM jsonb_array_elements_text(p_input) AS ary;

  RETURN v_output;

END;

$BODY$
LANGUAGE plpgsql VOLATILE;

จากนั้นทำ:

SELECT jsonb_array_to_text_array('["a", "b", "c"]'::jsonb);

ฉันเพิ่มการแสดงออกที่เร็วขึ้นในคำตอบและฟังก์ชั่นที่ง่ายขึ้น ซึ่งอาจถูกกว่ามาก
Erwin Brandstetter

4
ฟังก์ชั่นนี้ควรเป็น SQL บริสุทธิ์เพื่อให้เครื่องมือเพิ่มประสิทธิภาพสามารถมองเข้าไปได้ ไม่จำเป็นต้องใช้ pgplsql ที่นี่
หาร

8

คำถามนี้ถูกถามในรายชื่อผู้รับจดหมายของ PostgreSQLและฉันคิดวิธีแปลงข้อความ JSON เป็นรูปแบบข้อความ PostgreSQL ผ่านทางแฮ็กเกอร์ผ่านตัวดำเนินการแยกฟิลด์ JSON:

CREATE FUNCTION json_text(json) RETURNS text IMMUTABLE LANGUAGE sql
AS $$ SELECT ('['||$1||']')::json->>0 $$;

db=# select json_text(json_array_elements('["hello",1.3,"\u2603"]'));
 json_text 
-----------
 hello
 1.3
 

โดยทั่วไปมันจะแปลงค่าให้เป็นอาร์เรย์แบบองค์ประกอบเดียวจากนั้นจะขอองค์ประกอบแรก

อีกวิธีคือใช้ตัวดำเนินการนี้เพื่อแยกเขตข้อมูลทั้งหมดแบบหนึ่งต่อหนึ่ง แต่สำหรับอาร์เรย์ขนาดใหญ่สิ่งนี้น่าจะช้ากว่าเนื่องจากมันต้องแยกสตริง JSON ทั้งหมดสำหรับแต่ละองค์ประกอบอาร์เรย์นำไปสู่ความซับซ้อน O (n ^ 2)

CREATE FUNCTION json_array_elements_text(json) RETURNS SETOF text IMMUTABLE LANGUAGE sql
AS $$ SELECT $1->>i FROM generate_series(0, json_array_length($1)-1) AS i $$;

db=# select json_array_elements_text('["hello",1.3,"\u2603"]');
 json_array_elements_text 
--------------------------
 hello
 1.3
 

1

ฉันทดสอบตัวเลือกไม่กี่ตัว นี่คือแบบสอบถามที่ฉันชอบ สมมติว่าเรามีตารางที่มีเขตข้อมูล id และ json เขตข้อมูล json มีอาร์เรย์ซึ่งเราต้องการเปลี่ยนเป็นอาร์เรย์ pg

SELECT * 
FROM   test 
WHERE  TRANSLATE(jsonb::jsonb::text, '[]','{}')::INT[] 
       && ARRAY[1,2,3];

มันทำงานได้ทุกที่และเร็วกว่าที่อื่น ๆ แต่ดู crutchy)

อันดับแรกอาร์เรย์ json ถูกใช้เป็นข้อความแล้วเราก็แค่เปลี่ยนวงเล็บเหลี่ยมเป็นวงเล็บ ในที่สุดข้อความจะถูกนำแสดงโดยเป็นอาร์เรย์ของประเภทที่ต้องการ

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::INT[];

และถ้าคุณชอบข้อความ [] อาร์เรย์

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::TEXT[];

2
SELECT TRANSLATE('{"name": "foo", "tags": ["foo", "bar"]}'::jsonb::text, '[]','{}')::INT[]; ERROR: malformed array literal: "{"name": "foo", "tags": {"foo", "bar"}}"ฉันคิดว่าคุณต้องเพิ่มคำอธิบายบางอย่างเกี่ยวกับวิธีการทำงานนี้
dezso

คำถามคือวิธีการเปลี่ยนอาร์เรย์ JSON (!) เป็นอาร์เรย์ pg สมมติว่าฉันมีตารางที่มีคอลัมน์ id และ jsonb คอลัมน์ JSONb มีอาร์เรย์ json จากนั้น
FiscalCliff

แปล (jsonb :: jsonb :: text, '[]', '{}') :: INT [] แปลงอาร์เรย์ json เป็น pg array
FiscalCliff

SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"มันไม่ได้เป็นเช่นนั้นระเบิดหลักฐาน ...
Dezso

ลองใช้ข้อความ [] สำหรับอาร์เรย์เหล่านี้
FiscalCliff

0

ฟังก์ชั่นบางอย่างที่นำมาจากคำตอบของคำถามนี้คือสิ่งที่ฉันใช้และพวกเขาก็ทำงานได้ดี

CREATE OR REPLACE FUNCTION json_array_casttext(json) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM json_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION jsonb_array_casttext(jsonb) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION json_array_castint(json) RETURNS int[] AS $f$
    SELECT array_agg(x)::int[] || ARRAY[]::int[] FROM json_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION jsonb_array_castint(jsonb) RETURNS int[] AS $f$
    SELECT array_agg(x)::int[] || ARRAY[]::int[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

ในแต่ละของพวกเขาโดยเชื่อมต่อกับอาเรย์ที่ว่างเปล่าพวกเขาจัดการกับกรณีที่ทำให้ฉันต้องใช้สมองนิดหน่อยในการที่ถ้าคุณลองและโยนอาเรย์ที่ว่างเปล่าออกจากjson/ jsonbไม่มีมันคุณจะไม่ได้อะไรคืนมาแทน อาเรย์ที่ว่างเปล่า ( {}) ตามที่คุณคาดหวัง ฉันแน่ใจว่ามีการเพิ่มประสิทธิภาพบางอย่างสำหรับพวกเขา แต่พวกเขาก็ทิ้งไว้เพื่อความเรียบง่ายในการอธิบายแนวคิด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.