在 GROUP BY 中聚合 Postgres 中的 hstore
Aggregate hstore in Postgres within GROUP BY
我有这样的 hstore 数据:
|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" |
我希望能够按键汇总 hstores,按品牌分组:
|brand|likes|views |
|-----|-----|----------------------|
|Ford |3 |"3"=>"300", "5"=>"10" |
|Jeep |7 |"3"=>"700", "5"=>"20" |
This answer 提供了一个很好的解决方案,说明如何在没有 GROUP BY 的情况下执行此操作。使它适应这种情况会给出类似的东西:
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
但是这给出了:
ERROR: subquery uses ungrouped column "my_table.views" from outer query
感谢任何帮助!
这是因为在group by
查询中使用了没有聚合函数的views
列。
非常快速的解决方法:
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
更新
您还可以为此类任务创建 aggregate:
--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() 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 = '{}'
);
之后你的查询会更简单更多"canonical":
select
brand,
sum(likes) as total_likes,
hstore_sum(views) as total_views
from my_table
group by brand;
更新 2
即使没有 create aggregate
函数 hstore_sum_ffunc
也可能有用:
select
brand,
sum(likes) as total_likes,
hstore_sum_ffunc(array_agg(views)) as total_views
from my_table
group by brand;
如果您为 hstore
创建聚合,这会更容易一些:
create aggregate hstore_agg(hstore)
(
sfunc = hs_concat(hstore, hstore),
stype = hstore
);
那么你可以这样做:
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;
另一种选择是将可能很慢的相关子查询移动到 CTE 中:
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;
我有这样的 hstore 数据:
|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" |
我希望能够按键汇总 hstores,按品牌分组:
|brand|likes|views |
|-----|-----|----------------------|
|Ford |3 |"3"=>"300", "5"=>"10" |
|Jeep |7 |"3"=>"700", "5"=>"20" |
This answer 提供了一个很好的解决方案,说明如何在没有 GROUP BY 的情况下执行此操作。使它适应这种情况会给出类似的东西:
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
但是这给出了:
ERROR: subquery uses ungrouped column "my_table.views" from outer query
感谢任何帮助!
这是因为在group by
查询中使用了没有聚合函数的views
列。
非常快速的解决方法:
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
更新
您还可以为此类任务创建 aggregate:
--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() 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 = '{}'
);
之后你的查询会更简单更多"canonical":
select
brand,
sum(likes) as total_likes,
hstore_sum(views) as total_views
from my_table
group by brand;
更新 2
即使没有 create aggregate
函数 hstore_sum_ffunc
也可能有用:
select
brand,
sum(likes) as total_likes,
hstore_sum_ffunc(array_agg(views)) as total_views
from my_table
group by brand;
如果您为 hstore
创建聚合,这会更容易一些:
create aggregate hstore_agg(hstore)
(
sfunc = hs_concat(hstore, hstore),
stype = hstore
);
那么你可以这样做:
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;
另一种选择是将可能很慢的相关子查询移动到 CTE 中:
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;