J'ai une table contenant deux colonnes de permutations / combinaisons de tableaux entiers, et une troisième colonne contenant une valeur, comme ceci:
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' )
Je veux connaître la moyenne et l'écart type pour chaque permutation, ainsi que pour chaque combinaison. Je peux le faire avec cette requête:
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 );
Cependant, cette requête peut devenir assez lente lorsque j'ai beaucoup de données, car la table "foo" (qui, en réalité, se compose de 14 partitions chacune avec environ 4 millions de lignes) doit être analysée deux fois.
Récemment, j'ai appris que Postgres prend en charge les «fonctions de fenêtre», qui sont essentiellement comme un GROUP BY pour une colonne particulière. J'ai modifié ma requête pour les utiliser comme ceci:
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 );
Bien que cela fonctionne pour la colonne "combo_count", les colonnes "combo_average_value" et "combo_stddev" ne sont plus précises. Il semble que la moyenne soit prise pour chaque permutation, puis en moyenne une deuxième fois pour chaque combinaison, ce qui est incorrect.
Comment puis-je réparer cela? Les fonctions de fenêtre peuvent-elles même être utilisées comme optimisation ici?
la source
Réponses:
Vous pouvez avoir des fonctions de fenêtre sur le résultat des fonctions d'agrégation dans un seul niveau de requête.
Tout cela fonctionnerait bien après quelques modifications - sauf qu'il échoue pour l'écart-type sur le principe mathématique . Les calculs impliqués ne sont pas linéaires, vous ne pouvez donc pas simplement combiner les écarts-types des sous-populations.
Car
combo_average_value
tu aurais besoin de cette expressionPuisque vous avez besoin d'une moyenne pondérée . (La moyenne d'un groupe de 10 membres pèse plus que la moyenne d'un groupe de seulement 2 membres!)
Cela fonctionne :
J'utilise ici deux fenêtres différentes et je réduis les lignes avec
DISTINCT
lesquelles est appliqué même après les fonctions de fenêtre.Mais je doute sérieusement que ce sera plus rapide que votre requête d'origine. Je suis presque sûr que non.
Meilleures performances avec une disposition de table modifiée
Les tableaux ont une surcharge de 24 octets (légères variations selon le type). En outre, vous semblez avoir quelques éléments par tableau et de nombreuses répétitions. Pour une table énorme comme la vôtre, il serait utile de normaliser le schéma. Exemple de disposition:
Si vous n'avez pas besoin d'intégrité référentielle, vous pouvez omettre les contraintes de clé étrangère.
La connexion à
combo_id
pourrait également être placée dans le tableauperm
, mais dans ce scénario, je la stockerais (légèrement dénormalisée)value
pour de meilleures performances.Cela entraînerait une taille de ligne de 32 octets (en-tête de tuple + remplissage: 24 octets, 2 x int (8 octets), pas de remplissage), plus la taille inconnue de votre
numeric
colonne. (Si vous n'avez pas besoin d'une précision extrême, unedouble precision
ou même unereal
colonne peut aussi le faire.)Plus d'informations sur le stockage physique dans cette réponse connexe sur SO ou ici:
Configuration de PostgreSQL pour les performances de lecture
Quoi qu'il en soit, ce n'est qu'une fraction de ce que vous avez maintenant et rendrait votre requête beaucoup plus rapide par sa seule taille. Le regroupement et le tri sur des entiers simples sont également beaucoup plus rapides.
Vous devez d' abord agréger dans une sous-requête, puis vous joindre à
perm
etcombo
pour de meilleures performances.la source
foo
tableau qui n'étaient pas pertinentes. En réalité, il y a plusieurs autres colonnes qui ne sont pas utilisées par cette requête, donc je ne suis pas convaincu que la normalisation des permutations et des combinaisons fournirait une augmentation de vitesse significative, pour ce cas d'utilisation particulier.