Question

I've added a user defined aggregate to my database for computing the product of a group.

The code was taken essentially verbatim from here.

I'm using the function to calculate the lifetime to date returns of financial instruments for which I have monthly return data. The table looks something like this:

----------------------------------------------------------
| InstrumentId(int) | MonthEnd(datetime) | Return(float) |
----------------------------------------------------------

My query looks like this:

SELECT R1.InstrumentId,
       R1.MonthEnd,
       R1.MonthlyReturn,
       dbo.Product(1 + R2.MonthlyReturn) AS TotalReturn
FROM Returns R1
INNER JOIN Returns R2 ON  R2.InstrumentId = R1.InstrumentId 
                      AND R2.MonthEnd <= R1.MonthEnd
WHERE R1.InstrumentId BETWEEN 1 AND 50
GROUP BY R1.InstrumentId, R1.MonthEnd, R1.MonthlyReturn
ORDER BY R1.InstrumentId, R1.MonthEnd

The query works fine when I have only a few instruments, but adding certain instruments causes every result to be NULL. When I execute the query with OPTION(MAXDOP 1) the results are fine.

Does anyone know what's causing the issue?

EDIT: Forgot to mention that I'm running SQL Server 2012 and the aggregate targets .NET 4.5

Was it helpful?

Solution

These are the modifications I would make to the Product aggregate if I wanted it to ignore NULLs.

Change the attribute:

[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(
   Microsoft.SqlServer.Server.Format.Native,
   IsInvariantToDuplicates = false,
   IsInvariantToNulls = true,      // receiving a NULL value will be ignored
   IsInvariantToOrder = true,
   IsNullIfEmpty = true,
   Name = "Product"
)]

Change Accumulate:

 public void Accumulate(System.Data.SqlTypes.SqlDouble number) {
  if (!this.HasValue && !number.IsNull) { //Don't know if we'll be passed a NULL, but protect ourselves nonetheless
     this.Result = number;
  } else if (number.IsNull) {
     return; //Avoid setting HasValue
  } else {
     this.Result = System.Data.SqlTypes.SqlDouble.Multiply(this.Result, number);
  }
  this.HasValue = true;
}

Change Merge:

public void Merge(Product group) {
  if (group.HasValue) {
    if(this.HasValue) {
     this.Result = System.Data.SqlTypes.SqlDouble.Multiply
            (this.Result, group.Result);
    } else { //We may never have had our own value set
     this.Result = group.Result;
     this.HasValue = true;
    }
  }
}

I'm not sure if the change to Merge is truly needed, but I'd do it for safety's sake.

OTHER TIPS

If 1 + R2.MonthlyReturn is positive, I would considered use of exp(sum(log(...))) equivalent:

SELECT R1.InstrumentId,
       R1.MonthEnd,
       R1.MonthlyReturn,
       EXP(SUM(LOG(1 + R2.MonthlyReturn))) AS TotalReturn
FROM Returns R1
INNER JOIN Returns R2 ON  R2.InstrumentId = R1.InstrumentId 
                      AND R2.MonthEnd <= R1.MonthEnd
WHERE R1.InstrumentId BETWEEN 1 AND 50
GROUP BY R1.InstrumentId, R1.MonthEnd, R1.MonthlyReturn
ORDER BY R1.InstrumentId, R1.MonthEnd
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top