web-dev-qa-db-fra.com

Mysql - Prix moyen des 10% des volumes les moins chers (aide à l'algorithme)

Ma question est très similaire à this mais pas la même. Le tableau est le même: lignes avec ID, volume et prix. En bref, j'ai besoin d'une requête SQL qui me donnera les lignes avec les prix les plus bas dont la somme des volumes est inférieure ou égale à 10% du volume total. J'ai besoin du prix moyen de ces rangées. En termes encore plus courts, prix moyen pour le volume le moins cher de 10%. Pour une meilleure explication, j'ai écrit un algorithme en pseudocode sur la façon dont je l'aurais fait en C++:

  1. Trier le tableau par prix (le plus bas en premier).
  2. Calculez le volume total et stockez 10% de cette valeur dans une variable nommée "seuil".
  3. Déclarez une variable "somme" avec la valeur 0 et démarrez une boucle qui parcourra la table triée susmentionnée.
  4. Vérifiez si la "somme" est inférieure au "seuil". Si vrai, ajoutez la valeur de volume de la ligne à "somme" et ajoutez la ligne examinée à une liste. Augmentez l'itérateur de 1.
  5. Répétez l'étape 4 jusqu'à ce que la condition soit fausse. S'il est faux, il doit rompre la boucle et renvoyer la liste.
  6. Faites une moyenne arithmétique des prix de la ligne retournée.

J'ai écrit le tout avec trois explications pour m'assurer que les gens comprennent et, espérons-le, donner leur propre avis sur la façon de résoudre ce problème en SQL. Il peut y avoir une seule fonction, ou une requête très simple qui peut résoudre tout cela, j'ai donc donné un contexte détaillé au problème.

3
Branimir

Si vous utilisez une ancienne version de MySQL comme 5.1 - 5.7, vous devez utiliser des variables utilisateur MySQL car ces versions de MySQL ne prennent pas en charge les fonctions de fenêtre et/ou les expressions de table courantes.

J'ai porté les réponses de @Akina et @mustaccio et le violon d'Akina à une requête de travail MySQL 5.1+.

Avertissement car MySQL 5.1 à MySQL 5.7 n'utilise pas de fonctions de fenêtre et/ou d'expressions de table communes, vous devez écrire du code MySQL délicat comme celui-ci.
Notez que si vous utilisez MySQL 8, utilisez l'une de ces réponses.

Requête

SELECT 
 AVG(price)
FROM (

  SELECT 
     t.price 
   , (@RUNNING_SUM := @RUNNING_SUM + t.volume) AS running_sum
  FROM 
   t
  CROSS JOIN (SELECT @RUNNING_SUM := 0) AS init_user_param
  ORDER BY 
   t.price ASC  
) AS alias
WHERE
 alias.running_sum <= (0.1 * (
                         SELECT 
                            MAX(running_sum)
                         FROM (                      
                           SELECT 
                                t.price 
                             , (@RUNNING_SUM_1 := @RUNNING_SUM_1 + t.volume) AS running_sum
                            FROM 
                           t
                           CROSS JOIN (SELECT @RUNNING_SUM_1 := 0) AS init_user_param
                         ORDER BY 
                         t.price ASC
                      ) AS a
                ))

voir démo

3
Raymond Nijland

les lignes aux prix les plus bas dont la somme des volumes est inférieure ou égale à 10% du volume total. J'ai besoin du prix moyen de ces rangées

Tester:

WITH cte AS ( SELECT price, SUM(volume) OVER (ORDER BY price ASC) summ 
              FROM sourcetable )
SELECT AVG(price)
FROM cte
WHERE summ <= 0.1 * ( SELECT MAX(summ)
                      FROM cte )

violon

2
Akina

Selon la taille de la table, cette variante de solution d'Akina pourrait être un peu plus performante en calculant à la fois le total cumulé et le total total de volume en une seule fois:

WITH cte AS ( 
  SELECT 
    price, 
    SUM(volume) OVER (ORDER BY price ASC) running, 
    SUM(volume) OVER (ORDER BY price ASC 
                      RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) total, 
  FROM sourcetable )
SELECT AVG(price)
FROM cte
WHERE running <= 0.1 * total
2
mustaccio