我想让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"}]

or

{"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库的任何依赖。


博士TL;

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

没有json_agg的jsonb(在9.4中引入)版本。你可以将行聚合到一个数组中,然后转换它们:

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稍微快一些。

没有json_build_object函数的jsonb版本。你可以聚合成一行并转换:

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

与针对这类结果的其他查询不同,当使用to_jsonb时,array_agg似乎要快一些。我怀疑这是由于解析和验证json_agg的JSON结果造成的。

或者你可以使用显式类型转换:

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

or

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

取决于你想要的是json还是jsonb。

(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能够提供你所需要的结果,那么我能够想到的不使用它的唯一原因便是性能方面的考虑。别白费力气了。

零位

聚合函数在对零行进行操作时通常返回NULL。如果有这种可能,您可能希望使用COALESCE来避免它们。举几个例子:

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

Or

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

Hannes Landeholm指出了这一点


同样,如果你想从表中选择字段并将它们聚合为一个数组:

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'}]