【问题标题】:Postgresql merge rows with same key (hstore or json)Postgresql 合并具有相同键的行(hstore 或 json)
【发布时间】:2015-05-04 15:16:28
【问题描述】:

我有一张这样的桌子:

+--------+--------------------+   
|   ID   |   Attribute        |  
+--------+--------------------+ 
|    1   |"color" => "red"    |    
+--------+--------------------+  
|    1   |"color" => "green"  | 
+--------+--------------------+ 
|    1   |"shape" => "square" | 
+--------+--------------------+ 
|    2   |"color" => "blue"   | 
+--------+--------------------+ 
|    2   |"color" => "black"  | 
+--------+--------------------+ 
|    2   |"flavor" => "sweat" | 
+--------+--------------------+ 
|    2   |"flavor" => "salty" | 
+--------+--------------------+ 

我想运行一些 postgres 查询来获得这样的结果表:

+--------+------------------------------------------------------+   
|   ID   |                    Attribute                         |  
+--------+------------------------------------------------------+ 
|    1   |"color" => "red, green", "shape" => "square"          |    
+--------+------------------------------------------------------+  
|    2   |"color" => "blue, black", "flavor" => "sweat, salty"  | 
+--------+------------------------------------------------------+ 

属性列可以是hstore或者json格式。我在hstore中写了一个例子,但是如果我们在hstore中无法实现,但是在json中,我会将列更改为json。

我知道 hstore 不支持一个键到多个值,当我尝试一些合并方法时,它只为每个键保留一个值。但是对于json,我也没有找到任何支持这样的多值合并的东西。我认为这可以通过函数将同一键的值合并到字符串/文本中并将其添加回键/值对来完成。但我坚持实施它。

注意:如果在某些函数中实现,理想情况下,任何键如颜色、形状都不应出现在函数中,因为键可以动态扩展。

有人对此有任何想法吗?任何建议或头脑风暴都可能有所帮助。谢谢!

【问题讨论】:

    标签: json postgresql multimap hstore


    【解决方案1】:

    在其他任何事情之前请注意:在您想要的输出中,我会使用一些正确的json,而不是那种lookalike。所以根据我的正确输出是:

    +--------+----------------------------------------------------------------------+   
    |   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;
    

    我已经测试过了,它似乎可以工作,它返回一个 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_* 等效项。

    【讨论】:

    • 非常感谢您的快速回复。昨天我无法将我的 postgres 升级到 9.4 来测试它(json_to_record() 需要 9.4)。我需要在 postgres 中学习很多员工。我查看了 PAR_table,它似乎是表名的文本表示,同样 PAR_where_clause 是 where 子句的文本。但我还没有找到解释它是如何工作的或它的定义的东西,对于 regclass 也是如此。您能否对这些有所了解,或者提供一些我可以阅读的资料或文章?
    • 另外,正如我在帖子中提到的,属性列是用hstore格式编写的,看起来确实像json。
    • 为了防止任何 SQL 注入或不良行为,PAR_tableregclass 类型传递给函数,并通过format() 函数使用。关于 hstore,如果您使用的是 9.4,我建议您使用 jsonb 数据类型,它有很多好处。您在问题中说更改数据类型是一种选择。对于 9.4,我认为这是你能做的最好的。
    【解决方案2】:

    如果您只是想将其用于显示目的,这可能就足够了:

    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;
    

    这将返回:

    id | string_agg                                
    ---+-------------------------------------------
     1 | color => red,green, shape => square       
     2 | color => blue,black, flavor => sweat,salty
    

    SQLFiddle:http://sqlfiddle.com/#!15/98caa/2

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-16
      • 2011-08-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多