Вызов определяемых пользователем статистических функций CLR
В инструкциях Transact-SQL SELECT можно вызывать пользовательские статистические функции CLR, на которые распространяются те же правила, что и на системные статистические функции.
Применяются следующие дополнительные правила:
Текущий пользователь должен иметь разрешение EXECUTE на пользовательскую статистическую функцию.
Пользовательские статистические функции можно вызывать с помощью двусоставного имени в следующей форме: schema_name.udagg_name.
Тип аргумента пользовательской статистической функции должен совпадать с input_type статистической функции, как определено в инструкции CREATE AGGREGATE, или неявно преобразовываться в него.
Тип данных, возвращаемых пользовательской статистической функцией, должен совпадать с return_type в инструкции CREATE AGGREGATE.
Ниже приводится пример определяемой пользователем статистической функции, объединяющей набор строковых значений из столбца таблицы.
[C#]
using System;using System.Data;using Microsoft.SqlServer.Server;using System.Data.SqlTypes;using System.IO;using System.Text;[Serializable][SqlUserDefinedAggregate( Format.UserDefined, //use clr serialization to serialize the intermediate result IsInvariantToNulls = true, //optimizer property IsInvariantToDuplicates = false, //optimizer property IsInvariantToOrder = false, //optimizer property MaxByteSize = 8000) //maximum size in bytes of persisted value]public class Concatenate : IBinarySerialize{ /// <summary> /// The variable that holds the intermediate result of the concatenation /// </summary> private StringBuilder intermediateResult; /// <summary> /// Initialize the internal data structures /// </summary> public void Init() { this.intermediateResult = new StringBuilder(); } /// <summary> /// Accumulate the next value, not if the value is null /// </summary> /// <param name="value"></param> public void Accumulate(SqlString value) { if (value.IsNull) { return; } this.intermediateResult.Append(value.Value).Append(','); } /// <summary> /// Merge the partially computed aggregate with this aggregate. /// </summary> /// <param name="other"></param> public void Merge(Concatenate other) { this.intermediateResult.Append(other.intermediateResult); } /// <summary> /// Called at the end of aggregation, to return the results of the aggregation. /// </summary> /// <returns></returns> public SqlString Terminate() { string output = string.Empty; //delete the trailing comma, if any if (this.intermediateResult != null && this.intermediateResult.Length > 0) { output = this.intermediateResult.ToString(0, this.intermediateResult.Length - 1); } return new SqlString(output); } public void Read(BinaryReader r) { intermediateResult = new StringBuilder(r.ReadString()); } public void Write(BinaryWriter w) { w.Write(this.intermediateResult.ToString()); }}
[Visual Basic]
Imports SystemImports System.DataImports Microsoft.SqlServer.ServerImports System.Data.SqlTypesImports System.IOImports System.Text<Serializable(), SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToNulls:=True, IsInvariantToDuplicates:=False, IsInvariantToOrder:=False, MaxByteSize:=8000)> _Public Class Concatenate Implements IBinarySerialize ''' <summary> ''' The variable that holds the intermediate result of the concatenation ''' </summary> Private intermediateResult As StringBuilder ''' <summary> ''' Initialize the internal data structures ''' </summary> Public Sub Init() Me.intermediateResult = New StringBuilder() End Sub ''' <summary> ''' Accumulate the next value, not if the value is null ''' </summary> ''' <param name="value"></param> Public Sub Accumulate(ByVal value As SqlString) If value.IsNull Then Return End If Me.intermediateResult.Append(value.Value).Append(","c) End Sub ''' <summary> ''' Merge the partially computed aggregate with this aggregate. ''' </summary> ''' <param name="other"></param> Public Sub Merge(ByVal other As Concatenate) Me.intermediateResult.Append(other.intermediateResult) End Sub ''' <summary> ''' Called at the end of aggregation, to return the results of the aggregation. ''' </summary> ''' <returns></returns> Public Function Terminate() As SqlString Dim output As String = String.Empty 'delete the trailing comma, if any If Not (Me.intermediateResult Is Nothing) AndAlso Me.intermediateResult.Length > 0 Then output = Me.intermediateResult.ToString(0, Me.intermediateResult.Length - 1) End If Return New SqlString(output) End Function Public Sub Read(ByVal r As BinaryReader) Implements IBinarySerialize.Read intermediateResult = New StringBuilder(r.ReadString()) End Sub Public Sub Write(ByVal w As BinaryWriter) Implements IBinarySerialize.Write w.Write(Me.intermediateResult.ToString()) End SubEnd Class
После компиляции кода в библиотеку MyAgg.dll можно зарегистрировать статистическую функцию в SQL Server показанным далее образом.
CREATE ASSEMBLY MyAgg FROM 'C:\MyAgg.dll'GOCREATE AGGREGATE MyAgg (@input nvarchar(200)) RETURNS nvarchar(max)EXTERNAL NAME MyAgg.Concatenate
Примечание |
---|
Начиная с версии SQL Server 2005 в базе данных SQL Server с уровнем совместимости 80 нельзя создавать управляемые определяемые пользователем типы, хранимые процедуры, функции, статистические функции и триггеры. Чтобы сделать эти функции интеграции со средой CLR доступными в SQL Server, необходимо при помощи хранимой процедуры sp_dbcmptlevel (Transact-SQL) задать для базы данных уровень совместимости 90. |
Примечание |
---|
Управляемые объекты базы данных C++, такие как функции, возвращающие скалярные значения, скомпилированные с помощью параметра компиляции /clr:pure Visual C++, не поддерживаются и не выполняются в SQL Server 2005 RTM. |
Как и для большинства статистических функций, основная логика содержится в методе Accumulate. Здесь строка, передаваемая в параметре методу Accumulate, присоединяется к объекту StringBuilder, инициализированному в методе Init. Если метод Accumulate вызывается не в первый раз, к объекту StringBuilder также присоединяется запятая перед передаваемой строкой. По завершении вычислительных задач вызывается метод Terminate, возвращающий объект StringBuilder в виде строки.
Предположим, что таблица имеет следующую структуру.
CREATE TABLE BookAuthors( BookID int NOT NULL, AuthorName nvarchar(200) NOT NULL)
Затем вставляются следующие строки.
INSERT BookAuthors VALUES(1, 'Johnson')INSERT BookAuthors VALUES(2, 'Taylor')INSERT BookAuthors VALUES(3, 'Steven')INSERT BookAuthors VALUES(2, 'Mayler')INSERT BookAuthors VALUES(3, 'Roberts')INSERT BookAuthors VALUES(3, 'Michaels')
В результате следующего запроса будет получен следующий результат.
SELECT BookID, dbo.MyAgg(AuthorName)FROM BookAuthorsGROUP BY BookID
Идентификатор книги |
Имена авторов |
---|---|
1 |
Джонсон |
2 |
Тэйлор, Майлер |
3 |
Робертс, Майклс, Стивен |
См. также