如何使用窗口函数聚合Postgres中的唯一值

时间:2014-02-13 21:20:36

标签: sql postgresql aggregate-functions

(使用Postgres 9.1)

我的计划涉及杀虫剂喷雾器,试图根据原始目标喷洒多个实际单位。例如,喷雾器乔应该喷射10个目标,但他实际喷射了7个。

我提供的表格是所有计划目标(列= 目标)和实际目标(列= 实际)的摘要,以及其他一些数据,包括喷雾器的名称。这是架构:

CREATE TABLE spray_summary
(
 id character varying(1),
 target integer,
 ref_id character varying(1),
 actual integer,
 sprayer character varying(25)
)

数据是(id,target)和(ref_id,actual,sprayer)之间的非规范化连接,但是现在这个表是我必须要处理的。这是完整的结果:

SELECT * FROM spray_summary
+----+--------+--------+--------+---------+
| id | target | ref_id | actual | sprayer |
+----+--------+--------+--------+---------+
| a  |      1 | "l"    |     10 | "Joe"   |
| a  |      1 | "m"    |     10 | "Joe"   |
| a  |      1 | "p"    |     10 | "Joe"   |
| c  |      3 | "n"    |     10 | "Joe"   |
| c  |      3 | "o"    |     10 | "Joe"   |
+----+--------+--------+--------+---------+

由于一对多连接和“c”重复两次,您可以看到 id 值“a”重复三次。鉴于此,我想要做的是显示目标值的SUM,“实际”值的SUM和喷雾器。我使用了以下查询:

SELECT SUM(target) targets, SUM(actual) actuals, sprayer FROM spray_summary GROUP BY sprayer

返回了结果:

+--------+--------+---------+
| target | actual | sprayer |
+--------+--------+---------+
|      9 |     50 | "Joe"   |
+--------+--------+---------+

虽然实际值的总和是正确的(5 * 10 = 50),但目标值正在相乘,因为数据集是非规范化的。我希望“target”相对于 id sprayer 是唯一的,所以我尝试了一个窗口函数:

SELECT SUM(target) OVER(PARTITION BY sprayer, id),
sprayer,
SUM(actual)
FROM spray_summary
GROUP BY sprayer, target, id

这给了我结果:

+--------+--------+---------+
| target | actual | sprayer |
+--------+--------+---------+
|      1 |     30 | "Joe"   |
|      3 |     20 | "Joe"   |
+--------+--------+---------+

还是不对!正确的解决方案将提供以下内容:

+--------+--------+---------+
| target | actual | sprayer |
+--------+--------+---------+
|      4 |     50 | "Joe"   |
+--------+--------+---------+

但无论我多少尝试调整窗口函数,行都会被拆分,因为我到GROUP BY 目标,这会分散行。有任何想法吗?我知道这可以通过将表连接到自身来重写,一次用于SUM 目标,一次用于SUM 实际,但我没有这个选项。我只能修改列定义。

提前致谢。

编辑:我知道这可以通过将一些逻辑推入子查询来解决,但如果可能的话,我正在寻找列级解决方案。 SQL是自动生成的,所以我对结构没有多少控制权,但我可以修改列定义,因此我正在寻找列级解决方案,就像窗口函数一样。

最糟糕的情况是Postgres无法在列级别解决这个问题,我将不得不重新编写SQL生成器。

1 个答案:

答案 0 :(得分:0)

SQL Fiddle

select sum(target) as target, sum(actual) as actual, sprayer
from (
    select
        target,
        sum(actual) as actual,
        sprayer
    from spray_summary
    group by id, target, sprayer
) s
group by sprayer
order by sprayer
;
 target | actual | sprayer 
--------+--------+---------
      4 |     50 | joe