使用postgres将不同的行数据转换/交叉

时间:2014-07-17 19:44:05

标签: sql postgresql pivot

我有透明/交叉的不同数据,例如

给表A

name  tag
Bob   sport
Bob   action
Bob   comedy
Tom   action
Tom   drama
Sue   sport

我想要一个将数据转换为

的查询
name  sport  action  comedy  drama
Bob   1      1       1       0
Tom   0      1       0       1
Sue   1      0       0       0

任意数量n个不同的标签。

如果我在开始之前不知道不同的标签,我将如何使用sql创建此转换。

1 个答案:

答案 0 :(得分:2)

一些简单的解决方案适用于某些情况。使用此表(SQL Fiddle现在无法正常工作)

create table a (
    name text,
    tag text
);
insert into a (name, tag) values
('Bob', 'sport'),
('Bob', 'action'),
('Bob', 'comedy'),
('Tom', 'action'),
('Tom', 'drama'),
('Sue', 'sport');

一个简单的数组聚合,如果它们可以在其他地方拆分

select
    name,
    array_agg(tag order by tag) as tags,
    array_agg(total order by tag) as totals
from (
    select name, tag, count(a.name) as total
    from
        a
        right join (
            (select distinct tag from a) t
            cross join
            (select distinct name from a) n
        ) c using (name, tag)
    group by name, tag
) s
group by name
order by 1
;
 name |            tags             |  totals   
------+-----------------------------+-----------
 Bob  | {action,comedy,drama,sport} | {1,1,0,1}
 Sue  | {action,comedy,drama,sport} | {0,0,0,1}
 Tom  | {action,comedy,drama,sport} | {1,0,1,0}

对于JSON感知客户端,有一组JSON对象

select format(
    '{%s:{%s}}',
    to_json(name),
    string_agg(o, ',')
)::json as o
from (
    select name,
    format(
        '%s:%s',
        to_json(tag),
        to_json(count(a.name))
    ) as o
    from
        a
        right join (
            (select distinct tag from a) t
            cross join
            (select distinct name from a) n
        ) c using (name, tag)
    group by name, tag
) s
group by name
;
                          o                          
-----------------------------------------------------
 {"Bob":{"action":1,"comedy":1,"drama":0,"sport":1}}
 {"Sue":{"action":0,"comedy":0,"drama":0,"sport":1}}
 {"Tom":{"action":1,"comedy":0,"drama":1,"sport":0}}

或单个JSON对象

select format('{%s}', string_agg(o, ','))::json as o
from (
    select format(
        '%s:{%s}',
        to_json(name),
        string_agg(o, ',')
    ) as o
    from (
        select name,
        format(
            '%s:%s',
            to_json(tag),
            to_json(count(a.name))
        ) as o
        from
            a
            right join (
                (select distinct tag from a) t
                cross join
                (select distinct name from a) n
            ) c using (name, tag)
        group by name, tag
    ) s
    group by name
) s
;
                                                                            o                                                                            
---------------------------------------------------------------------------------------------------------------------------------------------------------
 {"Bob":{"action":1,"comedy":1,"drama":0,"sport":1},"Sue":{"action":0,"comedy":0,"drama":0,"sport":1},"Tom":{"action":1,"comedy":0,"drama":1,"sport":0}}