【问题标题】:SQL Server 2008 CLR aggregate functionSQL Server 2008 CLR 聚合函数
【发布时间】: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。跨度>

标签: c# sql sql-server-2008 aggregate-functions sqlclr


【解决方案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 {get{return _amount;}}
  public DateTime Date {get{return _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以外的转换方法?
猜你喜欢
  • 1970-01-01
  • 2018-10-12
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 2011-01-08
  • 2013-08-14
  • 2013-05-25
相关资源
最近更新 更多