【问题标题】:SQL CLR Aggregate incorrectly returning nullSQL CLR 聚合错误地返回 null
【发布时间】:2013-07-08 12:12:56
【问题描述】:

我已将用户定义的聚合添加到我的数据库中,用于计算组的乘积。

代码基本上是从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) 执行查询时,结果很好。

有人知道是什么原因造成的吗?

编辑: 忘了提到我正在运行 SQL Server 2012 并且聚合目标是 .NET 4.5

【问题讨论】:

  • “基本上逐字逐句”可以隐藏许多罪恶——你能详细说明存在差异的地方吗?
  • @Damien_The_Unbeliever 我一直在使用 EXP(SUM(LOG(...))) 来计算产品,它忽略了空值,所以我也将产品更改为忽略空值。我也尝试了没有更改的代码,但仍然导致相同的错误。
  • 您如何将其更改为忽略空值?您是刚刚更改了IsInvariantToNulls 还是在Accumulate 内部进行了更改?

标签: sql-server aggregate-functions sqlclr


【解决方案1】:

如果我希望它忽略 NULLs,我将对 Product 聚合进行这些修改。

更改属性:

[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,但为了安全起见,我会这样做。

【讨论】:

  • 工作就像一个魅力,谢谢。我猜我链接到的页面中的 Merge 函数有问题,因为在没有空值的组上运行时返回空值。
  • @willwpan - 我想出的理论是它可以并行化计划并为每个处理器创建一个实例。但它 a) 永远不知道其中一些实例是否会被传递一个非 NULL 值,并且 b) 不知道要合并到哪些实例中 - 所以有可能创建了一个实例,永远不会收到对 @987654328 的任何调用@ 然后调用了Merge
【解决方案2】:

如果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

【讨论】:

  • 我以前一直在使用 EXP(SUM(LOG(...))) 但我希望将来能添加更多自定义聚合。考虑到即使是这个简单的错误也会导致错误(并且它们还不能在窗口函数中使用),我犹豫要不要继续。
  • 依赖内置功能,直到完全不可能在没有 CLR 功能的情况下生存,这也许是一个不错的策略。
猜你喜欢
  • 2013-05-25
  • 1970-01-01
  • 2018-10-26
  • 1970-01-01
  • 2019-07-20
  • 2014-01-27
  • 1970-01-01
  • 2021-11-18
  • 2010-12-30
相关资源
最近更新 更多