Postgresでウィンドウ関数の集計を取得するにはどうすればよいですか?


11

次のように、整数配列の置換/組み合わせの2つの列を含むテーブルと、値を含む3番目の列があります。

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"テーブル(実際には、それぞれ約400万行の14のパーティションで構成されています)を2回スキャンする必要があるためです。

最近、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」列は正確ではなくなりました。順列ごとに平均がとられ、その後、組み合わせごとに2回目に平均化されているようですが、これは誤りです。

どうすれば修正できますか?ここでウィンドウ関数を最適化として使用することもできますか?


現在のバージョンのPostgres 9.2を想定していますか?ウィンドウ関数は8.4に付属しています。
Erwin Brandstetter 2013

指定するのを忘れました。はい、最新のPostgres 9.2.4を使用しています。
Scott Small

回答:


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);

ここでは2つの異なるウィンドウを使用し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列のサイズが不明になります。(極端な精度が必要ない場合は、1 double precisionつのreal列または1 つの列でさえも可能です。)

物理ストレージの詳細については、この関連する回答のSOまたはここで:
読み取りパフォーマンスのためのPostgreSQLの構成

とにかく、それはあなたが今持っているもののほんの一部であり、サイズだけでクエリをはるかに速くするでしょう。単純な整数のグループ化と並べ替えもはるかに高速です。

あなたはでしょう最初のサブクエリで集計して、その後に参加permし、combo最高のパフォーマンスのために。


明確で簡潔な回答をありがとうございます。あなたは正しいです。この方法でサブセット母集団の標準偏差を取得する方法はないようです。そうは言っても、私はあなたのソリューションのシンプルさが好きです。GROUP BYを削除すると、結果のクエリが非常に読みやすくなります。残念ながら、パフォーマンスは標準以下です。30分以上実行した後、クエリを強制終了する必要がありました。
Scott Small

@ScottSmall:パフォーマンスのために何かをすることができます...答えは更新を参照してください。
Erwin Brandstetter 2013

質問を簡単にするために、foo関係のない列をテーブルから削除しました。実際には、このクエリで使用されない列がさらにいくつかあるので、この特定のユースケースでは、順列と組み合わせを正規化すると大幅な速度向上が得られるとは思いません。
Scott Small

さらに、各置換と組み合わせを構成する整数値は、DB内の別のテーブルから取得されます。このデータを事前に生成すると、計算コストが高くなります。パーマ/コンボの最大長は5ですが、5Pnと5Cnは、nの大きな値(現在は約1000ですが、毎日増加しています)に対して非常に大きくなります...とにかく、最適化は別の日の問題です。Erwinのご協力に感謝いたします。
Scott Small
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.