web-dev-qa-db-ja.com

SQLにPRODUCT集計関数がないのはなぜですか?

SUMの動作に似たSELECT PRODUCT(table.price) FROM table GROUP BY table.saleのようなものを探しています。

ドキュメンテーションで何かを見逃したことがありますか、それとも本当にPRODUCT関数がありませんか?

もしそうなら、なぜですか?

注:postgres、mysql、mssqlで関数を探しましたが、どれも見つかりませんでしたので、すべてのsqlでサポートされていないと仮定しました。

46
lock

SQL標準にはPRODUCT set関数はありません。ただし、(たとえば、CONCATENATE set関数とは異なり、SQLには適していません。たとえば、結果のデータ型には複数の値が含まれ、最初の標準形式に関して問題が発生します) 。

SQL Standardsは、1990年頃のSQL製品全体の機能を統合し、将来の開発で「思考リーダーシップ」を提供することを目的としています。要するに、彼らはSQLが何をし、SQLが何をすべきかを文書化します。 PRODUCT set関数がないことは、1990年に含める価値があるベンダーがなく、標準に導入することに学術的な関心がなかったことを示唆しています。

もちろん、ベンダーは常に独自の機能を追加しようとしていますが、最近では通常、接線ではなく標準への拡張として使用されています。私が使用したSQL製品のいずれかでPRODUCT set関数(またはそれに対する要求さえ)を見たことを覚えていません。

いずれの場合でも、log set関数でexpおよびSUMスカラー関数(およびネガを処理するロジック)を使用すると、回避策は非常に簡単です。サンプルコードについては、@ gbnの回答をご覧ください。ただし、ビジネスアプリケーションでこれを行う必要はありません。

結論として、私の最良の推測は、SQLエンドユーザーからPRODUCT set関数に対する要求がないということです。さらに、学術的な興味を持つ人なら誰でも、おそらく回避策を受け入れられると思うでしょう(つまり、PRODUCT set関数が提供する構文糖を評価しません)。

興味深いことに、SQL Server Landには新しいセット関数が必要ですが、ウィンドウ関数の種類(および標準SQLも)が必要です。さらなる需要に関与する方法など、詳細については、 Itzik Ben-Ganのブログ を参照してください。

26
onedaywhen

MSSQLの場合、これを使用できます。それは他のプラットフォームに採用することができます:それは単に対数の数学と集計です。

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

ここでの私の答えから: SQL Serverクエリ-グループごとの乗算

49
gbn

なぜないのかはわかりませんが、(負の数にはもっと注意してください)ログと指数を使用して行うことができます:-

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

製品の集計関数を実行できますが、このように自分で計算する必要があります...

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

ソース: http://productfunctionsql.codeplex.com/

8
Fenton

T-SQLには、一連の行の文字列値を1つの変数に連結できるようにする巧妙なトリックがあります(ANSIかどうかはわかりません)。乗算にも機能するようです:

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

これは、log/expソリューションよりも数値的に安定している可能性があります。

6

それは、多くの製品に対応できるナンバリングシステムがないためだと思います。データベースは多数のレコード用に設計されているため、1000個の数の積は非常に大きくなり、浮動小数点数の場合、伝播されるエラーは膨大になります。

また、ログの使用は危険なソリューションになる可能性があることに注意してください。数学的にlog(a * b)= log(a)* log(b)ですが、実数を扱っていないため、コンピューターにはない可能性があります。 a * bではなく2 ^(log(a)+ log(b))を計算すると、予期しない結果が生じる可能性があります。例えば:

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

sQL Serverの戻り値

999999999644820000025518、9.999999999644812E + 23

だから私のポイントは、あなたが製品を慎重にやろうとしているときであり、テストは重いです。

3
naiem

この問題に対処する1つの方法(スクリプト言語で作業している場合)は、group_concat関数を使用することです。たとえば、SELECT group_concat(table.price) FROM table GROUP BY table.sale

これは、同じ販売価格のすべての価格をコンマで区切った文字列を返します。次に、パーサーを使用して各価格を取得し、乗算を実行できます。 (phpでは、実際にはarray_reduce関数を使用することもできます。実際には php.netマニュアル に適切な例があります)。

乾杯

1
Nicolás Sierra

1つのアプローチは、次の2つの組み込み関数を組み合わせることです。

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

デカルト積のカーディナリティは特定のセットのカーディナリティの積であるという事実に基づく別のアプローチ;-)

⚠警告:この例は単なる楽しみのためであり、かなり学術的なものです。実稼働では使用しないでください!(事実上、正の実際に小さな整数のためだけです)⚠

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

この問題は、ウィンドウ関数やCTEなどの最新のSQL機能を使用して解決できます。すべてが標準SQLであり、対数ベースのソリューションとは異なり、整数の世界から浮動小数点の世界への切り替えや非正数の処理は必要ありません。行に番号を付け、行がなくなるまで再帰クエリで製品を評価します。

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

あなたの質問は販売コラムによるグループ化を伴うので、事態は少し複雑になりましたが、それでも解決可能です:

   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;

結果:

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

Postgresでテスト済み。

0