具有多个参数的 SQLCLR 自定义聚合
SQLCLR custom aggregate with multiple parameters
我无法理解 CLR 用户定义聚合的工作原理。
我必须创建一些具有多个参数的自定义 CLR 聚合。
重点是根据第二个参数获取第一个参数的值。
例如,我的 table 中有以下值,我需要最老的员工 Name
每个 Type
:
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 用户定义的聚合,但我很难找到这种实现的具体示例。
目前我有这个。
我创建了一个 class 来收集数据,但我该如何对它们进行排序(或做其他事情)?
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; }
}
}
有什么想法吗?
不需要存储所有记录的列表 - 您只需要存储到目前为止看到的最旧记录的详细信息。
像这样的东西应该可以工作:
[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);
}
}
}
如果您正在寻找特定请求的实现,那么@Richard 的答案看起来是正确的(不过,您可能 仍然需要实现 Read
和 Write
使用自定义类型的方法 -- Format.UserDefined
).
但是,从对该问题的评论来看,这似乎更像是一个普遍的问题,即何时对您收集的任何信息进行处理。在那种情况下:
Accumulate
方法被调用对于特定组中的每一行。这是入口点。
Merge
方法在使用并行性时被调用。 SQL 服务器使用此方法来组合来自各个线程的信息。根据您正在执行的算法类型,您可以在此处:组合当前信息和传入信息,决定保留当前信息或传入信息(正如在@Richard 的实现中所做的那样),根据新信息重新计算当前信息传入信息。
Terminate
方法在每个特定 GROUP 的末尾被调用 。在这里您可以进行最终计算/逻辑,然后 return 预期结果。
的 MSDN 页面上找到此信息以及更多信息
我无法理解 CLR 用户定义聚合的工作原理。
我必须创建一些具有多个参数的自定义 CLR 聚合。 重点是根据第二个参数获取第一个参数的值。
例如,我的 table 中有以下值,我需要最老的员工 Name
每个 Type
:
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 用户定义的聚合,但我很难找到这种实现的具体示例。
目前我有这个。 我创建了一个 class 来收集数据,但我该如何对它们进行排序(或做其他事情)?
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; }
}
}
有什么想法吗?
不需要存储所有记录的列表 - 您只需要存储到目前为止看到的最旧记录的详细信息。
像这样的东西应该可以工作:
[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);
}
}
}
如果您正在寻找特定请求的实现,那么@Richard 的答案看起来是正确的(不过,您可能 仍然需要实现 Read
和 Write
使用自定义类型的方法 -- Format.UserDefined
).
但是,从对该问题的评论来看,这似乎更像是一个普遍的问题,即何时对您收集的任何信息进行处理。在那种情况下:
Accumulate
方法被调用对于特定组中的每一行。这是入口点。Merge
方法在使用并行性时被调用。 SQL 服务器使用此方法来组合来自各个线程的信息。根据您正在执行的算法类型,您可以在此处:组合当前信息和传入信息,决定保留当前信息或传入信息(正如在@Richard 的实现中所做的那样),根据新信息重新计算当前信息传入信息。Terminate
方法在每个特定 GROUP 的末尾被调用 。在这里您可以进行最终计算/逻辑,然后 return 预期结果。