posts - 78, comments - 79, trackbacks - 84

My Links

News

View Anthony Trudeau's profile on LinkedIn

Add to Technorati Favorites

Article Categories

Archives

Post Categories

Other Links

User Defined Aggregates (UDA) in SQL Server 2005

The first thing to do is create the class that will serve as the source for the aggregation.  The class that we add will implement four key methods:  Init, Accumulate, Merge, and Terminate.  It will be decorated with the Serializable and SqlUserDefinedAggregate attributes.  The latter is the key to allowing the class to be a UDA.

The next step was something left out of the book.  That was implementing the IBinarySerialize interface to go along with the Format.UserDefined value for the SqlUserDefinedAggregate attribute.  Using this interface is not necessary if you can use the Format.Native option.  I could not, because I was using the StringBuilder internally.  (The book mentioned Format.SerializedDataWithMetadata which is not an option that currently exists.)

The named parameters on the SqlUserDefinedAggregate are worth mentioning.  The MaxByteSize (whos maximum is 8000) specifies the maximum size of the serialized data.  Roughly, that means you can have a maximum size string of 3,999 characters ((8000 - 2 control bytes) / 2 bytes for UTF-16 encoding).  The IsInvariantToNulls parameter indicates if null values are ignored.  This makes sense with a string, because tagging a null to a string is not relevant.  IsVariantToDuplicates determines if duplicate values will be used.  False means duplicates will be used.  IsInvariantToOrder specifies if the order of the incoming data is relevant.  And finally IsNullIfEmpty indicates if a null value is returned if there are no values to be aggregated.

using System;
using System.Data.Sql;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;

namespace SqlAggregateFunctions
{
     [Serializable()]
     [SqlUserDefinedAggregate(Format.UserDefined, MaxByteSize=8000, IsInvariantToNulls=true,
          IsInvariantToDuplicates=false, IsInvariantToOrder=true, IsNullIfEmpty=true)]
     public class CommaListConcatenate : IBinarySerialize
     {
          private StringBuilder _list;

          public void Init()
          {
               _list = new StringBuilder();
          }

          public void Accumulate(SqlString value)
          {
               if (!value.IsNull)
               {
                    _list.Append(value.ToString());
                    _list.Append(",");
               }
          }

          public void Merge(CommaListConcatenate group)
         {
               _list.Append(group._list.ToString());
               _list.Append(",");
          }

         public SqlString Terminate()
         {
              string result = _list.ToString();

              if (result.Length == 0)
                   return SqlString.Null;
              else
                  return (SqlString)result.Substring(0, result.Length - 1);
          }

          public void Read(System.IO.BinaryReader r)
          {
               _list = new StringBuilder(r.ReadString());
          }

         public void Write(System.IO.BinaryWriter w)
        {
              w.Write(_list.ToString());
         }
     }
}

Build the assembly, and then go into Microsoft SQL Management Studio to perform the rest of the work.  The first step is essentially to import the assembly into the database.  The final step is to create the aggregate which uses your class.  The following T-SQL statements accomplish that task:

CREATE ASSEMBLY ConcatenationFunctions
FROM 'C:\Projects\SqlAggregateFunctions\SqlAggregateFunctions\bin\Debug\SqlAggregateFunctions.dll'

CREATE AGGREGATE CommaListConcatenate(@value NVARCHAR(4000))
RETURNS NVARCHAR(4000)
EXTERNAL NAME [ConcatenationFunctions].[SqlAggregateFunctions.CommaListConcatenate]

The return value of the aggregate represents the maximum size of the aggregate in character length (well, +1 for simplicity).  You get some nasty errors if you try to substitute something like TEXT instead of NVARCHAR.  I thought this was interesting considering TEXT should convert directly to SqlString.  Also, note the formatting of the EXTERNAL NAME which is the name of your assembly in your database followed by a dot and then the namespace, dot, and name of your class enclosed in brackets.

The following query can be used against the AdventureWorks database to test the UDA.  The result?  A nice comma delimited list of e-mail addresses.

SELECT dbo.CommaListConcatenate(P.EmailAddress)
|FROM HumanResources.Employee E
INNER JOIN Person.Contact P ON E.ContactID = P.ContactID
LEFT JOIN HumanResources.EmployeeDepartmentHistory ED ON E.EmployeeID = ED.EmployeeID
WHERE ED.DepartmentID = 3

I cannot say I'll ever find a really good use for the UDA.  Concatenating strings using a UDA just has too many limitations to be universally helpful such as the inability to specify a delimiter.  However, I thought it was an interesting subject, and I like the fact that I can; even if I never will.

Print | posted on Tuesday, February 21, 2006 12:06 PM |

Feedback

Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

I have been looking for a way to implement Sybase List() function today and the solution in this article is exactly what I need.

Do I need Visual Studio .Net to compile the C# codes first, which generates the DLL file? Or SQLServer2005 can handle this from top to bottom? Thanks.
2/22/2006 1:08 PM | Mason
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

I'm downloading .Net Framework 2.0 SDK. Hopefully it can digest C# codes.
2/22/2006 1:36 PM | Mason
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

I think you'll be able to pull off just using SDK (I'm pretty sure it has the C# compiler). However, you're on your own for writing the code in the text files. The Express editions are something else you could look at.

I'm glad the article was useful for you.
2/22/2006 5:31 PM | Anthony Trudeau
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

I downloaded Visual C# 2005 Express last night and built the DLL. Now the list() UDA function is created and working as expected. Thanks.
2/23/2006 10:45 AM | Mason
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

anyone know how to get by the maxByteSize of 8000, I have also created a List() function to mimic Sybases, but when selecting a large amount of data I get errors
3/13/2006 11:21 AM | Mike S
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

I tried it it was correct. I was looking for a such example.Thank you
4/14/2006 2:01 PM | Pandi
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

You're welcome.
4/14/2006 2:15 PM | Anthony Trudeau
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

Nice article!

http://www.codershangout.com
9/12/2006 7:16 AM | cbmeeks
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

Thank you!
9/12/2006 8:01 AM | Anthony Trudeau
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

This code is a lifesaver... far more efficient than any of the other ways of producing a comma-delimited list I've tried. Thank you!

One thing though for others trying to implement this: if you haven't already, you'll need to enable CLR on the SQL Server to get this to work. Here's how:

EXEC sp_configure 'show advanced options' , '1';
go
reconfigure;
go
EXEC sp_configure 'clr enabled' , '1'
go
reconfigure;
7/16/2007 10:29 AM | Rich
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

Are you sure about this '(8000 - 2 control bytes) / 2' limit?
I am using ASCII encoding and I am getting an error when the serialized data gets longer than 8000-250 bytes (it works fine for 8000-400 bytes).
1/21/2008 11:55 PM | chopeen
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

Make sure you're using user defined serialization for your UDA. The maximum character computation is based on UTF-16 encoding and comes from MSDN documentation.
1/22/2008 1:19 AM | Anthony Trudeau
Gravatar

# re: User Defined Aggregates (UDA) in SQL Server 2005

I am also looking for a way to get around the maxByteSize of 8000
4/22/2008 5:28 AM | CD

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 8 and 4 and type the answer here:

Powered by: