HiveBrain v1.2.0
Get Started
← Back to all entries
snippetsqlModerate

How to multiply rows for a column that contains negative and zero values?

Submitted by: @import:stackexchange-dba··
0
Viewed 0 times
rowsvaluescolumnmultiplythatforcontainshowandzero

Problem

I'm trying to get the Product of all rows for a specific column in a grouped by query. Most examples I've found point me towards combining exp, sum and log

exp(sum(log([Column A])))


The problem I'm having is that the column contains some zeros for values and thus I'm getting this error when zeros get passed to the log function:


An invalid floating point operation occurred.

I thought I could work around this by using a case expression, but that just doesn't work the way I would think it should, as it seems to evaluate all cases...

select 
  Name,
  Product = case 
    when min([Value]) = 0 then 0 
    when min([Value]) <> 0 then exp(sum(log(I))) -- trying to get the product of all rows in this column
  end
 from ids
 group by Name


SqlFiddle

Given the following result set:

Id  Name  Value
_________________________________
1   a     1
2   a     2
3   b     0
4   b     1


I would expect to get the following rows:

Name  Product
_____________
a     2
b     0


So in summary...
How do you multiply rows in a column that can contain negative or zero valued numbers?

Solution

The magic of NULLIF seems to do the trick for the test case in your question. Since you used a different example than in your SQL Fiddle, I don't know if that's what you want there too.

CREATE TABLE dbo.Ids
(
    Id INT NOT NULL IDENTITY(1, 1),
    Value INT,
    Name NVARCHAR(3)
);
INSERT INTO dbo.Ids ( Name, Value )
VALUES ( 'a', 1 );
INSERT INTO dbo.Ids ( Name, Value )
VALUES ( 'a', 2 );
INSERT INTO dbo.Ids ( Name, Value )
VALUES ( 'b', 0 );
INSERT INTO dbo.Ids ( Name, Value )
VALUES ( 'b', 1 );

SELECT   Name,
         CASE WHEN MIN(Value) = 0 THEN 0
              WHEN MIN(Value) > 0 THEN EXP(SUM(LOG(NULLIF(Value, 0)))) -- trying to get the product of all rows in this column
         END AS Product
FROM     Ids
GROUP BY Name;


Returns:

Name    Product
a       2
b       0


If you need a more general solution that handles negative numbers and other edge cases, see for example The Product Aggregate in T-SQL Versus the CLR by Scott Burkow. One T-SQL construction from that article is:

EXP(SUM(LOG(NULLIF(ABS([Value]), 0))))
*
IIF(SUM(IIF([Value] = 0, 1, NULL)) > 0, 0, 1)
*
IIF(SUM(IIF([Value] < 0, 1, 0)) % 2 = 1, -1, 1)


As to why your original CASE expression did not work as expected, from the documentation for CASE (Transact-SQL) (emphasis added):


You should only depend on order of evaluation of the WHEN conditions for scalar expressions (including non-correlated sub-queries that return scalars), not for aggregate expressions.

Code Snippets

CREATE TABLE dbo.Ids
(
    Id INT NOT NULL IDENTITY(1, 1),
    Value INT,
    Name NVARCHAR(3)
);
INSERT INTO dbo.Ids ( Name, Value )
VALUES ( 'a', 1 );
INSERT INTO dbo.Ids ( Name, Value )
VALUES ( 'a', 2 );
INSERT INTO dbo.Ids ( Name, Value )
VALUES ( 'b', 0 );
INSERT INTO dbo.Ids ( Name, Value )
VALUES ( 'b', 1 );

SELECT   Name,
         CASE WHEN MIN(Value) = 0 THEN 0
              WHEN MIN(Value) > 0 THEN EXP(SUM(LOG(NULLIF(Value, 0)))) -- trying to get the product of all rows in this column
         END AS Product
FROM     Ids
GROUP BY Name;
Name    Product
a       2
b       0
EXP(SUM(LOG(NULLIF(ABS([Value]), 0))))
*
IIF(SUM(IIF([Value] = 0, 1, NULL)) > 0, 0, 1)
*
IIF(SUM(IIF([Value] < 0, 1, 0)) % 2 = 1, -1, 1)

Context

StackExchange Database Administrators Q#193551, answer score: 13

Revisions (0)

No revisions yet.