40

In SQL there are aggregation operators, like AVG, SUM, COUNT. Why doesn't it have an operator for multiplication? "MUL" or something.

I was wondering, does it exist for Oracle, MSSQL, MySQL ? If not is there a workaround that would give this behaviour?

Shadowed
  • 956
  • 7
  • 19
maephisto
  • 4,952
  • 11
  • 53
  • 73
  • 3
    http://stackoverflow.com/questions/5218375/aggregate-multiplicate-function/5219216#5219216 --- tricky math-based solution. – zerkms Mar 24 '11 at 07:51
  • possible duplicate of [is there a PRODUCT function like there is a SUM function in Oracle SQL?](http://stackoverflow.com/questions/403924/is-there-a-product-function-like-there-is-a-sum-function-in-oracle-sql) – onedaywhen Mar 24 '11 at 09:25
  • @onedaywhen - accepted answer there doesn't work for 0 / <0 numbers. Besides, OP is trying to cover 3 DBMS in one question. "exact" duplicate is a stretch – RichardTheKiwi Mar 24 '11 at 09:35
  • Simpler solution: http://stackoverflow.com/questions/3653586 – gbn May 20 '11 at 04:55

7 Answers7

57

By MUL do you mean progressive multiplication of values?

Even with 100 rows of some small size (say 10s), your MUL(column) is going to overflow any data type! With such a high probability of mis/ab-use, and very limited scope for use, it does not need to be a SQL Standard. As others have shown there are mathematical ways of working it out, just as there are many many ways to do tricky calculations in SQL just using standard (and common-use) methods.

Sample data:

Column
1
2
4
8

COUNT : 4 items (1 for each non-null)
SUM   : 1 + 2 + 4 + 8 = 15
AVG   : 3.75 (SUM/COUNT)
MUL   : 1 x 2 x 4 x 8 ? ( =64 )

For completeness, the Oracle, MSSQL, MySQL core implementations *

Oracle : EXP(SUM(LN(column)))   or  POWER(N,SUM(LOG(column, N)))
MSSQL  : EXP(SUM(LOG(column)))  or  POWER(N,SUM(LOG(column)/LOG(N)))
MySQL  : EXP(SUM(LOG(column)))  or  POW(N,SUM(LOG(N,column)))
  • Care when using EXP/LOG in SQL Server, watch the return type http://msdn.microsoft.com/en-us/library/ms187592.aspx
  • The POWER form allows for larger numbers (using bases larger than Euler's number), and in cases where the result grows too large to turn it back using POWER, you can return just the logarithmic value and calculate the actual number outside of the SQL query


* LOG(0) and LOG(-ve) are undefined. The below shows only how to handle this in SQL Server. Equivalents can be found for the other SQL flavours, using the same concept

create table MUL(data int)
insert MUL select 1 yourColumn union all
           select 2 union all
           select 4 union all
           select 8 union all
           select -2 union all
           select 0

select CASE WHEN MIN(abs(data)) = 0 then 0 ELSE
       EXP(SUM(Log(abs(nullif(data,0))))) -- the base mathematics
     * round(0.5-count(nullif(sign(sign(data)+0.5),1))%2,0) -- pairs up negatives
       END
from MUL

Ingredients:

  • taking the abs() of data, if the min is 0, multiplying by whatever else is futile, the result is 0
  • When data is 0, NULLIF converts it to null. The abs(), log() both return null, causing it to be precluded from sum()
  • If data is not 0, abs allows us to multiple a negative number using the LOG method - we will keep track of the negativity elsewhere
  • Working out the final sign
    • sign(data) returns 1 for >0, 0 for 0 and -1 for <0.
    • We add another 0.5 and take the sign() again, so we have now classified 0 and 1 both as 1, and only -1 as -1.
    • again use NULLIF to remove from COUNT() the 1's, since we only need to count up the negatives.
    • % 2 against the count() of negative numbers returns either
    • --> 1 if there is an odd number of negative numbers
    • --> 0 if there is an even number of negative numbers
    • more mathematical tricks: we take 1 or 0 off 0.5, so that the above becomes
    • --> (0.5-1=-0.5=>round to -1) if there is an odd number of negative numbers
    • --> (0.5-0= 0.5=>round to 1) if there is an even number of negative numbers
    • we multiple this final 1/-1 against the SUM-PRODUCT value for the real result
Will Ediger
  • 893
  • 9
  • 17
RichardTheKiwi
  • 105,798
  • 26
  • 196
  • 262
  • See this for different ways of zero and negative handling: http://stackoverflow.com/questions/3653586 – gbn May 20 '11 at 04:56
  • To avoid ANSI warnings about NULL values being eliminated from the aggregate or SET operator, you can replace `count(nullif(sign(sign(data)+0.5),1))` by `COUNT(CASE WHEN SIGN(SIGN(data)+0.5)<>1 THEN 1 END)`. You can also `SET ANSI_WARNINGS OFF` before executing the query, but I prefer writing the query so that it not produce warnings in the first place. – TT. Feb 23 '14 at 18:22
  • 1
    Interestingly, the NULLIF(data,0) in `EXP(SUM(Log(abs(nullif(data,0)))))` is necessary for it to work, even though the CASE statement should return 0 without evaluating the ELSE part. If zero values are present in the dataset, omitting the NULLIF(data,0) results in a domain error. – TT. Feb 23 '14 at 20:23
  • 2
    There is plenty of "scope for use" in any field, such as finance, where rates of growth compound over time. You may have ten years of daily returns on a stock, that would be about 2500 factors (weekends are not counted), all similar to 1.00043 and 0.99863, which multiplied together give the cumulative ten-year growth factor. Lack of MULTIPLY in the standard is a significant shortcoming. –  Jul 03 '17 at 22:15
  • Agree with @mathguy, we needed it in our sql engine for financial and also and especially statistical calculations (we use Decimal internally for sufficient precision). Of course, the exp/^ trick does it too, but some SQL developers lack the mathemtical background to find this type of workaround. Product would be a nice enhancement of the standard. – Guido Leenders Jul 01 '18 at 08:39
29

No, but you can use Mathematics :)

if yourColumn is always bigger than zero:

select EXP(SUM(LOG(yourColumn))) As ColumnProduct from yourTable
Konerak
  • 39,272
  • 12
  • 98
  • 118
  • Konerak EXP ***is*** `e`. http://msdn.microsoft.com/en-us/library/ms179857.aspx. It is the EXP-onent of the natural log, which pairs with LOG – RichardTheKiwi Mar 24 '11 at 08:06
  • -1 "ORA-00909: invalid number of arguments". EDIT: Oops, I see it is not an Oracle-only question ... I've undone my -1. – Rob van Wijk Mar 24 '11 at 08:12
  • 1
    This is super clever! Helped me solve a similar problem in AWS Athena – there's a gotcha with Presto wherein you need to use ln() instead of the LOG() listed here – yungblud Apr 10 '18 at 22:11
  • 1
    @Konerak, maybe it´s a stupid question but could you explain the math behind the trick? Your solutions works perfectly for me but I don´t understand how. – Darem Nov 14 '19 at 12:35
  • 1
    Sure, Darem. "LOG(x)" means "to which power should I raise e to get x?". Now, if you then EXP that solution, offcourse you get x again. You asked the number to EXP, and you EXP with that number. Combine that with the fact that x^a * x^b = x^(a+b) - which is easy to understand as well: x^a means "multiple x by itself, a times". Now multiply that by x, b times. Offcourse it's the same as x a times and again b times. So now, if you SUM the numbers to which to power, when you EXP again, you get them times. – Konerak Nov 15 '19 at 18:08
7

I see an Oracle answer is still missing, so here it is:

SQL> with yourTable as
  2  ( select 1 yourColumn from dual union all
  3    select 2 from dual union all
  4    select 4 from dual union all
  5    select 8 from dual
  6  )
  7  select EXP(SUM(LN(yourColumn))) As ColumnProduct from yourTable
  8  /

COLUMNPRODUCT
-------------
           64

1 row selected.

Regards,
Rob.

Rob van Wijk
  • 17,555
  • 5
  • 39
  • 55
1

With PostgreSQL, you can create your own aggregate functions, see http://www.postgresql.org/docs/8.2/interactive/sql-createaggregate.html

To create an aggregate function on MySQL, you'll need to build an .so (linux) or .dll (windows) file. An example is shown here: http://www.codeproject.com/KB/database/mygroupconcat.aspx

I'm not sure about mssql and oracle, but i bet they have options to create custom aggregates as well.

Geeklab
  • 199
  • 2
1

You'll break any datatype fairly quickly as numbers mount up.

Using LOG/EXP is tricky because of numbers <= 0 that will fail when using LOG. I wrote a solution in this question that deals with this

Community
  • 1
  • 1
gbn
  • 422,506
  • 82
  • 585
  • 676
0

Using CTE in MS SQL:

CREATE TABLE Foo(Id int, Val int)
INSERT INTO Foo VALUES(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)

;WITH cte AS 
(
    SELECT Id, Val AS Multiply, row_number() over (order by Id) as rn
    FROM Foo
    WHERE Id=1
    UNION ALL
    SELECT ff.Id, cte.multiply*ff.Val as multiply, ff.rn FROM
    (SELECT f.Id, f.Val, (row_number() over (order by f.Id)) as rn
    FROM Foo f) ff
        INNER JOIN cte
        ON ff.rn -1= cte.rn
)
SELECT * FROM cte
LINQ2Vodka
  • 2,996
  • 2
  • 27
  • 47
-3

Not sure about Oracle or sql-server, but in MySQL you can just use * like you normally would.

mysql> select count(id), count(id)*10 from tablename;
+-----------+--------------+
| count(id) | count(id)*10 |
+-----------+--------------+
|       961 |         9610 |
+-----------+--------------+
1 row in set (0.00 sec)
Rob Williams
  • 1,442
  • 12
  • 13