web-dev-qa-db-fra.com

Pourquoi n'y a-t-il pas de fonction d'agrégation PRODUCT dans SQL?

Je cherche quelque chose comme SELECT PRODUCT(table.price) FROM table GROUP BY table.sale similaire à la façon dont SUM fonctionne.

Ai-je oublié quelque chose dans la documentation ou n'y a-t-il vraiment pas de fonction PRODUCT?

Si oui, pourquoi pas?

Remarque: J'ai cherché la fonction dans postgres, mysql et mssql et n'en ai trouvé aucune, j'ai donc supposé que sql ne la supportait pas.

46
lock

Il n'y a pas de fonction PRODUCT set dans le standard SQL. Il semblerait que ce soit un bon candidat (contrairement à, disons, une fonction CONCATENATE set: ce n'est pas un bon choix pour SQL, par exemple le type de données résultant impliquerait plusieurs valeurs et poserait un problème en ce qui concerne la première forme normale) .

Les normes SQL visent à consolider les fonctionnalités des produits SQL vers 1990 et à fournir un "leadership éclairé" sur le développement futur. En bref, ils documentent ce que fait SQL et ce que SQL doit faire. L'absence de PRODUCT fonction set suggère qu'en 1990 aucun fournisseur ne méritait d'être inclus et qu'il n'y avait aucun intérêt académique à l'introduire dans la norme.

Bien sûr, les fournisseurs ont toujours cherché à ajouter leurs propres fonctionnalités, ces jours-ci généralement en tant qu'extensions aux normes plutôt que tangentiellement. Je ne me souviens pas avoir vu une fonction PRODUCT set (ou même une demande) dans l'un des produits SQL que j'ai utilisés.

Dans tous les cas, le travail est assez simple en utilisant les fonctions scalaires log et exp (et la logique pour gérer les négatifs) avec la fonction SUM set; voir la réponse de @ gbn pour un exemple de code. Cependant, je n'ai jamais eu besoin de le faire dans une application métier.

En conclusion, ma meilleure supposition est qu'il n'y a aucune demande de la part des utilisateurs finaux SQL pour une fonction PRODUCT set; en outre, que toute personne ayant un intérêt académique trouverait probablement la solution de contournement acceptable (c'est-à-dire qu'elle ne valoriserait pas le sucre syntaxique qu'une fonction d'ensemble PRODUCT fournirait).

Par intérêt, il existe en effet une demande dans SQL Server Land pour de nouvelles fonctions de set mais pour celles de la variété des fonctions de fenêtre (et Standard SQL aussi). Pour plus de détails, y compris comment s'impliquer dans la poursuite de la demande, consultez le blog d'Itzik Ben-Gan .

26
onedaywhen

Pour MSSQL, vous pouvez l'utiliser. Il peut être adopté pour d'autres plates-formes: ce ne sont que des mathématiques et des agrégats sur des logarithmes.

SELECT
    GrpID,
    CASE
       WHEN MinVal = 0 THEN 0
       WHEN Neg % 2 = 1 THEN -1 * EXP(ABSMult)
       ELSE EXP(ABSMult)
    END
FROM
    (
    SELECT
       GrpID, 
       --log of +ve row values
       SUM(LOG(ABS(NULLIF(Value, 0)))) AS ABSMult,
       --count of -ve values. Even = +ve result.
       SUM(SIGN(CASE WHEN Value < 0 THEN 1 ELSE 0 END)) AS Neg,
       --anything * zero = zero
       MIN(ABS(Value)) AS MinVal
    FROM
       Mytable
    GROUP BY
       GrpID
    ) foo

Tiré de ma réponse ici: Requête SQL Server - multiplication par groupe

49
gbn

Je ne sais pas pourquoi il n'y en a pas, mais (faites plus attention aux nombres négatifs), vous pouvez utiliser des journaux et des exposants pour faire: -

select exp (sum (ln (table.price))) from table ...
22
Lord Peter

Vous pouvez effectuer une fonction d'agrégation de produits, mais vous devez faire les calculs vous-même, comme ceci ...

SELECT
    Exp(Sum(IIf(Abs([Num])=0,0,Log(Abs([Num])))))*IIf(Min(Abs([Num]))=0,0,1)*(1-2*(Sum(IIf([Num]>=0,0,1)) Mod 2)) AS P
FROM
   Table1

Source: http://productfunctionsql.codeplex.com/

8
Fenton

Il existe une astuce intéressante dans T-SQL (vous ne savez pas si c'est ANSI) qui permet de concaténer les valeurs de chaîne d'un ensemble de lignes en une variable. Il semble que cela fonctionne également pour la multiplication:

declare @Floats as table (value float)
insert into @Floats values (0.9)
insert into @Floats values (0.9)
insert into @Floats values (0.9)

declare @multiplier float = null

select 
    @multiplier = isnull(@multiplier, '1') * value
from @Floats

select @multiplier

Cela peut potentiellement être plus stable numériquement que la solution log/exp.

6
David Airapetyan

Je pense que c'est parce qu'aucun système de numérotation n'est capable d'accueillir de nombreux produits. Comme les bases de données sont conçues pour un grand nombre d'enregistrements, un produit de 1000 nombres serait super massif et en cas de nombres à virgule flottante, l'erreur propagée serait énorme.

Notez également que l'utilisation du journal peut être une solution dangereuse. Bien que mathématiquement log (a * b) = log (a) * log (b), il se peut que ce ne soit pas dans les ordinateurs car nous n'avons pas affaire à des nombres réels. Si vous calculez 2 ^ (log (a) + log (b)) au lieu de * b, vous pouvez obtenir des résultats inattendus. Par exemple:

SELECT 9999999999 * 99999999974482, EXP (LOG (9999999999) + LOG (99999999974482))

dans Sql Server renvoie

999999999644820000025518, 9.99999999644812E + 23

Donc, mon point est lorsque vous essayez de faire le produit avec soin et que le test est lourd.

3
naiem

Une façon de résoudre ce problème (si vous travaillez dans un langage de script) consiste à utiliser la fonction group_concat. Par exemple, SELECT group_concat(table.price) FROM table GROUP BY table.sale

Cela renverra une chaîne avec tous les prix pour la même valeur de vente, séparés par une virgule. Ensuite, avec un analyseur, vous pouvez obtenir chaque prix et faire une multiplication. (En php, vous pouvez même utiliser la fonction array_reduce, en fait dans le manuel php.net vous obtenez un exemple approprié).

À votre santé

1
Nicolás Sierra

Une approche consiste à combiner deux fonctions intégrées, comme suit:

SELECT table.sale, AVG(table.price) * COUNT(table.price) FROM table GROUP BY table.sale
0
ddunn801

Une autre approche basée sur le fait que la cardinalité du produit cartésien est le produit des cardinalités d'ensembles particuliers ;-)

⚠ AVERTISSEMENT: Cet exemple est juste pour le plaisir et est plutôt académique, ne l'utilisez pas en production! (À part le fait que c'est juste pour des entiers positifs et pratiquement petits) ⚠

with recursive t(c) as (
  select unnest(array[2,5,7,8])
), p(a) as (
  select array_agg(c) from t
  union all
  select p.a[2:]
  from p
  cross join generate_series(1, p.a[1])
)
select count(*) from p where cardinality(a) = 0;
0
Tomáš Záluský

Le problème peut être résolu en utilisant des fonctionnalités SQL modernes telles que les fonctions de fenêtre et les CTE. Tout est du SQL standard et, contrairement aux solutions basées sur le logarithme, ne nécessite pas de passer d'un monde entier à un monde à virgule flottante ni de gérer des nombres non positifs. Il suffit de numéroter les lignes et d'évaluer le produit dans une requête récursive jusqu'à ce qu'il ne reste aucune ligne:

   with recursive t(c) as (
     select unnest(array[2,5,7,8])
   ), r(c,n) as (
     select t.c, row_number() over () from t
   ), p(c,n) as (
     select c, n from r where n = 1
     union all
     select r.c * p.c, r.n from p join r on p.n + 1 = r.n
   )
   select c from p where n = (select max(n) from p);

Comme votre question concerne le regroupement par colonne de vente, les choses se sont un peu compliquées, mais elles sont toujours résolubles:

   with recursive t(sale,price) as (
     select 'multiplication', 2 union
     select 'multiplication', 5 union
     select 'multiplication', 7 union
     select 'multiplication', 8 union
     select 'trivial', 1 union
     select 'trivial', 8 union
     select 'negatives work', -2 union
     select 'negatives work', -3 union
     select 'negatives work', -5 union
     select 'look ma, zero works too!', 1 union
     select 'look ma, zero works too!', 0 union
     select 'look ma, zero works too!', 2
   ), r(sale,price,n,maxn) as (
     select t.sale, t.price, row_number() over (partition by sale), count(1) over (partition by sale)
     from t
   ), p(sale,price,n,maxn) as (
     select sale, price, n, maxn
     from r where n = 1
     union all
     select p.sale, r.price * p.price, r.n, r.maxn
     from p
     join r on p.sale = r.sale and p.n + 1 = r.n
   )
   select sale, price
   from p
   where n = maxn
   order by sale;

Résultat:

sale,price
"look ma, zero works too!",0
multiplication,560
negatives work,-30
trivial,8

Testé sur Postgres.

0
Tomáš Záluský