2017-03-21 4 views
1

Ich habe Daten mit einem hstore wie folgt aus:Aggregate hstore in Postgres innerhalb GROUP BY

|brand|account|likes|views     | 
|-----|-------|-----|----------------------| 
|Ford |ford_uk|1 |"3"=>"100"   | 
|Ford |ford_us|2 |"3"=>"200", "5"=>"10" | 
|Jeep |jeep_uk|3 |"3"=>"300"   | 
|Jeep |jeep_us|4 |"3"=>"400", "5"=>"20" | 

Ich mag wäre in der Lage sein, die hstores durch Schlüssel zu fassen, nach Marke gruppiert:

|brand|likes|views     | 
|-----|-----|----------------------| 
|Ford |3 |"3"=>"300", "5"=>"10" | 
|Jeep |7 |"3"=>"700", "5"=>"20" | 

This answer gibt eine gute Lösung, wie Sie dies ohne eine GROUP BY tun können. Anpassung an diese Situation etwas wie gibt:

SELECT 
    sum(likes) AS total_likes, 
(SELECT hstore(array_agg(key), array_agg(value::text)) 
    FROM (
    SELECT s.key, sum(s.value::integer) 
    FROM (
     SELECT((each(views)).*) 
    ) AS s(key, value) 
    GROUP BY key 
) x(key, value)) AS total_views 
FROM my_table 
GROUP BY brand 

jedoch ergibt dies:

ERROR: subquery uses ungrouped column "my_table.views" from outer query

Jede Hilfe dankbar!

Antwort

4

Es ist wegen der Verwendung views Spalte ohne Aggregatfunktion in der group by Abfrage.
Sehr schnelle Abhilfe:

with my_table(brand,account,likes,views) as (
    values 
    ('Ford', 'ford_uk', 1, '"3"=>"100"'::hstore), 
    ('Ford', 'ford_uk', 2, '"3"=>"200", "5"=>"10"'), 
    ('Jeep', 'jeep_uk', 3, '"3"=>"300"'::hstore), 
    ('Jeep', 'jeep_uk', 4, '"3"=>"400", "5"=>"20"')) 
SELECT 
    brand, 
    sum(likes) AS total_likes, 
(SELECT hstore(array_agg(key), array_agg(value::text)) 
    FROM (
    SELECT s.key, sum(s.value::integer) 
    FROM 
     unnest(array_agg(views)) AS h, --<< aggregate views according to the group by, then unnest it into the table 
     each(h) as s(key,value) 
    GROUP BY key 
) x(key, value)) AS total_views 
FROM my_table 
GROUP BY brand 

aktualisieren

Auch können Sie die aggregate für solche Aufgaben zu erstellen:

--drop aggregate if exists hstore_sum(hstore); 
--drop function if exists hstore_sum_ffunc(hstore[]); 
create function hstore_sum_ffunc(hstore[]) returns hstore language sql immutable as $$ 
    select hstore(array_agg(key), array_agg(value::text)) 
    from 
    (select s.key, sum(s.value::numeric) as value 
    from unnest($1) as h, each(h) as s(key, value) group by s.key) as t 
$$; 
create aggregate hstore_sum(hstore) 
(
    SFUNC = array_append, 
    STYPE = hstore[], 
    FINALFUNC = hstore_sum_ffunc, 
    INITCOND = '{}' 
); 

Danach Ihre Abfrage einfacher sein wird und mehr "kanonischen" :

select 
    brand, 
    sum(likes) as total_likes, 
    hstore_sum(views) as total_views 
from my_table 
group by brand; 

aktualisieren 2

Auch ohne create aggregate die Funktion hstore_sum_ffunc könnte nützlich sein:

select 
    brand, 
    sum(likes) as total_likes, 
    hstore_sum_ffunc(array_agg(views)) as total_views 
from my_table 
group by brand; 
+0

Wirklich cool @Abelisto. Sehr geschätzt. –

1

Wenn Sie ein Aggregat für hstore erstellen, wird dies ein wenig leichter:

create aggregate hstore_agg(hstore) 
(
    sfunc = hs_concat(hstore, hstore), 
    stype = hstore 
); 

Sie dann kann dies tun:

with totals as (
    select t1.brand, 
     hstore(k, sum(v::int)::text) as views 
    from my_table t1, each(views) x(k,v) 
    group by brand, k 
) 
select brand, 
     (select sum(likes) from my_table t2 where t1.brand = t2.brand) as likes, 
     hstore_agg(views) as views 
from totals t1 
group by brand; 

Eine weitere Möglichkeit ist es, die Zusammenarbeit im Zusammenhang Unterabfrage zu bewegen, die in einen CTE langsam sein könnten:

with vals as (
    select t1.brand, 
     hstore(k, sum(v::int)::text) as views 
    from my_table t1, each(views) x(k,v) 
    group by brand, k 
), view_totals as (
    select brand, 
     hstore_agg(views) as views 
    from vals 
    group by brand 
), like_totals as (
    select brand, 
     sum(likes) as likes 
    from my_table 
    group by brand 
) 
select vt.brand, 
     lt.likes, 
     vt.views 
from view_totals vt 
    join like_totals lt on vt.brand = lt.brand 
order by brand;