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');

当然您也可以将 idattribute 列名作为参数传递,也许可以稍微改进一下函数,这只是给您一个想法。

编辑: 如果您使用的是 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