--- title: "Invoking CLR User-Defined Aggregate Functions | Microsoft Docs" ms.custom: "" ms.date: "01/15/2019" ms.prod: sql ms.reviewer: "" ms.technology: clr ms.topic: "reference" dev_langs: - "TSQL" - "VB" - "CSharp" helpviewer_keywords: - "aggregate functions [CLR integration]" - "invoking user-defined aggregate functions" - "user-defined functions [CLR integration]" ms.assetid: 5a188b50-7170-4069-acad-5de5c915f65d author: "rothja" ms.author: "jroth" --- # CLR User-Defined Aggregate - Invoking Functions [!INCLUDE[appliesto-ss-xxxx-xxxx-xxx-md](../../includes/appliesto-ss-xxxx-xxxx-xxx-md.md)] In [!INCLUDE[tsql](../../includes/tsql-md.md)] SELECT statements, you can invoke common language runtime (CLR) user-defined aggregates, subject to all the rules that apply to system aggregate functions. The following additional rules apply: - The current user must have **EXECUTE** permission on the user-defined aggregate. - User-defined aggregates must be invoked using a two-part name in the form of *schema_name.udagg_name*. - The argument type of the user-defined aggregate must match or be implicitly convertible to the *input_type* of the aggregate, as defined in the **CREATE AGGREGATE** statement. - The return type of the user-defined aggregate must match the *return_type* in the **CREATE AGGREGATE** statement. ## Example 1 The following is an example of a user-defined aggregate function that concatenates a set of string values taken from a column in a table: [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 { /// /// The variable that holds the intermediate result of the concatenation /// public StringBuilder intermediateResult; /// /// Initialize the internal data structures /// public void Init() { this.intermediateResult = new StringBuilder(); } /// /// Accumulate the next value, not if the value is null /// /// public void Accumulate(SqlString value) { if (value.IsNull) { return; } this.intermediateResult.Append(value.Value).Append(','); } /// /// Merge the partially computed aggregate with this aggregate. /// /// public void Merge(Concatenate other) { this.intermediateResult.Append(other.intermediateResult); } /// /// Called at the end of aggregation, to return the results of the aggregation. /// /// 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 System Imports System.Data Imports Microsoft.SqlServer.Server Imports System.Data.SqlTypes Imports System.IO Imports System.Text _ Public Class Concatenate Implements IBinarySerialize ''' ''' The variable that holds the intermediate result of the concatenation ''' Public intermediateResult As StringBuilder ''' ''' Initialize the internal data structures ''' Public Sub Init() Me.intermediateResult = New StringBuilder() End Sub ''' ''' Accumulate the next value, not if the value is null ''' ''' Public Sub Accumulate(ByVal value As SqlString) If value.IsNull Then Return End If Me.intermediateResult.Append(value.Value).Append(","c) End Sub ''' ''' Merge the partially computed aggregate with this aggregate. ''' ''' Public Sub Merge(ByVal other As Concatenate) Me.intermediateResult.Append(other.intermediateResult) End Sub ''' ''' Called at the end of aggregation, to return the results of the aggregation. ''' ''' 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 Sub End Class ``` Once you compile the code into **MyAgg.dll**, you can register the aggregate in [!INCLUDE[ssNoVersion](../../includes/ssnoversion-md.md)] as follows: ``` CREATE ASSEMBLY MyAgg FROM 'C:\MyAgg.dll'; GO CREATE AGGREGATE MyAgg (@input nvarchar(200)) RETURNS nvarchar(max) EXTERNAL NAME MyAgg.Concatenate; ``` > [!NOTE] > Visual C++ database objects, such as scalar-valued functions, that have been compiled with the /clr:pure compiler option are not supported for execution in [!INCLUDE[ssNoVersion](../../includes/ssnoversion-md.md)]. As with most aggregates, the bulk of the logic is in the **Accumulate** method. Here, the string that is passed in as a parameter to the **Accumulate** method is appended to the **StringBuilder** object that was initialized in the **Init** method. Assuming that this is not the first time the **Accumulate** method has been called, a comma is also appended to the **StringBuilder** prior to appending the passed-in string. At the conclusion of the computational tasks, the **Terminate** method is called, which returns the **StringBuilder** as a string. For example, consider a table with the following schema: ``` CREATE TABLE BookAuthors ( BookID int NOT NULL, AuthorName nvarchar(200) NOT NULL ); ``` Then insert the following rows: ``` INSERT BookAuthors VALUES(1, 'Johnson'),(2, 'Taylor'),(3, 'Steven'),(2, 'Mayler'),(3, 'Roberts'),(3, 'Michaels'); ``` The following query would then produce the following result: ``` SELECT BookID, dbo.MyAgg(AuthorName) FROM BookAuthors GROUP BY BookID; ``` |BookID|Author Names| |------------|------------------| |1|Johnson| |2|Taylor, Mayler| |3|Roberts, Michaels, Steven| ## Example 2 The following sample shows an aggregate that has two parameters on the **Accumulate** method. [C#] ``` using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; [Serializable] [SqlUserDefinedAggregate( Format.Native, IsInvariantToDuplicates = false, IsInvariantToNulls = true, IsInvariantToOrder = true, IsNullIfEmpty = true, Name = "WeightedAvg")] public struct WeightedAvg { /// /// The variable that holds the intermediate sum of all values multiplied by their weight /// private long sum; /// /// The variable that holds the intermediate sum of all weights /// private int count; /// /// Initialize the internal data structures /// public void Init() { sum = 0; count = 0; } /// /// Accumulate the next value, not if the value is null /// /// Next value to be aggregated /// The weight of the value passed to Value parameter public void Accumulate(SqlInt32 Value, SqlInt32 Weight) { if (!Value.IsNull && !Weight.IsNull) { sum += (long)Value * (long)Weight; count += (int)Weight; } } /// /// Merge the partially computed aggregate with this aggregate /// /// The other partial results to be merged public void Merge(WeightedAvg Group) { sum += Group.sum; count += Group.count; } /// /// Called at the end of aggregation, to return the results of the aggregation. /// /// The weighted average of all inputed values public SqlInt32 Terminate() { if (count > 0) { int value = (int)(sum / count); return new SqlInt32(value); } else { return SqlInt32.Null; } } } ``` [Visual Basic] ``` Imports System Imports System.Data Imports System.Data.SqlClient Imports System.Data.SqlTypes Imports Microsoft.SqlServer.Server Imports System.Runtime.InteropServices _ _ Public Class WeightedAvg ''' ''' The variable that holds the intermediate sum of all values multiplied by their weight ''' Private sum As Long ''' ''' The variable that holds the intermediate sum of all weights ''' Private count As Integer ''' ''' The variable that holds the intermediate sum of all weights ''' Public Sub Init() sum = 0 count = 0 End Sub ''' ''' Accumulate the next value, not if the value is null ''' ''' Next value to be aggregated ''' The weight of the value passed to Value parameter Public Sub Accumulate(ByVal Value As SqlInt32, ByVal Weight As SqlInt32) If Not Value.IsNull AndAlso Not Weight.IsNull Then sum += CType(Value, Long) * CType(Weight, Long) count += CType(Weight, Integer) End If End Sub ''' ''' Merge the partially computed aggregate with this aggregate. ''' ''' The other partial results to be merged Public Sub Merge(ByVal Group As WeightedAvg) sum = Group.sum count = Group.count End Sub ''' ''' Called at the end of aggregation, to return the results of the aggregation. ''' ''' The weighted average of all inputed values Public Function Terminate() As SqlInt32 If count > 0 Then '' int value = (int)(sum / count); '' return new SqlInt32(value); Dim value As Integer = CType(sum / count, Integer) Return New SqlInt32(value) Else Return SqlInt32.Null End If End Function End Class ``` After you compile the C# or Visual Basic source code, run the following [!INCLUDE[tsql](../../includes/tsql-md.md)]. This script assumes that the DLL is called WghtAvg.dll and is in the root directory of your C drive. A database called test is also assumed. ``` use test; go -- sp_configure 'clr enabled', 1; -- go --- RECONFIGURE WITH OVERRIDE; -- go IF EXISTS (SELECT name FROM systypes WHERE name = 'MyTableType') DROP TYPE MyTableType; go IF EXISTS (SELECT name FROM sysobjects WHERE name = 'WeightedAvg') DROP AGGREGATE WeightedAvg; go IF EXISTS (SELECT name FROM sys.assemblies WHERE name = 'MyClrCode') DROP ASSEMBLY MyClrCode; go CREATE ASSEMBLY MyClrCode FROM 'C:\WghtAvg.dll'; GO CREATE AGGREGATE WeightedAvg (@value int, @weight int) RETURNS int EXTERNAL NAME MyClrCode.WeightedAvg; go CREATE TYPE MyTableType AS table (ItemValue int, ItemWeight int); go DECLARE @myTable AS MyTableType; INSERT INTO @myTable VALUES(1, 4), (6, 1); SELECT dbo.WeightedAvg(ItemValue, ItemWeight) FROM @myTable; go ``` ## See Also [CLR User-Defined Aggregates](../../relational-databases/clr-integration-database-objects-user-defined-functions/clr-user-defined-aggregates.md)