我有一个类型为data的列json,其中包含这样的JSON文档:

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


我想将嵌套的tags数组转换为连接的字符串('foo, bar')。从理论上讲,使用array_to_string()函数将很容易做到这一点。但是,此功能不接受json输入。所以我想知道如何将这个JSON数组转换成Postgres数组(类型text[])?

评论

您正在寻找json_extract_path_text(your_column,'tags')吗?

@a_horse_with_no_name:剩下的问题:数组元素仍被引用为JSON格式。文本提取不正确...

#1 楼

Postgres 9.4或更高版本

显然受此帖子启发,Postgres 9.4添加了缺少的功能:感谢Laurence Rowe的补丁程序和Andrew Dunstan的贡献!


json_array_elements_text(json)
jsonb_array_elements_text(jsonb)

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

LATERAL或相关子查询中每行汇总未嵌套的元素。然后将保留原始顺序,并且在外部查询中不需要ORDER BYGROUP BY甚至是唯一键。请参阅:


如何将ORDER BY和LIMIT与聚合函数结合使用?

在以下所有SQL中将jsonb的'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;


相关:


LATERAL和PostgreSQL中的子查询有什么区别?

相关子查询中的ARRAY构造函数:

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


相关:


如何结合聚合函数应用ORDER BY和LIMIT?

细微差别:null元素保留在实际数组中。在上述查询中产生text字符串是不可能的,该字符串不能包含null值。真正的表示形式是数组。

函数包装器

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

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


使其具有SQL函数,因此可以在较大的查询中内联。
使其具有IMMUTABLE(因为它是)以避免在较大的查询中重复求值并允许其在索引表达式中使用。
将其设为PARALLEL SAFE(在Postgres 9.6或更高版本中!)以免妨碍并行化。请参见:


何时将功能标记为PARALLEL RESTRICTED与PARALLEL SAFE?

呼叫: /> db <>在这里拨弄



Postgres 9.3或更早版本

使用功能json_array_elements()。但是我们从中得到了双引号字符串。

外部查询中带有聚合的替代查询。 CROSS JOIN删除数组缺失或为空的行。对于处理元素也可能有用。我们需要一个唯一的键来聚合:

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


ARRAY构造函数,但仍带有引号的字符串:

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;


请注意,与上述不同,null转换为文本值“ null”。从严格意义上讲是不正确的,并且可能有歧义。

穷人用trim()取消报价:

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


从tbl中检索一行:

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;


字符串形式相关子查询:

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;


ARRAY构造函数:

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


原始的(过时的)SQL Fiddle.db <>在这里拨弄。

相关:


需要从中动态选择JSON数组元素postgresql表

注释(自pg 9.4起已过时)

我们需要json_array_elements_text(json),这是json_array_elements(json)的孪生子,才能从JSON数组返回正确的text值。但这似乎是所提供的JSON函数库中所缺少的。或其他一些函数从标量text值中提取JSON值。我似乎也错过了那个。
所以我即兴使用trim(),但是对于非平凡的情况,这会失败...

评论


一如既往的好帖子,但是有了您对内部知识的了解,为什么不从array-> jsonb进行强制转换。我可以理解,由于sql-array具有更强的类型,因此无法实现其他类型的转换。仅仅是因为PostgreSQL反对自动生成要转换的代码(int [],bigint [],text [])等。

–埃文·卡洛尔(Evan Carroll)
17年1月17日在19:02

@Evan:您将使用to_jsonb()进行数组-> jsonb转换。

–欧文·布兰德斯特
17年1月19日在2:12

SELECT ARRAY(SELECT json_array_elements_text(_js))是否真的保证保留数组的顺序?从理论上讲,是否不允许优化器更改从json_array_elements_text出来的行的顺序?

– FelixGeisendörfer
19年8月29日在8:48

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

–欧文·布兰德斯特
19年8月29日在9:52

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

– FelixGeisendörfer
19年8月29日在11:36



#2 楼

PG 9.4+

肯定的答案是您所需要的,但是为了简单起见,这里我使用了一个帮助程序:

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array(p_input jsonb)
 RETURNS text[]
 LANGUAGE sql
 IMMUTABLE
AS $function$

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

$function$;


然后再做:

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


更新2/23/2020以回应评论:评论是正确的,因为这样做可能更有效。在我发布时,还没有提供模块化的解决方案,所以我认真地提供了一个解决方案,即使不是最优的。从那以后,Erwin用简单高效的功能更新了他的答案,所以我从未更新过我的答案。现在更新它,因为仍然有更多人关注此答案。

又有一个更新,因为这让我有点烦:
如果没有值,上述函数将返回null。根据您的情况,这可能不是理想的。这是一个函数,如果值不是null,则返回空数组,但如果输入为null,则仍返回null。

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array_strict(p_input jsonb)
 RETURNS text[]
 LANGUAGE sql
 IMMUTABLE
AS $function$

SELECT 
  CASE 
    WHEN p_input IS null 
    THEN null 
    ELSE coalesce(ary_out, ARRAY[]::text[]) 
  END
FROM (
  SELECT array_agg(ary)::text[] AS ary_out
  FROM jsonb_array_elements_text(p_input) AS ary
) AS extracted;

$function$
;


评论


我在回答中添加了一些更快的表达式,并且函数更简单。这可以便宜得多。

–欧文·布兰德斯特
2015年10月6日,下午2:46

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

–划分
16年4月18日在10:00

...,并且应该是IMMUTABLE而不是VOLATILE,因为它不涉及数据库,并且任何给定的输入将始终生成相同的输出。

–user9645
2月19日下午17:55

#3 楼

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

CREATE FUNCTION json_text(json) RETURNS text IMMUTABLE LANGUAGE sql
AS $$ SELECT ('['||||']')::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 ->>i FROM generate_series(0, json_array_length()-1) AS i $$;

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


#4 楼

我已经测试了一些选项。这是我最喜欢的查询。假设我们有一个包含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[];


评论


SELECT TRANSLATE('{“ name”:“ foo”,“ tags”:[“ foo”,“ bar”]}'::: jsonb :: text,'[]','{}'):: INT [] ;错误:格式不正确的数组文字:“ {” name“:” foo“,” tags“:{” foo“,” bar“}}”“我认为您必须添加一些有关此方法的解释。

– dezso
16 Dec 5'在9:20

问题是如何将JSON array(!)转换为pg数组。假设我有包含id和jsonb列的表。 JSONb列包含json数组。然后

–FiscalCliff
16 Dec 5'在19:12

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

–FiscalCliff
16 Dec 5'在19:20

SELECT translation('[“ foo”,“ bar”]'::: jsonb :: text,'[]','{}'):: INT [];错误:整数:“ foo”的输入语法无效。

– dezso
16 Dec 6'在10:31

考虑对这些数组使用text []

–FiscalCliff
16 Dec 6'在11:27

#5 楼

这几个函数取自该问题的答案,它们是我正在使用的函数,它们在运行中效果很好。

CREATE OR REPLACE FUNCTION json_array_casttext(json) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM json_array_elements_text() 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() 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() 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() t(x);
$f$ LANGUAGE sql IMMUTABLE;


在每个函数中,通过与一个空值连接数组,它们处理了一个让我费尽脑筋的情况,因为如果您尝试从json / jsonb投射一个空数组,如果没有它,您将一无所获,而不是像空数组({})期望。我敢肯定他们有一些优化,但是为了简单起见,只剩下它们了。