次のように、整数配列の置換/組み合わせの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回目の平均がとられているようですが、これは誤りです。
どうすれば修正できますか?ここでウィンドウ関数を最適化として使用することもできますか?
あなたは単一のクエリレベルの集計関数の結果にウィンドウ関数を持つことができます。
これは、いくつかの変更を行った後、すべてうまく機能します。ただし、が数学的な原理の標準偏差で失敗するを除きます。関連する計算は線形ではないので、部分母集団の標準偏差を単純に組み合わせることができません。
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集約し、そしてperm
とcombo
に結合するのが最善ですパフォーマンス。