web-dev-qa-db-ja.com

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

次のように、整数配列の置換/組み合わせの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回目の平均がとられているようですが、これは誤りです。

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

11
Scott Small

あなたは単一のクエリレベルの集計関数の結果にウィンドウ関数を持つことができます

これは、いくつかの変更を行った後、すべてうまく機能します。ただし、が数学的な原理の標準偏差で失敗するを除きます。関連する計算は線形ではないので、部分母集団の標準偏差を単純に組み合わせることができません。

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 列のサイズが不明になります。 。 (極端な精度が必要ない場合は、double precision列またはreal列でも可能です。)

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

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

あなたはサブクエリでfirst集約し、そしてpermcomboに結合するのが最善ですパフォーマンス。

9