如何在Postgres中获取窗口函数的集合?


11

我有一张表,其中包含两列整数数组的排列/组合,第三列包含一个值,如下所示:

CREATE TABLE foo
(
  perm integer[] NOT NULL,
  combo integer[] NOT NULL,
  value numeric NOT NULL DEFAULT 0
);
INSERT INTO foo
VALUES
( '{3,1,2}', '{1,2,3}', '1.1400' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0.9280' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,1,2}', '{1,2,3}', '1.2680' ),
( '{3,1,2}', '{1,2,3}', '0' ),
( '{3,2,1}', '{1,2,3}', '0' ),
( '{3,2,1}', '{1,2,3}', '0.8000' )

我想找出每个排列以及每个组合的平均偏差和标准偏差。我可以使用以下查询来做到这一点:

SELECT
  f1.perm,
  f2.combo,
  f1.perm_average_value,
  f2.combo_average_value,
  f1.perm_stddev,
  f2.combo_stddev,
  f1.perm_count,
  f2.combo_count
FROM
(
  SELECT
    perm,
    combo,
    avg( value ) AS perm_average_value,
    stddev_pop( value ) AS perm_stddev,
    count( * ) AS perm_count
  FROM foo
  GROUP BY perm, combo
) AS f1
JOIN
(
  SELECT
    combo,
    avg( value ) AS combo_average_value,
    stddev_pop( value ) AS combo_stddev,
    count( * ) AS combo_count
  FROM foo
  GROUP BY combo
) AS f2 ON ( f1.combo = f2.combo );

但是,当我有大量数据时,该查询会变得非常慢,因为“ foo”表(实际上由14个分区组成,每个分区大约有400万行)需要扫描两次。

最近,我了解到Postgres支持“窗口函数”,这对于特定列基本上像GROUP BY。我修改了查询以如下方式使用它们:

SELECT
  perm,
  combo,
  avg( value ) as perm_average_value,
  avg( avg( value ) ) over w_combo AS combo_average_value,
  stddev_pop( value ) as perm_stddev,
  stddev_pop( avg( value ) ) over w_combo as combo_stddev,
  count( * ) as perm_count,
  sum( count( * ) ) over w_combo AS combo_count
FROM foo
GROUP BY perm, combo
WINDOW w_combo AS ( PARTITION BY combo );

尽管这对于“ combo_count”列有效,但“ combo_average_value”和“ combo_stddev”列不再准确。似乎是对每个排列取平均值,然后对每个组合第二次取平均值,这是不正确的。

我怎样才能解决这个问题?窗口功能甚至可以用作优化吗?


假设当前版本为Postgres 9.2?窗口功能带有8.4。
Erwin Brandstetter

抱歉,我忘了指定。是的,我正在使用最新的Postgres 9.2.4。
Scott Small

Answers:


9

可以在单个查询级别上针对集合函数的结果使用窗口函数。

进行一些修改后,这一切都将很好地工作-除非它因数学原理的标准偏差失败。涉及的计算不是线性的,因此您不能简单地组合子群体的标准偏差。

SELECT perm
      ,combo
      ,avg(value)                 AS perm_average_value
      ,sum(avg(value) * count(*)) OVER w_combo /
       sum(count(*)) OVER w_combo AS combo_average_value
      ,stddev_pop(value)          AS perm_stddev
      ,0                          AS combo_stddev  -- doesn't work!
      ,count(*)                   AS perm_count
      ,sum(count(*)) OVER w_combo AS combo_count
FROM   foo
GROUP  BY perm, combo
WINDOW w_combo  AS (PARTITION BY combo);

因为combo_average_value你需要这个表达

sum(avg(value) * count(*)) OVER w_combo / sum(count(*)) OVER w_combo

由于您需要加权平均值。(一个只有10个成员的小组的平均权重比只有2个成员的小组的平均权重更大!)

这有效

SELECT DISTINCT ON (perm, combo)
       perm
      ,combo
      ,avg(value)        OVER wpc AS perm_average_value
      ,avg(value)        OVER wc  AS combo_average_value
      ,stddev_pop(value) OVER wpc AS perm_stddev
      ,stddev_pop(value) OVER wc  AS combo_stddev
      ,count(*)          OVER wpc AS perm_count
      ,count(*)          OVER wc  AS combo_count
FROM   foo
WINDOW wc  AS (PARTITION BY combo)
      ,wpc AS (PARTITION BY perm, combo);

我在这里使用了两个不同的窗口,并减少了DISTINCT窗口功能之后应用的行数。

但是我严重怀疑它会比您原来的查询更快。我敢肯定不是。

更改表格布局可提高性能

数组的开销为24个字节(根据类型的不同而略有变化)。而且,您似乎每个数组有很多项目,并且有很多重复。对于像您这样的大表,将需要对模式进行规范化。布局示例:

CREATE TABLE combo ( 
  combo_id serial PRIMARY KEY
 ,combo    int[] NOT NULL
);

CREATE TABLE perm ( 
  perm_id  serial PRIMARY KEY
 ,perm     int[] NOT NULL
);

CREATE TABLE value (
  perm_id  int REFERENCES perm(perm_id)
 ,combo_id int REFERENCES combo(combo_id)
 ,value numeric NOT NULL DEFAULT 0
);

如果不需要参照完整性,则可以省略外键约束。

与的连接combo_id也可以放在表中perm,但是在这种情况下,我会将其存储(略微归一化)value以提高性能。

这将导致行大小为32字节(元组头+填充:24字节,2 x int(8字节),无填充),以及numeric列的未知大小。(如果您不需要极高的精度,double precision也可以使用一real列甚至一列。)

在SO上的相关答案中或在这里,有关物理存储的更多信息:
配置PostgreSQL以获得读取性能

无论如何,这仅是您现在所拥有的一小部分,仅凭大小便可以使您的查询快得多。对简单整数进行分组和排序也要快得多。

您将首先聚合一个子查询,然后加入permcombo获得最佳性能。


感谢您简洁明了的回答。您是正确的,似乎无法以这种方式获得子集总体的标准偏差。话虽如此,我喜欢您解决方案的简单性。消除GROUP BY使结果查询更具可读性。不幸的是,您怀疑性能不及预期。在运行30分钟后,我不得不终止查询。
Scott Small

@ScottSmall:您可以为提高性能而做些事情 ……请参阅更新答案。
Erwin Brandstetter

为了简化我的问题,我从foo表中删除了不相关的列。实际上,此查询未使用其他几列,因此,我不认为对这种特殊用例进行规范化排列和组合将大大提高速度。
Scott Small

此外,构成每个排列和组合的整数值来自数据库中的另一个表。预先生成此数据在计算上是昂贵的。烫发/梳子的最大长度为5,但是5Pn和5Cn对于较大的n值(当前大约为1000,但每天都在增长)变得非常大...无论如何,优化是另一天的问题。再次感谢您对Erwin的所有帮助。
Scott Small
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.