¿Cómo obtengo el agregado de una función de ventana en Postgres?

11

Tengo una tabla que contiene dos columnas de permutaciones / combinaciones de matrices de enteros, y una tercera columna que contiene un valor, así:

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

Quiero averiguar la desviación promedio y estándar para cada permutación, así como para cada combinación. Puedo hacer eso con esta consulta:

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

Sin embargo, esa consulta puede ser bastante lenta cuando tengo muchos datos, porque la tabla "foo" (que en realidad consiste en 14 particiones cada una con aproximadamente 4 millones de filas) necesita ser escaneada dos veces.

Recientemente, aprendí que Postgres admite "Funciones de ventana", que es básicamente como un GROUP BY para una columna en particular. Modifiqué mi consulta para usarlos así:

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

Si bien esto funciona para la columna "combo_count", las columnas "combo_average_value" y "combo_stddev" ya no son precisas. Parece que se toma el promedio para cada permutación y luego se promedia una segunda vez para cada combinación, lo cual es incorrecto.

¿Cómo puedo arreglar esto? ¿Se pueden usar las funciones de ventana como una optimización aquí?

Scott Small
fuente
¿Asumiendo la versión actual de Postgres 9.2? Las funciones de ventana vienen con 8.4.
Erwin Brandstetter
Lo siento, olvidé especificar. Sí, estoy usando la última versión de Postgres 9.2.4.
Scott Small

Respuestas:

9

Usted puede tener funciones de la ventana sobre el resultado de las funciones de agregado en un solo nivel de consulta.

Todo esto funcionaría bien después de algunas modificaciones, excepto que falla para la desviación estándar en el principio matemático . Los cálculos involucrados no son lineales, por lo que no puede simplemente combinar desviaciones estándar de subpoblaciones.

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

Porque combo_average_valuenecesitarías esta expresión

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

Ya que necesita un promedio ponderado . (¡El promedio de un grupo con 10 miembros pesa más que el promedio de un grupo con solo 2 miembros!)

Esto funciona :

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

Estoy usando dos ventanas diferentes aquí, y reduzco las filas con las DISTINCTque se aplica incluso después de las funciones de la ventana.

Pero dudo seriamente que sea más rápido que su consulta original. Estoy bastante seguro de que no lo es.

Mejor rendimiento con diseño de tabla alterado

Las matrices tienen una sobrecarga de 24 bytes (ligeras variaciones según el tipo). Además, parece tener bastantes elementos por matriz y muchas repeticiones. Para una mesa enorme como la suya, sería normalizar el esquema. Diseño de ejemplo:

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

Si no necesita integridad referencial, puede omitir las restricciones de clave externa.

La conexión combo_idtambién se podría colocar en la tabla perm, pero en este escenario la almacenaría (ligeramente desnormalizada) valuepara un mejor rendimiento.

Esto daría como resultado un tamaño de fila de 32 bytes (encabezado de tupla + relleno: 24 bytes, 2 x int (8 bytes), sin relleno), más el tamaño desconocido de su numericcolumna. (Si no necesita una precisión extrema, una double precisiono incluso una realcolumna también podrían necesitarlo ).

Más sobre almacenamiento físico en esta respuesta relacionada en SO o aquí:
Configuración de PostgreSQL para rendimiento de lectura

De todos modos, eso es solo una fracción de lo que tiene ahora y haría que su consulta sea mucho más rápida solo por tamaño. Agrupar y ordenar enteros simples también es mucho más rápido.

Lo haría primero agregada en una subconsulta y luego unirse a permy combopara un mejor rendimiento.

Erwin Brandstetter
fuente
Gracias por la respuesta clara y concisa. Tiene razón, parece que no hay forma de obtener la desviación estándar de una población de subconjuntos de esta manera. Dicho esto, me gusta la simplicidad de su solución. La eliminación de GROUP BY hace que la consulta resultante sea mucho más legible. Desafortunadamente, como sospechaba, el rendimiento es inferior. Tuve que matar la consulta después de ejecutar durante más de 30 minutos.
Scott Small
@ScottSmall: Podrías hacer algo por el rendimiento ... consulta la actualización para responder.
Erwin Brandstetter
Para simplificar mi pregunta, eliminé las columnas de la footabla que no eran relevantes. En realidad, hay varias columnas más que no se utilizan en esta consulta, por lo que no estoy convencido de que la normalización de las permutaciones y combinaciones proporcionaría un aumento de velocidad significativo, para este caso de uso en particular.
Scott Small
Además, los valores enteros que forman cada permutación y combinación provienen de otra tabla en la base de datos. La pregeneración de estos datos es computacionalmente costosa. La longitud máxima de un perm / combo es 5, sin embargo, 5Pn y 5Cn crecen bastante grandes para valores grandes de n (actualmente alrededor de 1000, pero crecen diariamente) ... de todos modos, optimizar esa es la pregunta de otro día. Gracias de nuevo por toda su ayuda Erwin.
Scott Small