Bagaimana saya mendapatkan agregat fungsi jendela di Postgres?

11

Saya memiliki tabel yang berisi dua kolom permutasi / kombinasi array integer, dan kolom ketiga berisi nilai, seperti:

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

Saya ingin mengetahui rata-rata dan standar deviasi untuk setiap permutasi, serta untuk setiap kombinasi. Saya bisa melakukannya dengan pertanyaan ini:

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

Namun, kueri itu bisa sangat lambat ketika saya memiliki banyak data, karena tabel "foo" (yang pada kenyataannya, terdiri dari 14 partisi masing-masing dengan sekitar 4 juta baris) perlu dipindai dua kali.

Baru-baru ini, saya mengetahui bahwa Postgres mendukung "Fungsi Jendela", yang pada dasarnya seperti GROUP BY untuk kolom tertentu. Saya memodifikasi permintaan saya untuk menggunakan ini seperti ini:

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

Meskipun ini berfungsi untuk kolom "combo_count", kolom "combo_average_value" dan "combo_stddev" tidak lagi akurat. Tampaknya rata-rata diambil untuk setiap permutasi, dan kemudian dirata-rata untuk kedua kalinya untuk setiap kombinasi, yang tidak benar.

Bagaimana saya bisa memperbaikinya? Dapatkah fungsi jendela bahkan digunakan sebagai pengoptimalan di sini?

Scott Small
sumber
Mengasumsikan versi Postgres 9.2 saat ini? Fungsi jendela datang dengan 8.4.
Erwin Brandstetter
Maaf, saya lupa menentukan. Ya saya menggunakan yang terbaru, Postgres 9.2.4.
Scott Small

Jawaban:

9

Anda dapat memiliki fungsi jendela pada hasil fungsi agregat dalam tingkat kueri tunggal.

Ini semua akan bekerja dengan baik setelah beberapa modifikasi - kecuali bahwa gagal untuk standar deviasi pada prinsip matematika . Perhitungan yang terlibat tidak linier, jadi Anda tidak bisa begitu saja menggabungkan standar deviasi sub-populasi.

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

Untuk combo_average_valueAnda perlu ungkapan ini

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

Karena Anda perlu rata-rata tertimbang . (Rata-rata grup dengan 10 anggota memiliki berat lebih dari rata-rata grup dengan hanya 2 anggota!)

Ini bekerja :

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

Saya menggunakan dua jendela berbeda di sini, dan mengurangi baris DISTINCTyang diterapkan bahkan setelah fungsi jendela.

Tapi saya sangat ragu itu akan lebih cepat dari permintaan awal Anda. Saya cukup yakin tidak.

Performa yang lebih baik dengan tata letak tabel yang diubah

Array memiliki overhead 24 byte (sedikit variasi tergantung pada jenisnya). Juga, Anda tampaknya memiliki beberapa item per array dan banyak pengulangan. Untuk meja besar seperti milik Anda, akan lebih baik untuk menormalkan skema. Contoh tata letak:

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

Jika Anda tidak memerlukan integritas referensial, Anda dapat menghilangkan batasan kunci asing.

Koneksi ke combo_idjuga dapat ditempatkan di tabel perm, tetapi dalam skenario ini saya akan menyimpannya (sedikit dinormalisasi) valueuntuk kinerja yang lebih baik.

Ini akan menghasilkan ukuran baris 32 byte (tuple header + padding: 24 byte, 2 x int (8 byte), tanpa bantalan), ditambah ukuran numerickolom Anda yang tidak diketahui . (Jika Anda tidak membutuhkan ketepatan yang ekstrem, kolom double precisionatau bahkan realmungkin juga akan melakukannya.)

Lebih lanjut tentang penyimpanan fisik dalam jawaban terkait ini pada SO atau di sini:
Mengkonfigurasi PostgreSQL untuk kinerja baca

Bagaimanapun, itu hanya sebagian kecil dari apa yang Anda miliki sekarang dan akan membuat kueri Anda jauh lebih cepat berdasarkan ukuran saja. Pengelompokan dan pengurutan pada bilangan bulat sederhana juga jauh lebih cepat.

Pertama - tama Anda akan mengumpulkan dalam subquery dan kemudian bergabung ke permdan combountuk kinerja terbaik.

Erwin Brandstetter
sumber
Terima kasih atas jawaban yang jelas dan ringkas. Anda benar, sepertinya tidak ada cara untuk mendapatkan standar deviasi dari populasi subset dengan cara ini. Yang sedang berkata, saya suka kesederhanaan solusi Anda. Menghilangkan GROUP BY membuat kueri yang dihasilkan jauh lebih mudah dibaca. Sayangnya seperti yang Anda duga kinerjanya di bawah standar. Saya harus mematikan kueri setelah berjalan selama lebih dari 30 menit.
Scott Small
@ScottSmall: Anda dapat melakukan sesuatu untuk kinerja ... lihat pembaruan untuk menjawab.
Erwin Brandstetter
Untuk menyederhanakan pertanyaan saya, saya menghapus kolom dari footabel yang tidak relevan. Pada kenyataannya, ada beberapa kolom lagi yang tidak digunakan oleh permintaan ini, jadi saya tidak yakin bahwa normalisasi permutasi dan kombinasi akan memberikan peningkatan kecepatan yang signifikan, untuk kasus penggunaan khusus ini.
Scott Small
Selain itu, nilai integer yang membentuk permutasi dan kombinasi berasal dari tabel lain di DB. Pra-menghasilkan data ini mahal secara komputasi. Panjang maksimum perm / kombo adalah 5, namun 5Pn dan 5Cn tumbuh cukup besar untuk nilai besar n (saat ini sekitar 1000, tetapi bertambah setiap hari) ... tetap, mengoptimalkan itu adalah pertanyaan hari lain. Sekali lagi terima kasih atas semua bantuan Anda Erwin.
Scott Small