Postgresql 合并具有相同键的行(hstore 或 json)
Postgresql merge rows with same key (hstore or json)
我有一个 table 这样的:
+--------+--------------------+
| ID | Attribute |
+--------+--------------------+
| 1 |"color" => "red" |
+--------+--------------------+
| 1 |"color" => "green" |
+--------+--------------------+
| 1 |"shape" => "square" |
+--------+--------------------+
| 2 |"color" => "blue" |
+--------+--------------------+
| 2 |"color" => "black" |
+--------+--------------------+
| 2 |"flavor" => "sweat" |
+--------+--------------------+
| 2 |"flavor" => "salty" |
+--------+--------------------+
我想 运行 一些得到结果的 postgres 查询 table 像这样:
+--------+------------------------------------------------------+
| ID | Attribute |
+--------+------------------------------------------------------+
| 1 |"color" => "red, green", "shape" => "square" |
+--------+------------------------------------------------------+
| 2 |"color" => "blue, black", "flavor" => "sweat, salty" |
+--------+------------------------------------------------------+
属性列可以是hstore 或json 格式。我举个例子写在hstore中,但是如果我们不能在hstore中实现,但是在json中,我会把列改成json。
我知道hstore不支持一键多值,当我尝试一些合并方法时,它只为每个键保留一个值。但是对于json,我也没有找到任何支持这样的多值合并的东西。我认为这可以通过函数将同一键的值合并到 string/text 并将其添加回 key/value 对来完成。但我坚持执行它。
注意:如果在某些函数中实现此功能,理想情况下任何键(如颜色、形状)都不应出现在函数中,因为键可以动态扩展。
有人对此有任何想法吗?任何建议或头脑风暴都可能有所帮助。谢谢!
先说明一下:在您想要的输出中,我会使用一些适当的 json
而不是那种 相似的 。所以根据我的正确输出是:
+--------+----------------------------------------------------------------------+
| ID | Attribute |
+--------+----------------------------------------------------------------------+
| 1 | '{"color":["red","green"], "flavor":[], "shape":["square"]}' |
+--------+----------------------------------------------------------------------+
| 2 | '{"color":["blue","black"], "flavor":["sweat","salty"], "shape":[]}' |
+--------+----------------------------------------------------------------------+
一个解析 json 属性并执行动态查询的 PL/pgSQL 函数就可以完成这项工作,类似于:
CREATE OR REPLACE FUNCTION merge_rows(PAR_table regclass) RETURNS TABLE (
id integer,
attributes json
) AS $$
DECLARE
ARR_attributes text[];
VAR_attribute text;
ARR_query_parts text[];
BEGIN
-- Get JSON attributes names
EXECUTE format('SELECT array_agg(name ORDER BY name) AS name FROM (SELECT DISTINCT json_object_keys(attribute) AS name FROM %s) AS s', PAR_table) INTO ARR_attributes;
-- Write json_build_object() query part
FOREACH VAR_attribute IN ARRAY ARR_attributes LOOP
ARR_query_parts := array_append(ARR_query_parts, format('%L, array_remove(array_agg(l.%s), null)', VAR_attribute, VAR_attribute));
END LOOP;
-- Return dynamic query
RETURN QUERY EXECUTE format('
SELECT t.id, json_build_object(%s) AS attributes
FROM %s AS t,
LATERAL json_to_record(t.attribute) AS l(%s)
GROUP BY t.id;',
array_to_string(ARR_query_parts, ', '), PAR_table, array_to_string(ARR_attributes, ' text, ') || ' text');
END;
$$ LANGUAGE plpgsql;
我已经测试过了,它似乎可以工作,它 returns 一个 json。这是我的测试代码:
CREATE TABLE mytable (
id integer NOT NULL,
attribute json NOT NULL
);
INSERT INTO mytable (id, attribute) VALUES
(1, '{"color":"red"}'),
(1, '{"color":"green"}'),
(1, '{"shape":"square"}'),
(2, '{"color":"blue"}'),
(2, '{"color" :"black"}'),
(2, '{"flavor":"sweat"}'),
(2, '{"flavor":"salty"}');
SELECT * FROM merge_rows('mytable');
当然您也可以将 id
和 attribute
列名作为参数传递,也许可以稍微改进一下函数,这只是给您一个想法。
编辑: 如果您使用的是 9.4,请考虑使用 jsonb
数据类型,它要好得多,并为您提供了改进的空间。您只需要将 json_*
函数更改为其 jsonb_*
等价物。
如果您只是为了显示目的,这可能就足够了:
select id, string_agg(key||' => '||vals, ', ')
from (
select t.id, x.key, string_agg(value, ',') vals
from t
join lateral each(t.attributes) x on true
group by id, key
) t
group by id;
如果你不是 9.4,则不能使用横向连接:
select id, string_agg(key||' => '||vals, ', ')
from (
select id, key, string_agg(val, ',') as vals
from (
select t.id, skeys(t.attributes) as key, svals(t.attributes) as val
from t
) t1
group by id, key
) t2
group by id;
这将 return:
id | string_agg
---+-------------------------------------------
1 | color => red,green, shape => square
2 | color => blue,black, flavor => sweat,salty
SQLFiddle:http://sqlfiddle.com/#!15/98caa/2
我有一个 table 这样的:
+--------+--------------------+
| ID | Attribute |
+--------+--------------------+
| 1 |"color" => "red" |
+--------+--------------------+
| 1 |"color" => "green" |
+--------+--------------------+
| 1 |"shape" => "square" |
+--------+--------------------+
| 2 |"color" => "blue" |
+--------+--------------------+
| 2 |"color" => "black" |
+--------+--------------------+
| 2 |"flavor" => "sweat" |
+--------+--------------------+
| 2 |"flavor" => "salty" |
+--------+--------------------+
我想 运行 一些得到结果的 postgres 查询 table 像这样:
+--------+------------------------------------------------------+
| ID | Attribute |
+--------+------------------------------------------------------+
| 1 |"color" => "red, green", "shape" => "square" |
+--------+------------------------------------------------------+
| 2 |"color" => "blue, black", "flavor" => "sweat, salty" |
+--------+------------------------------------------------------+
属性列可以是hstore 或json 格式。我举个例子写在hstore中,但是如果我们不能在hstore中实现,但是在json中,我会把列改成json。
我知道hstore不支持一键多值,当我尝试一些合并方法时,它只为每个键保留一个值。但是对于json,我也没有找到任何支持这样的多值合并的东西。我认为这可以通过函数将同一键的值合并到 string/text 并将其添加回 key/value 对来完成。但我坚持执行它。
注意:如果在某些函数中实现此功能,理想情况下任何键(如颜色、形状)都不应出现在函数中,因为键可以动态扩展。
有人对此有任何想法吗?任何建议或头脑风暴都可能有所帮助。谢谢!
先说明一下:在您想要的输出中,我会使用一些适当的 json
而不是那种 相似的 。所以根据我的正确输出是:
+--------+----------------------------------------------------------------------+
| ID | Attribute |
+--------+----------------------------------------------------------------------+
| 1 | '{"color":["red","green"], "flavor":[], "shape":["square"]}' |
+--------+----------------------------------------------------------------------+
| 2 | '{"color":["blue","black"], "flavor":["sweat","salty"], "shape":[]}' |
+--------+----------------------------------------------------------------------+
一个解析 json 属性并执行动态查询的 PL/pgSQL 函数就可以完成这项工作,类似于:
CREATE OR REPLACE FUNCTION merge_rows(PAR_table regclass) RETURNS TABLE (
id integer,
attributes json
) AS $$
DECLARE
ARR_attributes text[];
VAR_attribute text;
ARR_query_parts text[];
BEGIN
-- Get JSON attributes names
EXECUTE format('SELECT array_agg(name ORDER BY name) AS name FROM (SELECT DISTINCT json_object_keys(attribute) AS name FROM %s) AS s', PAR_table) INTO ARR_attributes;
-- Write json_build_object() query part
FOREACH VAR_attribute IN ARRAY ARR_attributes LOOP
ARR_query_parts := array_append(ARR_query_parts, format('%L, array_remove(array_agg(l.%s), null)', VAR_attribute, VAR_attribute));
END LOOP;
-- Return dynamic query
RETURN QUERY EXECUTE format('
SELECT t.id, json_build_object(%s) AS attributes
FROM %s AS t,
LATERAL json_to_record(t.attribute) AS l(%s)
GROUP BY t.id;',
array_to_string(ARR_query_parts, ', '), PAR_table, array_to_string(ARR_attributes, ' text, ') || ' text');
END;
$$ LANGUAGE plpgsql;
我已经测试过了,它似乎可以工作,它 returns 一个 json。这是我的测试代码:
CREATE TABLE mytable (
id integer NOT NULL,
attribute json NOT NULL
);
INSERT INTO mytable (id, attribute) VALUES
(1, '{"color":"red"}'),
(1, '{"color":"green"}'),
(1, '{"shape":"square"}'),
(2, '{"color":"blue"}'),
(2, '{"color" :"black"}'),
(2, '{"flavor":"sweat"}'),
(2, '{"flavor":"salty"}');
SELECT * FROM merge_rows('mytable');
当然您也可以将 id
和 attribute
列名作为参数传递,也许可以稍微改进一下函数,这只是给您一个想法。
编辑: 如果您使用的是 9.4,请考虑使用 jsonb
数据类型,它要好得多,并为您提供了改进的空间。您只需要将 json_*
函数更改为其 jsonb_*
等价物。
如果您只是为了显示目的,这可能就足够了:
select id, string_agg(key||' => '||vals, ', ')
from (
select t.id, x.key, string_agg(value, ',') vals
from t
join lateral each(t.attributes) x on true
group by id, key
) t
group by id;
如果你不是 9.4,则不能使用横向连接:
select id, string_agg(key||' => '||vals, ', ')
from (
select id, key, string_agg(val, ',') as vals
from (
select t.id, skeys(t.attributes) as key, svals(t.attributes) as val
from t
) t1
group by id, key
) t2
group by id;
这将 return:
id | string_agg
---+-------------------------------------------
1 | color => red,green, shape => square
2 | color => blue,black, flavor => sweat,salty
SQLFiddle:http://sqlfiddle.com/#!15/98caa/2