Як отримати сукупність віконної функції в Postgres?


11

У мене є таблиця, що містить два стовпчики перестановок / комбінацій цілих масивів та третій стовпець, що містить значення, наприклад:

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" (яка насправді складається з 14 розділів з кожним розміром приблизно 4 мільйони рядків) потрібно сканувати двічі.

Нещодавно я дізнався, що 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" вже не є точними. Здається, що середнє значення приймається за кожну перестановку, а потім в середньому вказується на кожну комбінацію, що є неправильним.

Як я можу це виправити? Чи можна тут використовувати навіть функції вікон як оптимізацію?


Якщо припустити поточну версію Postgres 9.2? Функції вікон поставляються з 8.4.
Ервін Брандстеттер

Вибачте, я забув вказати. Так, я використовую останню версію, Postgres 9.2.4.
Скотт Малий

Відповіді:


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

Я тут використовую два різних вікна і зменшую рядки, з 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 х int (8 байт), відсутність прокладки), а також невідомий розмір numericстовпця. (Якщо вам не потрібна надзвичайна точність, може бути зроблений теж стовпець double precisionабо навіть realстовпець.)

Детальніше про фізичне зберігання в цьому пов'язаному відповіді на SO або тут:
Налаштування PostgreSQL для продуктивності читання

У будь-якому випадку, це лише частка того, що ви маєте зараз, і зробить ваш запит набагато швидшим лише за розміром. Групування та сортування за простими цілими числами також набагато швидше.

Ви спершу об’єднаєте підзапит, а потім приєднаєтесь до permта comboдля найкращої продуктивності.


Дякую за чітку та стислу відповідь. Ви маєте рацію, здавалося б, немає способу отримати стандартне відхилення сукупності підмножини таким чином. Попри це, мені подобається простота вашого рішення. Усунення GROUP BY робить отриманий запит значно читабельнішим. На жаль, як ви підозрювали, виступ є підпунктним. Мені довелося вбити запит після запуску понад 30 хвилин.
Скотт Малий

@ScottSmall: Ви можете зробити щось для ефективності ... див. Оновлення, щоб відповісти.
Ервін Брандстеттер

Щоб спростити запитання, я видалив стовпці з fooтаблиці, які не були релевантними. Насправді є ще кілька стовпців, які не використовуються цим запитом, тому я не переконаний, що нормалізація перестановок та комбінацій забезпечить значне збільшення швидкості для цього конкретного випадку використання.
Скотт Малий

Крім того, цілі значення, що складають кожну перестановку та комбінацію, походять з іншої таблиці в БД. Попередньо генерувати ці дані обчислювально дорого. Максимальна довжина perm / combo - 5, проте 5Pn і 5Cn зростають досить великими для великих значень n (наразі близько 1000, але зростають щодня) ... все одно, оптимізуючи це питання іншого дня. Ще раз дякую за всю вашу допомогу Ервіне.
Скотт Малий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.