SQLCLR custom aggregate with multiple parameters

十年热恋 提交于 2019-12-02 08:31:16

There's no need to store a list of all the records - you only need to store the details of the oldest record you've seen so far.

Something like this should work:

[Serializable]
[SqlUserDefinedAggregate(
    Format.UserDefined,
    IsInvariantToOrder = true,
    IsInvariantToNulls = true,
    IsInvariantToDuplicates = true,
    MaxByteSize = -1)]
public struct sOlder : IBinarySerialize
{
    private struct MyData
    {
        public string Name { get; set; }
        public int? Age { get; set; }

        public int CompareTo(MyData other)
        {
            if (Age == null) return other.Age == null ? 0 : -1;
            if (other.Age == null) return 1;
            return Age.Value.CompareTo(other.Age.Value);
        }

        public static bool operator <(MyData left, MyData right)
        {
            return left.CompareTo(right) == -1;
        }

        public static bool operator >(MyData left, MyData right)
        {
            return left.CompareTo(right) == 1;
        }
    }

    private MyData _eldestPerson;

    public void Init()
    {
        _eldestPerson = default(MyData);
    }

    public void Accumulate(SqlString name, SqlInt32 age)
    {
        if (!name.IsNull && !age.IsNull)
        {
            var currentPerson = new MyData
            {
                Name = name.Value,
                Age = age.Value
            };

            if (currentPerson > _eldestPerson)
            {
                _eldestPerson = currentPerson;
            }
        }
    }

    public void Merge (sOlder other)
    {
        if (other._eldestPerson > _eldestPerson)
        {
            _eldestPerson = other._eldestPerson;
        }
    }

    public SqlString Terminate()
    {
        return _eldestPerson.Name;
    }

    public void Write(BinaryWriter writer)
    {
        if (_eldestPerson.Age.HasValue)
        {
            writer.Write(true);
            writer.Write(_eldestPerson.Age.Value);
            writer.Write(_eldestPerson.Name);
        }
        else
        {
            writer.Write(false);
        }
    }

    public void Read(BinaryReader reader)
    {
        if (reader.ReadBoolean())
        {
            _eldestPerson.Age = reader.ReadInt32();
            _eldestPerson.Name = reader.ReadString();
        }
        else
        {
            _eldestPerson = default(MyData);
        }
    }
}

If you are looking for an implementation of your specific request, then @Richard's answer looks to be correct (though, you might still need to implement the Read and Write methods for using a custom type -- Format.UserDefined).

However, it seems from the comments on the question that this is more of a general question of when to do processing of whatever information you are collecting. In that case:

  • The Accumulate method is called for every row in a particular GROUP. This is the entry point.

  • The Merge method is called when parallelism is being used. SQL Server uses this method to combine the information from various threads. Depending on the type of algorithm you are doing, here you might: combine the current and incoming information, decide to keep the current info or the incoming info (as is being done in @Richard's implementation), recalculate the current info based on the new incoming info.

  • The Terminate method is called at the end of each particular GROUP. Here is where you would do the final calculation / logic and then return the expected result.

This information, and more, can be found on the MSDN page for Requirements for CLR User-Defined Aggregates.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!