2015-05-04 69 views
2

我有这样的一个表:PostgreSQL的行合并使用相同的密钥(hstore或JSON)

+--------+--------------------+ 
| 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,我没有发现任何支持多值合并的东西。我认为这可以通过将同一个键的值合并为一个字符串/文本并将其添加回键/值对来完成。但我坚持实施它。

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

有没有人有这方面的想法?任何建议或头脑风暴都可能有所帮助。谢谢!

回答

0

只是一个注意事项之前:在您desidered输出我会用一些适当的json而不是那种看起来像。所以,根据我正确的输出将是:

+--------+----------------------------------------------------------------------+ 
| ID |        Attribute        | 
+--------+----------------------------------------------------------------------+ 
| 1 | '{"color":["red","green"], "flavor":[], "shape":["square"]}'   |  
+--------+----------------------------------------------------------------------+ 
| 2 | '{"color":["blue","black"], "flavor":["sweat","salty"], "shape":[]}' | 
+--------+----------------------------------------------------------------------+ 

PL/pgSQL的功能,解析JSON属性和执行动态查询会做的工作,这样的事情:

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_*等效函数。

+0

非常感谢您的快速回复。我无法将我的postgres升级到9。4来测试它昨天(json_to_record()需要9.4)。有很多员工需要在postgres中学习。我查找了PAR_table,它看起来像表名的文本表示,并且类似地,PAR_where_clause是where子句的文本。但是我还没有找到解释它如何工作的定义或者它的定义,和regclass一样。您能否介绍一下这些内容,或者提供一些我可以阅读的来源或文章? – icebox

+0

另外,正如我在文章中提到的,属性列是以hstore格式编写的,它看起来像json。 – icebox

+0

为了防止任何SQL注入或不良行为PAR_table被作为类型[regclass](http://www.postgresql.org/docs/current/static/datatype-oid.html)传递给函数,并通过[ format()](http://www.postgresql.org/docs/current/static/functions-string.html#FUNCTIONS-STRING-FORMAT)函数。关于hstore,如果你使用9.4,我建议你使用[jsonb](http://www.postgresql.org/docs/current/static/datatype-json.html)数据类型,它有很多好处。你在你的问题中说过改变数据类型是一个选项。 9.4,我认为这是你能做的最好的。 – Eggplant

0

如果你只是想这个用于显示目的,这可能是不够的:

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