Postgres'te bir pencere işlevinin toplamını nasıl alabilirim?


11

Permütasyonlar / tamsayı dizileri kombinasyonları ve böyle bir değer içeren üçüncü bir sütun içeren bir tablo var:

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

Her bir permütasyonun yanı sıra her kombinasyon için ortalama ve standart sapmayı bulmak istiyorum. Bu sorgu ile yapabilirim:

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

Ancak, "foo" tablosu (gerçekte, her biri yaklaşık 4 milyon satırlı 14 bölümden oluşur) iki kez taranması gerektiğinden, bu sorgu çok fazla veri olduğunda oldukça yavaş olabilir.

Son zamanlarda, Postgres'in temelde belirli bir sütun için GROUP BY'a benzeyen "Pencere İşlevleri" ni desteklediğini öğrendim. Ben bu gibi kullanmak için benim sorgu değiştirildi:

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

Bu "combo_count" sütunu için çalışırken, "combo_average_value" ve "combo_stddev" sütunları artık doğru değildir. Her permütasyon için ortalamanın alındığı ve daha sonra her kombinasyon için ikinci bir kez ortalamanın alındığı anlaşılıyor, bu yanlış.

Bunu nasıl düzeltebilirim? Pencere fonksiyonları burada bir optimizasyon olarak kullanılabilir mi?


Mevcut sürüm Postgres 9.2 var mı? Pencere fonksiyonları 8.4 ile geldi.
Erwin Brandstetter

Üzgünüm, belirtmeyi unuttum. Evet, en son Postgres 9.2.4 kullanıyorum.
Scott Small

Yanıtlar:


9

Sen edebilir tek bir sorgu seviyesinde toplama işlevleri sonucuna pencere işlevlere sahiptir.

Bu, birkaç değişiklikten sonra güzel bir şekilde işe yarayacaktı - ancak matematiksel prensipte standart sapma için başarısız oldu . İlgili hesaplamalar doğrusal değildir, bu nedenle alt popülasyonların standart sapmalarını birleştiremezsiniz.

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

Çünkü combo_average_valuebu ifadeye ihtiyacınız olacak

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

Ağırlıklı bir ortalamaya ihtiyacınız olduğundan . (10 üyeli bir grubun ortalaması, sadece 2 üyeli bir grubun ortalamasından daha ağırdır!)

Bu çalışıyor :

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

Burada iki farklı pencere kullanıyorum ve DISTINCTpencere işlevlerinden sonra bile uygulanan satırları azaltıyorum .

Ancak, orijinal sorgunuzdan daha hızlı olacağından şüpheliyim. Emin değilim.

Değiştirilmiş masa düzeni ile daha iyi performans

Diziler 24 baytlık bir ek yüke sahiptir (türe bağlı olarak küçük değişiklikler). Ayrıca, dizi başına oldukça az öğe ve birçok tekrar var gibi görünüyor. Sizinki gibi büyük bir tablo için şemayı normalleştirmek için ödeme yaparsınız . Örnek düzen:

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

Referans bütünlüğüne ihtiyacınız yoksa, yabancı anahtar kısıtlamalarını atlayabilirsiniz.

İle bağlantı combo_idda tabloya yerleştirilebilir perm, ancak bu senaryoda valuedaha iyi performans için onu (biraz normalleştirildi) depolayacağım .

Bu, 32 baytlık satır boyutuyla (grup başlığı + dolgu: 24 bayt, 2 x int (8 bayt), dolgu yok) ve ayrıca numericsütununuzun bilinmeyen boyutuyla sonuçlanır . (Aşırı hassasiyete ihtiyacınız yoksa, bir double precisionveya bir realsütun da olabilir.)

SO veya burada bu ilgili cevapta fiziksel depolama hakkında daha fazla bilgi :
PostgreSQL'i okuma performansı için yapılandırma

Her neyse, bu şu anda sahip olduğunuzun sadece bir kısmı ve sorgunuzu yalnızca boyutlarına göre çok daha hızlı hale getirecekti. Basit tamsayılarda gruplama ve sıralama da çok daha hızlıdır.

Verirsin ilk alt sorguda toplama ve sonra katılmak permve comboen iyi performans için.


Açık ve özlü cevap için teşekkür ederim. Doğru, bir alt küme popülasyonunun standart sapmasını bu şekilde elde etmenin bir yolu yok gibi görünüyor. Bununla birlikte, çözümünüzün sadeliğini seviyorum. GROUP BY grubunun kaldırılması, ortaya çıkan sorguyu daha okunaklı hale getirir. Maalesef, performansın düşük olduğundan şüphelendiğiniz gibi. 30 dakikadan fazla çalıştıktan sonra sorguyu öldürmek zorunda kaldım.
Scott Small

@ScottSmall: Performans için bir şeyler yapabilirsiniz ... cevaplamak için güncellemeye bakın.
Erwin Brandstetter

Sorumu basitleştirmek fooiçin alakalı olmayan sütunları tablodan kaldırdım . Gerçekte, bu sorgu tarafından kullanılmayan birkaç sütun daha var, bu yüzden permütasyonları ve kombinasyonları normalleştirmenin, bu özel kullanım durumu için önemli bir hız artışı sağlayacağına ikna olmadım.
Scott Small

Ek olarak, her bir permütasyonu ve kombinasyonu oluşturan tamsayı değerler DB'deki başka bir tablodan gelir. Bu verilerin önceden oluşturulması hesaplama açısından pahalıdır. Bir perm / combo'nun maksimum uzunluğu 5'tir, ancak 5Pn ve 5Cn büyük n değerleri için (şu anda 1000 civarında, ancak günlük olarak büyüyor) oldukça büyür ... yine de, bu başka bir günün sorusu. Tüm yardımlarınız için tekrar teşekkürler Erwin.
Scott Small
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.