2013-07-10 1 views
0

Я добавил агрегированную пользователем информацию в свою базу данных для вычисления продукта группы.SQL CLR Агрегат неверно возвращает нуль

Код был выполнен главным образом от here.

Я использую функцию для расчета срока службы финансовых инструментов, для которых у меня есть ежемесячные данные о возврате. Таблица выглядит примерно так:

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

Мой запрос выглядит следующим образом:

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 

запрос отлично работает, когда у меня есть только несколько инструментов, но с добавлением некоторых инструментов вызывает каждый результат будет NULL. Когда я выполняю запрос с помощью OPTION (MAXDOP 1), результаты будут точными.

Кто-нибудь знает, что вызывает проблему?

EDIT: Забыл упомянуть, что я бегу SQL Server 2012 и агрегатные цели .NET 4.5

+0

«по существу дословно» может скрыть множество грехов - можете ли вы подробно остановиться на местах, где есть различия? –

+0

@Damien_The_Unbeliever Я использовал EXP (SUM (LOG (...))) для вычисления продукта, и он игнорирует нули, поэтому я изменил продукт и проигнорировал нули. Я также пробовал код без каких-либо изменений, и он по-прежнему вызывает те же ошибки. –

+0

Как вы изменили его, чтобы игнорировать нули? Вы только что изменили 'IsInvariantToNulls' или внесли изменения внутри' Accumulate'? –

ответ

1

Эти это изменения, которые я внес бы в агрегат продукта, если бы я хотел, чтобы он игнорировал NULL.

Изменить атрибут:

[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" 
)] 

Изменение 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; 
} 

Изменение 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; 
    } 
    } 
} 

Я не уверен, если изменение Merge действительно необходимо, но Я бы сделал это ради безопасности.

+0

Работал как очарование, спасибо. Я предполагаю, что что-то не так с функцией Merge на странице, с которой я связан, поскольку это возвращало nulls при запуске групп без нулей. –

+0

@willwpan - теория, с которой я пришел, заключалась в том, что она могла бы параллелизировать план и создать один экземпляр для каждого процессора. Но это: а) Никогда не знает, будет ли когда-либо из тех экземпляров когда-либо передаваться значение, отличное от NULL, и б) не знает, какие из них объединяются - так что возможно, что экземпляр создан, он никогда не получает вызовы 'Accumulate ', а затем он вызывает« Merge ». –

0

Если 1 + R2.MonthlyReturn положительна, я бы рассмотрел использование exp(sum(log(...))) эквивалент:

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 
+0

Я использовал EXP (СУММ (LOG (...))), но я надеялся добавить в будущем дополнительные пользовательские агрегаты. Учитывая, что даже этот простой вызывает ошибки (и что они еще не могут использоваться в функциях окна), я не решаюсь продолжать. –

+0

Опираясь на встроенные возможности, пока совершенно невозможно жить с CLR-функциями, возможно, это не плохая стратегия. –