SQL Server 2008 CLR 聚合函数

Posted

技术标签:

【中文标题】SQL Server 2008 CLR 聚合函数【英文标题】:SQL Server 2008 CLR aggregate function 【发布时间】:2014-01-27 13:29:06 【问题描述】:

我一直在研究使用 CLR 聚合对一系列数据执行一些复杂的财务计算,但是尽管阅读了很多关于该主题的文章并且进行了很多摆弄,但我还是无法弄清楚。

我的输入是一系列日期和值,我希望能够执行以下操作:

SELECT dbo.FinancialCalc(amount, date)
FROM (VALUES
            (-100000, '11/30/2011'),
            (-50000, '3/15/2012'),
            (-2500, '7/18/2012')         
            ) n(amount, date)

到目前为止,这是我的代码:

[SqlUserDefinedAggregate(Format.UserDefined, MaxByteSize = 8000, Name = "FinancialCalc", IsInvariantToDuplicates = false, IsInvariantToNulls = true, IsInvariantToOrder = true, IsNullIfEmpty = true)]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public class FinancialCalc : IBinarySerialize
    
    private List<Transaction> transactions;
    private List<DateTime> dates;
    private List<Double> values;

    public void Init()
            
        this.transactions = new List<Transaction>();
        this.dates = new List<DateTime>();
        this.values = new List<double>();
    

    public void Accumulate(SqlDouble amount, SqlDateTime date)
    
        this.dates.Add(date.Value);
        this.values.Add(amount.Value);
    

    public void Merge(FinancialCalc Group)
    
       //is this needed?
    

    public SqlDouble Terminate()
    
       //here is where I would do the calc: return transactions.Calculate() or somethine
        return values.Sum();
    

    public void Read(System.IO.BinaryReader r)
    
        int itemCount = r.ReadInt16();

        for (int i = 0; i <= itemCount - 1; i++)
        
            this.values.Add(r.ReadDouble());          
        
    

    public void Write(System.IO.BinaryWriter w)
    
        w.Write(this.values.Count);
        foreach (double s in this.values)
        
            w.Write(s);
        
    

如何将SQL查询中的数据成功拿到List&lt;Transaction&gt;中,以便处理并返回计算值?

【问题讨论】:

您是否需要日期和值的列表,或者这正是您目前所拥有的?您想要的只是Transaction 对象的(任意)列表吗? Transaction 对象是什么样的? 其他列表只是为了测试——交易就是我所需要的。交易看起来像public class Transaction public double value get;set; public DateTime date get;set; 所以,类似于我的回答中假设的一个。所有需要改变的(如果你不想改变你的Transaction 定义以匹配我的)是使用对象初始化语法而不是我假设的构造函数,在AccumulateRead。跨度> 【参考方案1】:

如果我假设 Transaction 看起来像这样:

public class Transaction

  private readonly double _amount;
  private readonly DateTime _date;
  public Transaction(double amount,DateTime date)
    _amount = amount;
    _date = date;
  
  public double Amount getreturn _amount;
  public DateTime Date getreturn _date;

然后我假设你真正想要的看起来像这样:

[SqlUserDefinedAggregate(Format.UserDefined,
        //Play it safe, we don't know how large we'll get
        MaxByteSize = -1,
        Name = "FinancialCalc", IsInvariantToDuplicates = false,
        IsInvariantToNulls = true, IsInvariantToOrder = true,
        IsNullIfEmpty = true)]
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public class FinancialCalc : IBinarySerialize
    
    private List<Transaction> transactions;

    public void Init()
            
        this.transactions = new List<Transaction>();
    

    public void Accumulate(SqlDouble amount, SqlDateTime date)
    
        this.transactions.Add(new Transaction(date.Value,amount.Value));
    

    public void Merge(FinancialCalc Group)
    
       //Yes, you do need this. Group contains another set of transactions
       //and is going to disappear after this method has been called
       this.transactions.AddRange(Group.transactions);
    

    public SqlDouble Terminate()
    
        //Do your calculation based on the content of transactions
        return new SqlDouble(transactions.Sum(t=>t.Amount));
    

    public void Read(System.IO.BinaryReader r)
    
        int itemCount = r.ReadInt16();

        for (int i = 0; i <= itemCount - 1; i++)
        
            this.transactions.Add(new Transaction(r.ReadDouble(),
                                       new DateTime(r.ReadInt64()));          
        
    

    public void Write(System.IO.BinaryWriter w)
    
        w.Write(this.transactions.Count);
        foreach (var t in this.transactions)
        
            w.Write(t.Amount);
            w.Write(t.Date.ToBinary());
        
    

【讨论】:

感谢@Damien,非常感谢。虽然在部署时出现错误 - `CREATE AGGREGATE 失败,因为类型 'FinancialCalc' 由于字段 'CS$9__CachedAnonymousMethodDelegate1' 不符合 UDAGG 规范。我认为这是因为终止方法中的 LINQ,它反正我也不需要。 @woggles - 是的,我只是手工编写的,我没有尝试编译它或将它添加到 SQL Server 实例中:-( 差不多了 :) 转换日期时的另一个问题我认为:System.ArgumentOutOfRangeException: Ticks must be between DateTime.MinValue.Ticks and DateTime.MaxValue.Ticks. Parameter name: ticks 运行 sql 时 @woggles,您是否尝试使用dateTimeValue = sqlDateTimeValue.Value以外的转换方法?

以上是关于SQL Server 2008 CLR 聚合函数的主要内容,如果未能解决你的问题,请参考以下文章

sqlserver2008r2clr应用签名不匹配

SQL Server 中系统视图sysobjects中type字段的说明

SQL Server2008窗口计算

sql server中啥是聚合函数

如何在 SQL Server 的 clr 存储过程中执行动态 .net 代码

SQL Server 自定义聚合函数