【问题标题】:SQLCLR custom aggregate with multiple parameters具有多个参数的 SQLCLR 自定义聚合
【发布时间】:2015-09-25 06:41:32
【问题描述】:

我无法理解 CLR 用户定义聚合的工作原理。

我必须创建一些具有多个参数的自定义 CLR 聚合。 重点是根据第二个参数获取第一个参数的值。

例如,我的表中有以下值,对于每个 Type,我需要最年长的员工 Name

    Type   |   Name   |   Age   
--------------------------------
Manager    | emp 1    |   35    
Manager    | emp 2    |   42    
Developer  | emp 3    |   36    
Developer  | emp 4    |   45    
Developer  | emp 5    |   22    

所以我想写一个这样的查询来使用我的程序集得到结果:

Select      Type, dbo.fOldestEmployee(Name, Age) AS [Name]
From        xxx
Group By    Type

这会响应:

    Type   |   Name   
----------------------
Manager    | emp 2     
Developer  | emp 4    

看起来可以使用 CLR 用户定义的聚合,但我很难找到这种实现的具体示例。

目前我有这个。 我创建了一个类来收集数据,但是如何对它们进行排序(或做其他事情)?

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text;
using System.Collections;
using System.IO;

[Serializable]
[SqlUserDefinedAggregate(
    Format.UserDefined,
    IsInvariantToOrder = false, // order changes the result
    IsInvariantToNulls = false, // nulls change the result
    IsInvariantToDuplicates = false, // duplicates change the result
    MaxByteSize = -1)]
public struct sOlder
{
    private List<MyData> _datas;

    public void Init()
    {
        _datas = new List<MyData>();
    }

    public void Accumulate(SqlString valueField, SqlInt32 ValueInt)
    {
        if (!valueField.IsNull && !ValueInt.IsNull)
        {
            _datas.Add(new MyData
            {
                ValField = valueField.Value,
                ValInt = ValueInt.Value
            });
        }
    }

    public void Merge (sOlder Group)
    {
        _datas.AddRange(Group._datas);
    }

    public SqlString Terminate ()
    {
        //...
    }

    public class MyData
    {
        public String ValField { get; set; }
        public Int32 ValInt { get; set; }
    }
}

有什么想法吗?

【问题讨论】:

  • 这与聚合无关,您只需要编写一个语句,返回每种类型的最老员工。有很多方法可以做到这一点,并且所有方法都会比您尝试做的更快。检查例如ROW_NUMBER。如果您按类型分区并按年龄降序排列,则您正在寻找具有 ROW_NUMBER 1 的条目
  • 感谢您的回复。我们其实是通过语句或者sql函数来做这种处理的。但这只是了解其工作原理的一个简单示例。
  • 谁在大容量上做这个处理之王。我们通过使用例如Concatenate Aggregate 提高了很多性能
  • Select Type, MAX(Age) From xxx Group By Type

标签: c# sql-server .net-assembly sqlclr user-defined-aggregate


【解决方案1】:

无需存储所有记录的列表 - 您只需要存储迄今为止看到的最旧记录的详细信息。

这样的事情应该可以工作:

[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);
        }
    }
}

【讨论】:

  • 即使您为MyData 创建了一个结构而不是一个类,您是否还需要指定Format.UserDefined 并实现ReadWrite 方法?我没试过,但我在Format Enumeration 中读到“Native”表明这是由于使用了string
  • @srutzky:根据问题,您仍然需要使用 SqlUserDefinedAggregateSerializable 属性。您还需要实现the IBinarySerialize interface
  • 好的,感谢您的澄清。是的,我假设需要这些属性,但只是想知道这里是否意味着能够摆脱 not 实现 IBinarySerialize
  • @srutzky:不,我同意您对文档的解释。我已经用基本实现更新了答案。
  • 谢谢大家,工作就像一个魅力!谢谢你的解释
【解决方案2】:

如果您正在寻找特定请求的实现,那么@Richard 的答案看起来是正确的(尽管,您可能仍然需要实现 ReadWrite 使用方法自定义类型 -- Format.UserDefined)。

但是,从 cmets 看来,这更像是一个一般性问题,即何时处理您收集的任何信息。在这种情况下:

  • 为特定 GROUP 中的每一行调用 Accumulate 方法。这是入口点。

  • 在使用并行性时调用Merge 方法。 SQL Server 使用这种方法来组合来自不同线程的信息。根据您正在执行的算法类型,您可以在此处:结合当前信息和传入信息,决定保留当前信息或传入信息(就像在@Richard 的实现中所做的那样),根据新信息重新计算当前信息传入信息。

  • Terminate 方法在每个特定 GROUP 的末尾调用。在这里您可以进行最终计算/逻辑,然后返回预期结果。

可以在Requirements for CLR User-Defined Aggregates 的 MSDN 页面上找到此信息以及更多信息。

【讨论】:

    猜你喜欢
    • 2015-11-22
    • 1970-01-01
    • 1970-01-01
    • 2019-06-08
    • 2016-07-24
    • 1970-01-01
    • 1970-01-01
    • 2020-02-02
    • 2015-11-10
    相关资源
    最近更新 更多