User Defined Aggregate Function in SQL for String Concatenation using CLR
If you tried to concatenate strings from group in SQL, probably you would start looking for an aggregate function similar to SUM or COUNT. There is no function like this in SQL Server 2005 and even further there is no way how to create a custom aggregate function using SQL. You can use just built-in aggregates.
Fortunately, SQL Server 2005 integrates common language runtime (CLR). So, the solution is to write an aggregate function in C# and include it among built-in functions in SQL Server.
Let us show an example with aggregate function for strings concatenation. At first you should create a new project in VS - use template Class Library, name it for example MyCLRLib.
The key methods are:
Init()- initializes private variablesAccumulate()- appends the next valueMerge()- merges partial aggregatesTerminates()- returns a result aggregation
using System;
using System.Text;
using System.IO;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
[Serializable]
[SqlUserDefinedAggregate(
Format.UserDefined, //use custom 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
{
// Intermediate result data of concatenation
private StringBuilder intermediateResult;
public void Init()
{
this.intermediateResult = new StringBuilder();
}
// If the next value is not null, append it to the end of string
public void Accumulate(SqlString value)
{
if (value.IsNull)
{
return;
}
this.intermediateResult.Append(value.Value).Append(", ");
}
//Merges the partial aggregate with this aggregate
public void Merge(Concatenate part)
{
this.intermediateResult.Append(part.intermediateResult);
}
//Returns the result of the aggregation when finished
public SqlString Terminate()
{
string result = string.Empty;
if (this.intermediateResult != null
&& this.intermediateResult.Length > 0)
{
result = this.intermediateResult.ToString(0, this.intermediateResult.Length - 1);
}
return new SqlString(result);
}
public void Read(BinaryReader reader)
{
intermediateResult = new StringBuilder(reader.ReadString());
}
public void Write(BinaryWriter writer)
{
writer.Write(this.intermediateResult.ToString());
}
}
Now you can register this function in SQL Server. First of all, enable .NET CLR code execution in your SQL Server:
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE WITH OVERRIDE;
GO
and then create new aggregate function from your compiled assembly:
CREATE ASSEMBLY MyCLRLib FROM 'D:\MyCLRLib.dll'
GO
CREATE AGGREGATE Concatenate (@input nvarchar(1000)) RETURNS nvarchar(max)
EXTERNAL NAME MyCLRLib.Concatenate
GO
Now you can write:
SELECT Surname, dbo.Concatenate(FirstName)
FROM Families
GROUP BY Surname
5 komentářů:
Great article, thanks!
Just one thing..
in
CREATE AGGREGATE Concatenate (@input nvarchar(1000)) RETURNS nvarchar(max)
EXTERNAL NAME MyAgg.Concatenate
GO
shouldn't there be
EXTERNAL NAME MyCLRLib.Concatenate
instead of
EXTERNAL NAME MyAgg.Concatenate ?
Thanks for your suggestion, Martin :)
You are right, MyCLRLib.Concatenate is thy only correct possibility.
My original class Concatenate was in MyAgg namespace, so the external name was MyCLRLib.MyAgg.Concatenate.
Now it should be correct.
Fine work. Appreciate the time you took to put this explanation together. I'll likely use this template in a variety of future applications.
BTW, everything worked the first time through for me. I was hung up on the namespace at first. SQL Server kept erroring that it couldn't find the class, but when I nixed the namespace in the C# code, voila! It worked. As stated above in the comments, I guess you must put the namespace.class in the SQL create query, if you use a namespace in the C# code (default upon creation in C# Express).
L8r,
Jage
There was a mistake
Post a Comment