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.
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 .
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
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 ...
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
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.
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.
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é
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
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;
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.