如何将两行转换为 postgresql 中的键值 json 对象?
How to convert two rows into key-value json object in postgresql?
我有一个像下面这样的数据模型,它被简化为只向您展示这个问题(SQL Fiddle Link 在底部):
一个人在数据库中表示为一个元 table 行,具有名称和多个属性,这些属性作为键值对存储在数据 table 中(键和值在单独的列)。
预期结果
现在我想检索所有用户及其所有属性。属性应在单独的列中作为 json 对象返回。例如:
name, data
Florian, { "age":23, "color":"blue" }
Markus, { "age":24, "color":"green" }
我的方法
现在我的问题是,我找不到在 postgres 中创建键值对的方法。我试过以下:
SELECT
name,
array_to_json(array_agg(row(d.key, d.value))) AS data
FROM meta AS m
JOIN (
SELECT d.fk_id, d.key, d.value AS value FROM data AS d
) AS d
ON d.fk_id = m.id
GROUP BY m.name;
但它 returns 作为数据列:
[{"f1":"age","f2":24},{"f1":"color","f2":"blue"}]
其他解决方案
我知道有一个函数 crosstab
可以让我将数据 table 转换为键作为列和值作为行 table。但这不是动态的。而且我不知道一个人在数据中有多少属性table。所以这不是一个选择。
我还可以用两行值创建一个类似于 json 的字符串并将它们聚合。但也许有一个更好的解决方案。
不,无法更改数据模型,因为真实的数据模型已经被多方使用。
SQLFiddle
检查并测试我为这个问题创建的 fiddle:
http://sqlfiddle.com/#!15/bd579/14
我找到了一种 return 使用动态列交叉表数据的方法。也许重写这个会更好地满足您的需求:
CREATE OR REPLACE FUNCTION report.usp_pivot_query_amount_generate(
i_group_id INT[],
i_start_date TIMESTAMPTZ,
i_end_date TIMESTAMPTZ,
i_interval INT
) RETURNS TABLE (
tab TEXT
) AS $ab$
DECLARE
_key_id TEXT;
_text_op TEXT = '';
_ret TEXT;
BEGIN
-- SELECT DISTNICT for query results
FOR _key_id IN
SELECT DISTINCT at_name
FROM report.company_data_date cd
JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id
JOIN report.amount_types at ON cda.amount_type_id = at.id
WHERE date_start BETWEEN i_start_date AND i_end_date
AND group_id = ANY (i_group_id)
AND interval_type_id = i_interval
LOOP
-- build function_call with datatype of column
IF char_length(_text_op) > 1 THEN
_text_op := _text_op || ', ' || _key_id || ' NUMERIC(20,2)';
ELSE
_text_op := _text_op || _key_id || ' NUMERIC(20,2)';
END IF;
END LOOP;
-- build query with parameter filters
_ret = '
SELECT * FROM crosstab(''SELECT date_start, at.at_name, cda.amount ct
FROM report.company_data_date cd
JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id
JOIN report.amount_types at ON cda.amount_type_id = at.id
WHERE date_start between $$' || i_start_date::TEXT || '$$ AND $$' || i_end_date::TEXT || '$$
AND interval_type_id = ' || i_interval::TEXT || '
AND group_id = ANY (ARRAY[' || array_to_string(i_group_id, ',') || '])
ORDER BY date_start'')
AS ct (date_start timestamptz, ' || _text_op || ')';
RETURN QUERY
SELECT _ret;
END;
$ab$ LANGUAGE 'plpgsql';
调用函数获取字符串,然后执行字符串。我想我试过在函数中执行它,但效果不佳。
使用聚合函数json_object_agg(key, value)
:
select
name,
json_object_agg(key, value) as data
from data
join meta on fk_id = id
group by 1;
该功能已在 Postgres 9.4 中引入。
当我需要更新一些 JSON 并删除数据库中的一些元素时,我 运行 遇到了同样的问题。下面的查询对我来说效果很好,因为它保留了字符串引号但不将它们添加到数字中。
select
'{' || substr(x.arr, 3, length(x.arr) - 4) || '}'
from
(
select
replace(replace(cast(array_agg(xx) as varchar), '\"', '"'), '","', ', ') as arr
from
(
select
elem.key,
elem.value,
'"' || elem.key || '":' || elem.value as xx
from
quote q
cross join
json_each(q.detail::json -> 'bQuoteDetail'-> 'quoteHC'->0) as elem
where
elem.key != 'allRiskItems'
) f
) x
我有一个像下面这样的数据模型,它被简化为只向您展示这个问题(SQL Fiddle Link 在底部):
一个人在数据库中表示为一个元 table 行,具有名称和多个属性,这些属性作为键值对存储在数据 table 中(键和值在单独的列)。
预期结果
现在我想检索所有用户及其所有属性。属性应在单独的列中作为 json 对象返回。例如:
name, data
Florian, { "age":23, "color":"blue" }
Markus, { "age":24, "color":"green" }
我的方法
现在我的问题是,我找不到在 postgres 中创建键值对的方法。我试过以下:
SELECT
name,
array_to_json(array_agg(row(d.key, d.value))) AS data
FROM meta AS m
JOIN (
SELECT d.fk_id, d.key, d.value AS value FROM data AS d
) AS d
ON d.fk_id = m.id
GROUP BY m.name;
但它 returns 作为数据列:
[{"f1":"age","f2":24},{"f1":"color","f2":"blue"}]
其他解决方案
我知道有一个函数 crosstab
可以让我将数据 table 转换为键作为列和值作为行 table。但这不是动态的。而且我不知道一个人在数据中有多少属性table。所以这不是一个选择。
我还可以用两行值创建一个类似于 json 的字符串并将它们聚合。但也许有一个更好的解决方案。
不,无法更改数据模型,因为真实的数据模型已经被多方使用。
SQLFiddle
检查并测试我为这个问题创建的 fiddle: http://sqlfiddle.com/#!15/bd579/14
我找到了一种 return 使用动态列交叉表数据的方法。也许重写这个会更好地满足您的需求:
CREATE OR REPLACE FUNCTION report.usp_pivot_query_amount_generate(
i_group_id INT[],
i_start_date TIMESTAMPTZ,
i_end_date TIMESTAMPTZ,
i_interval INT
) RETURNS TABLE (
tab TEXT
) AS $ab$
DECLARE
_key_id TEXT;
_text_op TEXT = '';
_ret TEXT;
BEGIN
-- SELECT DISTNICT for query results
FOR _key_id IN
SELECT DISTINCT at_name
FROM report.company_data_date cd
JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id
JOIN report.amount_types at ON cda.amount_type_id = at.id
WHERE date_start BETWEEN i_start_date AND i_end_date
AND group_id = ANY (i_group_id)
AND interval_type_id = i_interval
LOOP
-- build function_call with datatype of column
IF char_length(_text_op) > 1 THEN
_text_op := _text_op || ', ' || _key_id || ' NUMERIC(20,2)';
ELSE
_text_op := _text_op || _key_id || ' NUMERIC(20,2)';
END IF;
END LOOP;
-- build query with parameter filters
_ret = '
SELECT * FROM crosstab(''SELECT date_start, at.at_name, cda.amount ct
FROM report.company_data_date cd
JOIN report.company_data_amount cda ON cd.id = cda.company_data_date_id
JOIN report.amount_types at ON cda.amount_type_id = at.id
WHERE date_start between $$' || i_start_date::TEXT || '$$ AND $$' || i_end_date::TEXT || '$$
AND interval_type_id = ' || i_interval::TEXT || '
AND group_id = ANY (ARRAY[' || array_to_string(i_group_id, ',') || '])
ORDER BY date_start'')
AS ct (date_start timestamptz, ' || _text_op || ')';
RETURN QUERY
SELECT _ret;
END;
$ab$ LANGUAGE 'plpgsql';
调用函数获取字符串,然后执行字符串。我想我试过在函数中执行它,但效果不佳。
使用聚合函数json_object_agg(key, value)
:
select
name,
json_object_agg(key, value) as data
from data
join meta on fk_id = id
group by 1;
该功能已在 Postgres 9.4 中引入。
当我需要更新一些 JSON 并删除数据库中的一些元素时,我 运行 遇到了同样的问题。下面的查询对我来说效果很好,因为它保留了字符串引号但不将它们添加到数字中。
select
'{' || substr(x.arr, 3, length(x.arr) - 4) || '}'
from
(
select
replace(replace(cast(array_agg(xx) as varchar), '\"', '"'), '","', ', ') as arr
from
(
select
elem.key,
elem.value,
'"' || elem.key || '":' || elem.value as xx
from
quote q
cross join
json_each(q.detail::json -> 'bQuoteDetail'-> 'quoteHC'->0) as elem
where
elem.key != 'allRiskItems'
) f
) x