如何将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

Answers:


94

Postgres 9.4或更高版本

显然是受本文启发的,Postgres 9.4添加了缺少的功能:
感谢Laurence Rowe的补丁程序和Andrew Dunstan的提交!

取消嵌套JSON数组。然后使用array_agg()ARRAY构造函数从中构建Postgres 数组。或string_agg()建立一个text 字符串

汇总LATERAL或相关子查询中每行的未嵌套元素。然后保留原始顺序,我们不需要ORDER BYGROUP BY甚至不需要外部查询中的唯一键。看到:

jsonb在以下所有SQL代码中,将“ json”替换为“ jsonb” 。

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值)是不可能的。的真实表示是一个数组。

功能包装

为了使重复使用更加简单,请将逻辑封装在一个函数中:

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 <>在这里拨弄


Postgres 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 Fiddle
db <>在这里拨弄

有关:

注释(自9.4页起已过时)

我们需要a json_array_elements_text(json)的孪生子,json_array_elements(json)text从JSON数组返回正确的值。但这似乎是所提供的JSON函数库中缺少的。或其他一些text从标JSON量值中提取值的函数。我似乎也想念那个。
因此,我即兴创作了trim(),但对于非平凡的案例,这将失败...


一如既往的好帖子,但是在您了解内部原理的情况下,为什么不从array-> 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出来的行的顺序?
FelixGeisendörfer

@Felix:SQL标准中没有正式的保证。(同样,在标准SQL的SELECT列表中,甚至不允许设置返回函数。)但是Postgres手册中有一个非正式的断言。请参阅:dba.stackexchange.com/a/185862/3684-要明确-以轻微的性能损失为代价-请参阅: dba.stackexchange.com/a/27287/3684。就我个人而言,我100%确信自9.4起,该特定表达式在Postgres的每个当前和将来版本中都能正常工作。
欧文·布兰德斯特

@ErwinBrandstetter非常感谢您确认这一点!我目前正在做一篇文章的研究,该文章总结了PostgreSQL提供的正式和非正式担保订购担保,您的回答非常有用!如果您有兴趣阅读该文章,请告诉我,如果没有,请不要担心。我非常感谢您对StackOverflow所做的贡献,多年来,您从中学到了很多!
FelixGeisendö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 2015年

4
此函数应该是纯SQL,以便优化程序可以窥视它。无需在此处使用pgplsql。
划分

8

PostgreSQL邮件列表上问了这个问题,我想出了一种通过JSON字段提取运算符将JSON文本转换为PostgreSQL文本类型的方法:

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];

它在任何地方都可以工作,并且比其他任何人都快,但看起来很残酷)

首先将json数组转换为文本,然后将方括号更改为括号。最后,将文本强制转换为所需类型的数组。

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

并且如果您喜欢text []数组

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 array(!)转换为pg数组。假设我有包含id和jsonb列的表。JSONb列包含json数组。然后
FiscalCliff

TRANSLATE(jsonb :: jsonb :: text,'[]','{}'):: INT []将json数组转换为pg数组。
FiscalCliff

SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"它不是那么防弹……
dezso

考虑将text []用于这些数组
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您将一无所获,而不是返回{}如您所料,清空数组()。我敢肯定它们有一些优化,但是为了简单起见,只保留了它们。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.