Пользовательский агрегат SQLCLR с несколькими параметрами
У меня проблемы с пониманием того, как работают пользовательские агрегаты CLR.
Я должен создать несколько пользовательских агрегатов CLR с несколькими параметрами. Дело в том, чтобы получить значение первого параметра в зависимости от второго.
Например, у меня есть следующие значения в моей таблице, и мне нужен самый старый сотрудник 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, но мне сложно найти конкретный пример такого рода реализации.
На данный момент у меня есть это. Я создаю класс для сбора данных, но как я могу сортировать (или делать что-то другое) для них?
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; }
}
}
Есть идеи?
2 ответа
Нет необходимости хранить список всех записей - вам нужно хранить только детали самой старой записи, которую вы когда-либо видели.
Примерно так должно работать:
[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 Server использует этот метод для объединения информации из разных потоков. В зависимости от типа алгоритма, который вы используете, здесь вы можете: объединить текущую и входящую информацию, принять решение сохранить текущую информацию или входящую информацию (как это делается в реализации @Richard), пересчитать текущую информацию на основе новой входящая информацияTerminate
метод вызывается в конце каждой конкретной группы. Здесь вы должны сделать окончательный расчет / логику, а затем вернуть ожидаемый результат.
Эту информацию и многое другое можно найти на странице MSDN Требования к пользовательским агрегатам CLR.