Using SQL Server 2005 CLR Features to Create Aggregates
Using SQL Server 2005 CLR Features to Create Aggregates
I played around with SQL Server 2005 a bit this past week to explore (more in-depth) some of the different CLR features that are available. I'll be posting a few of the things I've experimented with over the next few days. One thing that can be really useful is the ability to create custom aggregate functions using C# or VB.NET, store them in the database and then use them within queries or stored procedures.
Jeff Jones (a SQL Server guru teaching at Interface Technical Training) and I were talking about the new Pivot functionality and how it was fairly painful to create the list of values to pivot on using pure T-SQL. It can definitely be done, but is a bit "hacky" IMHO. For example, the following query (which Jeff shared with me) could be used to dynamically grab the values to use as pivot columns. The query relies upon XPath capabilities of SQL Server to create a list similar to "[2004],[2005],[2006]" and then uses that list in a pivot statement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | SELECT @Yearlist = STUFF((SELECT ', [' + DYear + ']' AS [text()] FROM (SELECT DISTINCT CONVERT(VARCHAR, YEAR(OrderDate)) as dyear FROM dbo.Orders) Y ORDER BY dyear FOR XML PATH('')), 1, 1, '') IF @YEARLIST IS NOT NULL EXEC ('SELECT * FROM (SELECT ProductID, YEAR(OrderDate) as theyear, Total FROM dbo.Orders) t PIVOT (SUM(Total) FOR theyear IN (' + @Yearlist + ')) PVT') ELSE SELECT * FROM (SELECT ProductID, YEAR(OrderDate) as theyear, Total FROM dbo.Orders) t PIVOT (SUM(Total) FOR theyear IN ([2006])) PVT Although the XML PATH trick shown above does create the list of years to pivot on correctly, it's fairly tricky to understand what's going on. After thinking through it a little I decided that writing a CLR aggregate to handle generating the list of years would make things much easier. So, using a VS.NET 2005 database project I wrote the following class which handles generating the "[year1],[year2],[year3]" list used in the pivot query. [Serializable] [SqlUserDefinedAggregate( Format.UserDefined, //use clr serialization IsInvariantToNulls = true, //optimizer property IsInvariantToDuplicates = false, //optimizer property IsInvariantToOrder = false, //optimizer property MaxByteSize = 8000) //maximum size in bytes of persisted value ] public class PivotConcatenate : IBinarySerialize { /// <summary> /// The variable that holds the intermediate /// result of the concatenation /// </summary> private ArrayList items; /// <summary> /// Initialize the internal data structures /// </summary> public void Init() { this.items = new ArrayList(); } /// <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; } if (!items.Contains(value.Value)) { this.items.Add(value.Value); } } /// <summary> /// Merge the partially computed aggregate /// with this aggregate. /// </summary> /// <param name="other"></param> public void Merge(PivotConcatenate other) { this.items.Add(other.items); } /// <summary> /// Called at the end of aggregation, to return the /// results of the aggregation. /// </summary> /// <returns></returns> public SqlString Terminate() { //delete the trailing comma, if any if (this.items != null && this.items.Count > 0) { //output = this.items.ToString(0, //this.items.Length - 2); } return new SqlString(AddDelimeters(true)); } public string AddDelimeters(bool addStartEndChars) { string output = String.Empty; string[] newItems = (string[])items.ToArray(typeof(string)); Array.Sort(newItems); output = String.Join("],[", newItems); if (addStartEndChars) output = "[" + output + "]"; return output; } public void Read(BinaryReader r) { items = new ArrayList(); items.Add(r.ReadString()); } public void Write(BinaryWriter w) { w.Write(AddDelimeters(false)); } } After deploying the aggregate class to my SQL Server database I was then able to change the query shown earlier to the following which is much simpler I think. Notice that the PivotConcatenate aggregate (shown above) is called directly rather than using the XML PATH trick. DECLARE @Yearlist VARCHAR(MAX) SELECT @Yearlist = (SELECT dbo.PivotConcatenate(CONVERT(VARCHAR, YEAR(OrderDate))) as dyear FROM dbo.Orders) PRINT @Yearlist EXEC ('SELECT * FROM (SELECT ProductID, YEAR(OrderDate) as theyear, Total FROM dbo.Orders) t PIVOT (SUM(Total) FOR theyear IN (' + @Yearlist + ')) PVT') |
SQL Server 2005's CLR capabilities can definitely help simplify code and make it easier to do string manipulations plus much more. I'll post some examples of using CLR functionality to grab RSS feeds from within SQL Server soon. Thanks to Jeff Jones for sharing the original pivot example with me.
You May Also Like
aggregate functions, C#, CLR, Pivot, SQL, SQL Server 2005, VB.NET
A Simple Introduction to Cisco CML2
0 3774 0Mark Jacob, Cisco Instructor, presents an introduction to Cisco Modeling Labs 2.0 or CML2.0, an upgrade to Cisco’s VIRL Personal Edition. Mark demonstrates Terminal Emulator access to console, as well as console access from within the CML2.0 product. Hello, I’m Mark Jacob, a Cisco Instructor and Network Instructor at Interface Technical Training. I’ve been using … Continue reading A Simple Introduction to Cisco CML2
Cable Testers and How to Use them in Network Environments
0 692 1This content is from our CompTIA Network + Video Certification Training Course. Start training today! In this video, CompTIA Network + instructor Rick Trader demonstrates how to use cable testers in network environments. Let’s look at some tools that we can use to test our different cables in our environment. Cable Testers Properly Wired Connectivity … Continue reading Cable Testers and How to Use them in Network Environments
Government Edition – Encrypting a USB Flash Drive in Windows 10
0 270 2In this video, Security Instructor Mike Danseglio demonstrates how to use BitLocker in Window 10 to secure files on a USB Flash drive that adhere to stricter data protection requirements as found inside Government entities. BitLocker 2-day instructor-led training is now available at Interface: BITLOCK: Planning and Deploying BitLocker Drive Encryption Training Video Transcription: Hi. … Continue reading Government Edition – Encrypting a USB Flash Drive in Windows 10