From 6e0cd92dc379042cba4f2366a69fbe4b028f23ca Mon Sep 17 00:00:00 2001 From: Jovan Popovic Date: Wed, 20 Dec 2017 12:29:11 +0100 Subject: [PATCH 1/6] Initial version of transitive closure library --- .../sql-clr/TransitiveClosure/.gitignore | 7 + .../Properties/AssemblyInfo.cs | 36 ++ .../TransitiveClosure/TransitiveClosure.tt | 20 ++ .../TransitiveClosureAggregate.cs | 321 ++++++++++++++++++ .../TransitiveClosureAggregatorLibrary.csproj | 73 ++++ .../sql-clr/TransitiveClosure/tcc.pfx | Bin 0 -> 1764 bytes 6 files changed, 457 insertions(+) create mode 100644 samples/features/sql-clr/TransitiveClosure/.gitignore create mode 100644 samples/features/sql-clr/TransitiveClosure/Properties/AssemblyInfo.cs create mode 100644 samples/features/sql-clr/TransitiveClosure/TransitiveClosure.tt create mode 100644 samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregate.cs create mode 100644 samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregatorLibrary.csproj create mode 100644 samples/features/sql-clr/TransitiveClosure/tcc.pfx diff --git a/samples/features/sql-clr/TransitiveClosure/.gitignore b/samples/features/sql-clr/TransitiveClosure/.gitignore new file mode 100644 index 00000000..407d0cc4 --- /dev/null +++ b/samples/features/sql-clr/TransitiveClosure/.gitignore @@ -0,0 +1,7 @@ +*.cproj.user +.vs/* +.vscode/* +bin/* +obj/* +Properties/PublishProfiles/* +TransitiveClosure.sql \ No newline at end of file diff --git a/samples/features/sql-clr/TransitiveClosure/Properties/AssemblyInfo.cs b/samples/features/sql-clr/TransitiveClosure/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..09186f99 --- /dev/null +++ b/samples/features/sql-clr/TransitiveClosure/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TransitiveClosureAggregatorLibrary")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TransitiveClosureAggregatorLibrary")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("097ef341-926d-4dd2-a434-08e9980d6089")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/features/sql-clr/TransitiveClosure/TransitiveClosure.tt b/samples/features/sql-clr/TransitiveClosure/TransitiveClosure.tt new file mode 100644 index 00000000..96c0308d --- /dev/null +++ b/samples/features/sql-clr/TransitiveClosure/TransitiveClosure.tt @@ -0,0 +1,20 @@ +<#@output extension=".sql"#> +<#@ template language="C#" hostspecific="True" #> + +DROP AGGREGATE IF EXISTS TCC.CLUSTERING; +GO + +--Drop the assembly if it already exists +DROP ASSEMBLY IF EXISTS TransitiveClosure; +GO + +--Create the assembly +CREATE ASSEMBLY TransitiveClosure FROM '<#= this.Host.ResolvePath("bin\\Release\\TransitiveClosureAggregatorLibrary.dll") #>' WITH PERMISSION_SET = SAFE; +GO + +CREATE SCHEMA TCC; +GO + +CREATE AGGREGATE TCC.CLUSTERING(@id1 INT, @id2 INT) +RETURNS NVARCHAR(MAX) +EXTERNAL NAME TransitiveClosure.[TransitiveClosure.Aggregate]; \ No newline at end of file diff --git a/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregate.cs b/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregate.cs new file mode 100644 index 00000000..20aa3491 --- /dev/null +++ b/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregate.cs @@ -0,0 +1,321 @@ +using System; +using System.IO; +using System.Data.SqlTypes; +using System.Text; +using Microsoft.SqlServer.Server; +using System.Collections.Generic; +using System.Collections; + +namespace TransitiveClosure +{ + /// + /// Class that represents a group of numbers in the same cluster. + /// + public class Group: IEnumerable + { + private int? _groupRoot = null; + + private Dictionary _group = new Dictionary(); + + public Dictionary.KeyCollection Elements => _group.Keys; + + public int Count => _group.Keys.Count; + + public bool ContainsElement(int element) + { + return _group.ContainsKey(element); + } + + /// + /// Adds a pair of numbers to a group. + /// + /// + /// + public void AddUnique(int from, int to) + { + if (_groupRoot == null) _groupRoot = from; + this.AddIfNotExists(from); + this.AddIfNotExists(to); + } + /// + /// Adds the element into the current group. + /// + /// The number that should be added. + public void Add(int element) + { + if (_groupRoot == null) _groupRoot = element; + _group.Add(element, true); + } + + /// + /// Adds the element to a group if it is not already there. + /// + /// + public void AddIfNotExists(int element) + { + if (!_group.ContainsKey(element)) + { + _group.Add(element, true); + } + } + + public void MergeWith(Group source) + { + foreach (var e in source.Elements) + { + this.AddIfNotExists(e); + } + } + + public IEnumerator GetEnumerator() + { + foreach(var e in _group.Keys) + { + yield return e; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return (IEnumerator)GetEnumerator(); + } + + public override string ToString() + { + return string.Format($"[{_groupRoot.Value}]"); + } + } + + public class GroupSet: IEnumerable + { + private List _groupSet = new List(); + private Dictionary _numbers = new Dictionary(); + private int _merges = 0; + + public int Groups => _groupSet.Count; + + public int Numbers => _numbers.Count; + + public int Merges => _merges; + + public void Add(Group group) + { + _groupSet.Add(group); + + foreach(int e in group) + { + if (!_numbers.ContainsKey(e)) + _numbers.Add(e, group); + else + throw new ApplicationException("Element is already assigned to a group"); + } + } + + public List FindInGroups(int from, int to) + { + var result = new List(); + + if (_numbers.ContainsKey(from)) result.Add(_numbers[from]); + if (_numbers.ContainsKey(to)) result.Add(_numbers[to]); + + return result; + } + + public void AddPair(int from, int to) + { + //Find if the inputValue is already in a group + var foundInGroups = FindInGroups(from, to); + + // no item matches: create a new group and add both the values to it + if (foundInGroups.Count == 0) + { + var ng = new Group(); + ng.AddIfNotExists(from); + ng.AddIfNotExists(to); + + _groupSet.Add(ng); + + if (!_numbers.ContainsKey(from)) _numbers.Add(from, ng); + if (!_numbers.ContainsKey(to)) _numbers.Add(to, ng); + } + + // one item match, add the related item to the same group + if (foundInGroups.Count == 1) + { + var g = foundInGroups[0]; + g.AddUnique(from, to); + + if (!_numbers.ContainsKey(from)) _numbers.Add(from, g); else _numbers[from] = g; + if (!_numbers.ContainsKey(to)) _numbers.Add(to, g); else _numbers[to] = g; + } + + // if there is a match for both items but in two different groups + // merge them into just one group and delete the other + if (foundInGroups.Count == 2) + { + var g1 = foundInGroups[0]; + var g2 = foundInGroups[1]; + + if (g1 == g2) return; + + // Always move the smaller group + if (g2.Count > g1.Count) + { + var t = g1; + g1 = g2; + g2 = t; + } + + if (!_numbers.ContainsKey(from)) _numbers.Add(from, g1); else _numbers[from] = g1; + if (!_numbers.ContainsKey(to)) _numbers.Add(to, g1); else _numbers[to] = g1; + + g1.MergeWith(g2); + + foreach(var e in g2) + { + if (!_numbers.ContainsKey(e)) _numbers.Add(e, g1); else _numbers[e] = g1; + } + + _merges += 1; + + _groupSet.Remove(g2); + } + } + + public IEnumerator GetEnumerator() + { + foreach(var g in _groupSet) + { + yield return g; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return (IEnumerator)GetEnumerator(); + } + } + + /// + /// Aggregate that takes a pair of numbers that represents an edge in some graph/relation. + /// As an output, returns groups of reachable edges, for example: + /// { + /// "0":[1,2,3,4], + /// "1":[5,6,7], + /// "2":[8,9] + /// } + /// + [Serializable] + [SqlUserDefinedAggregateAttribute(Format.UserDefined, MaxByteSize = -1)] + public class Aggregate : IBinarySerialize + { + private GroupSet _groupSet; + + public int Groups => _groupSet.Groups; + + public int Numbers => _groupSet.Numbers; + + public int Merges => _groupSet.Merges; + + public void Init() + { + _groupSet = new GroupSet(); + } + + public void Accumulate(int inputValue1, int inputValue2) + { + _groupSet.AddPair(inputValue1, inputValue2); + } + + public void Merge(Aggregate value) + { + foreach (var g in value._groupSet) + { + int? pe = null; + foreach (var ce in g) + { + if (pe.HasValue) + { + this.Accumulate(pe.Value, ce); + } + pe = ce; + } + } + } + + public SqlString Terminate() + { + return this.ToString(); + } + + public override string ToString() + { + int c = 0; + StringBuilder sb = new StringBuilder(); + sb.Append("{"); + foreach (var g in this._groupSet) + { + sb.Append("\"" + c + "\":["); + + var ea = new int[g.Elements.Count]; + g.Elements.CopyTo(ea, 0); + + sb.Append(string.Join(",", ea)); + + sb.Append("],"); + + c += 1; + } + if (sb.Length > 1) sb.Remove(sb.Length - 1, 1); + sb.Append("}"); + return sb.ToString(); + } + + public void Read(BinaryReader r) + { + if (r == null) throw new ArgumentNullException("r"); + _groupSet = new GroupSet(); + + // Group Count + int g = r.ReadInt32(); + + // For Each Group + for (int j = 0; j < g; j++) + { + var l = new Group(); + + // List Size (or Values Count) + int s = r.ReadInt32(); + + // Read values and put them in the list + for (int i = 0; i < s; i++) + { + l.Add(r.ReadInt32()); + } + + // Add list to dictionary + _groupSet.Add(l); + } + } + + public void Write(BinaryWriter w) + { + if (w == null) throw new ArgumentNullException("w IS NULL"); + + // Group count + w.Write(_groupSet.Groups); + + foreach (var g in _groupSet) + { + // Values Count + w.Write(g.Count); + + // Values + foreach (var e in g) + { + w.Write(e); + } + } + } + } +} \ No newline at end of file diff --git a/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregatorLibrary.csproj b/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregatorLibrary.csproj new file mode 100644 index 00000000..68a5f113 --- /dev/null +++ b/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregatorLibrary.csproj @@ -0,0 +1,73 @@ + + + + + Debug + AnyCPU + {097EF341-926D-4DD2-A434-08E9980D6089} + Library + Properties + TransitiveClosureAggregatorLibrary + TransitiveClosureAggregatorLibrary + v4.5.2 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + true + + + tcc.pfx + + + + + + + + + + + + + + + + + + True + True + TransitiveClosure.tt + + + TextTemplatingFileGenerator + TransitiveClosure.sql + + + + + + + + + + \ No newline at end of file diff --git a/samples/features/sql-clr/TransitiveClosure/tcc.pfx b/samples/features/sql-clr/TransitiveClosure/tcc.pfx new file mode 100644 index 0000000000000000000000000000000000000000..12006e673aeaa6b44f325c786119229bd29b4d31 GIT binary patch literal 1764 zcmZXTc{J2}8^C|Fm}xAd1{H_Q9r9Xo`%PKO8X;@sW{Ql2X6)D4%OxhJk)>-HQPv0{ z+-WjuB!nxn4m!k;+hoZ)snkXHSLeR>p7*@xInVZap67eMe>@LJM@_?F2#}7-M`1J> z){IY5Ff^=~j!Hw)QOOWa0_li`|0y9VkaR>Tgo_}(jgt6#N>UsKFQ&sAKsuZaDx$>y zi=oG{$Q?Ak0oz!?kO+sP2at65ukEI+KTTIB?uw$eW83PnQ__1f>@Ig$!OtX!R@H2C za&K?z)T!jn+C1>}Bfs1%da)j00OpA6R_C{WjlMX5sEbY{WM)_#6nG&{><)Z*zEI|d zP%A`Aw4%mDi{)J->g6ouAN*1^$AkSn1 zXAt?tU+lG+GG6Cv5UGRqf#RM`d(2Ds9z=KwRj&1s@$D6)`tWCU(s?$xtm;*r*z|~d zkqyk(K_Nk0tHCO>OV=vq0 ztbMCy@R&n}m%fX#yC^C9erzoxJpYsy%J}N<>mAHZbNx#+E;Hv6;?=G=Ie~R_cN1|T z!hOy$&cJKqeAi?7J2NBhC**_QqV-@+kEWb^`n#)wC((>|&88zCXrFkx(W1DVE8b!Q z$@)Gk?D%={?WOWmO}fRv{PFPFU3~rL%4-!)xsz>+j+3)9`wcN*^!w5j(azL+Mgs+t zC_*o8zndziTFd;T=j)xDGA?VqKk^l6@BHnty0B2K>{LDTEY)odGZVC+sIlyEsc=BZ z_)Uv#;^@ljzx%vKWjIW%WazCKYdkgGEkd$5b=LTULy+oP)ty$DXB*GV^LL>XR zzIhbb$$AgG)G%6JNV>T7&dh@=-3O5-W(C0Y@OO2U!as79^uES|}J2M%Uj3BRcIn2{-_LfHx2d{0xLa zN&&(FJn$1_zmZ^o3T4Nk_!1BTv7ezl6u1DzzTcGJZsf={5v{$P_du#8hD6U38yGbGw zE&2uB*R9TF<{E$M{8RqSw)PR0n@W}75f$ZeSNy)`r0|XBvwDg4KRy+!7k+;4jWrRF z&p)vXQKUuE$l<)G#riI~K&HI)2TjXoovrFRqHSjfX2%8}3Zco;P# zP*~x-)zA!J&fmD+JGk`dw$ClNd1z_(gb}?zb00^wK;^6@#tOI9xHXjhrJ3;l`JtZz$HSSNB!|r{Lo%2~A!o~lRi)kh;_q?cX7z2Ff=pPmoYsPrHFSq1zPAQc-7HCryaDAq|Ys!hBA*c+ZQDRzH z1Y7|rYq|)ZMjv#ppC-2FB+eRE$(uOzAZ77WZC-4SbQ^5+9v2-dwbev@!2Pzse*y9u B0ucZJ literal 0 HcmV?d00001 From a70c6a5ecefb856742f21f698c0dda85f0f8c34e Mon Sep 17 00:00:00 2001 From: Jovan Popovic Date: Wed, 20 Dec 2017 13:01:16 +0100 Subject: [PATCH 2/6] Renamed schema to TC --- .../sql-clr/TransitiveClosure/TransitiveClosure.tt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/samples/features/sql-clr/TransitiveClosure/TransitiveClosure.tt b/samples/features/sql-clr/TransitiveClosure/TransitiveClosure.tt index 96c0308d..2216864e 100644 --- a/samples/features/sql-clr/TransitiveClosure/TransitiveClosure.tt +++ b/samples/features/sql-clr/TransitiveClosure/TransitiveClosure.tt @@ -1,7 +1,10 @@ <#@output extension=".sql"#> <#@ template language="C#" hostspecific="True" #> -DROP AGGREGATE IF EXISTS TCC.CLUSTERING; +DROP AGGREGATE IF EXISTS TC.CLUSTERING; +GO + +DROP SCHEMA IF EXISTS TC; GO --Drop the assembly if it already exists @@ -12,9 +15,9 @@ GO CREATE ASSEMBLY TransitiveClosure FROM '<#= this.Host.ResolvePath("bin\\Release\\TransitiveClosureAggregatorLibrary.dll") #>' WITH PERMISSION_SET = SAFE; GO -CREATE SCHEMA TCC; +CREATE SCHEMA TC; GO -CREATE AGGREGATE TCC.CLUSTERING(@id1 INT, @id2 INT) +CREATE AGGREGATE TC.CLUSTERING(@id1 INT, @id2 INT) RETURNS NVARCHAR(MAX) EXTERNAL NAME TransitiveClosure.[TransitiveClosure.Aggregate]; \ No newline at end of file From d37ec2b7b759831458dbb512fdb508ccb2b30d94 Mon Sep 17 00:00:00 2001 From: Jovan Popovic Date: Wed, 20 Dec 2017 14:42:38 +0100 Subject: [PATCH 3/6] Added README to TCC sample --- .../sql-clr/TransitiveClosure/README.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 samples/features/sql-clr/TransitiveClosure/README.md diff --git a/samples/features/sql-clr/TransitiveClosure/README.md b/samples/features/sql-clr/TransitiveClosure/README.md new file mode 100644 index 00000000..bb8bff87 --- /dev/null +++ b/samples/features/sql-clr/TransitiveClosure/README.md @@ -0,0 +1,79 @@ +# Implementing Transitive Closure Clustering in SQL Server using CLR UDF +SQL Database don't have built-in support for transitive closure clustering, so the only workaround is to implement this algorithm in .Net framework and expose it as T-SQL function. +This code sample demonstrates how to create CLR User-Defined aggregate that implements clustering. + +### Contents + +[About this sample](#about-this-sample)
+[Build the CLR/RegEx functions](#build-functions)
+[Add RegEx functions to your SQL database](#add-functions)
+[Test the functions](#test)
+[Disclaimers](#disclaimers)
+ + + +## About this sample +1. **Applies to:** SQL Server 2016+ Enterprise / Developer / Evaluation Edition +2. **Key features:** + - CLR, JSON +3. **Programming Language:** .NET C# +4. **Author:** Davide Mauri, Jovan Popovic [jovanpop-msft] + + + +## Build the CLR/RegEx functions + +1. Download the source code and open the solution using Visual Studio. +2. Change the password in .pfk file and rebuild the solution in **Release** mode. +3. Open and save TransitiveClosure.tt to generate output T-SQL file that will contain script that inserts .dll file with the Transitive closure clustering aggregate. + + +## Add Clustering aggregate to your SQL database + +File TransitiveClosure.sql contains the code that will import aggregate into SQL Database. + +If you have not added CLR assemblies in your database, you should use the following script to enable CLR: +``` +sp_configure @configname=clr_enabled, @configvalue=1 +GO +RECONFIGURE +GO +``` + +Once you enable CLR, you can use the T-SQL script to add the clustering aggregate. The script depends on the location where you have built the project, and might look like: +``` +CREATE ASSEMBLY TransitiveClosure FROM 'D:\GitHub\sql-server-samples\samples\features\sql-clr\TransitiveClosure\bin\Release\TransitiveClosureAggregatorLibrary.dll' WITH PERMISSION_SET = SAFE; +GO + +CREATE SCHEMA TC; +GO + +CREATE AGGREGATE TC.CLUSTERING(@id1 INT, @id2 INT) +RETURNS NVARCHAR(MAX) +EXTERNAL NAME TransitiveClosure.[TransitiveClosure.Aggregate]; +``` + +This code will import assembly in SQL Database and add three functions that provide clustering functionalities. + + + +## Test the function + +Once you create the assembly and expose the functions, you can use it to cluster some relational data in T-SQL code: + +``` +declare @edges table(n1 int, n2 int); + +insert into @edges +values (1,2),(2,3),(3,4),(4,5),(2,21),(2,22), +              (7,8),(8,9),(9,10); + +select TC.CLUSTERING(n1,n2) +from @edges; +``` + + + +## Disclaimers +The code included in this sample is not intended to be a set of best practices on how to build scalable enterprise grade applications. This is beyond the scope of this sample. + From d8aebaf6a207c18f3775f2e5d292c498d3d6bc29 Mon Sep 17 00:00:00 2001 From: Jovan Popovic Date: Wed, 20 Dec 2017 14:49:22 +0100 Subject: [PATCH 4/6] Updated README --- .../sql-clr/TransitiveClosure/README.md | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/samples/features/sql-clr/TransitiveClosure/README.md b/samples/features/sql-clr/TransitiveClosure/README.md index bb8bff87..ad87473a 100644 --- a/samples/features/sql-clr/TransitiveClosure/README.md +++ b/samples/features/sql-clr/TransitiveClosure/README.md @@ -21,7 +21,7 @@ This code sample demonstrates how to create CLR User-Defined aggregate that impl -## Build the CLR/RegEx functions +## Build the CLR/TransitiveClosure aggregate 1. Download the source code and open the solution using Visual Studio. 2. Change the password in .pfk file and rebuild the solution in **Release** mode. @@ -53,13 +53,13 @@ RETURNS NVARCHAR(MAX) EXTERNAL NAME TransitiveClosure.[TransitiveClosure.Aggregate]; ``` -This code will import assembly in SQL Database and add three functions that provide clustering functionalities. +This code will import assembly in SQL Database and add an aggregate that provides clustering functionalities. ## Test the function -Once you create the assembly and expose the functions, you can use it to cluster some relational data in T-SQL code: +Once you create the assembly and expose the aggregate, you can use it to cluster some relational data in T-SQL code: ``` declare @edges table(n1 int, n2 int); @@ -71,6 +71,26 @@ values (1,2),(2,3),(3,4),(4,5),(2,21),(2,22), select TC.CLUSTERING(n1,n2) from @edges; ``` +The result will be JSON document that groups the numbers that belong to the same cluster. +```javascript +{ + "0":[1,2,3,4,5,21,22], + "1":[7,8,9,10] +} +``` +You can transform this JSON document into relational formatusing **OPENJSON** function: +``` +select cluster = [key], elements = value +from openjson( +       (select TC.CLUSTERING(n1,n2) from @edges) +); +``` +The result of this query is: + +|cluster|elements| +|----|---| +|0|[1,2,3,4,5,21,22]| +|1|[7,8,9,10]| From cba3faa417c96bad0fd2b7611e2150687bc335ab Mon Sep 17 00:00:00 2001 From: Jovan Popovic Date: Wed, 20 Dec 2017 15:33:48 +0100 Subject: [PATCH 5/6] Update README --- samples/features/sql-clr/TransitiveClosure/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/features/sql-clr/TransitiveClosure/README.md b/samples/features/sql-clr/TransitiveClosure/README.md index ad87473a..1139e182 100644 --- a/samples/features/sql-clr/TransitiveClosure/README.md +++ b/samples/features/sql-clr/TransitiveClosure/README.md @@ -5,7 +5,7 @@ This code sample demonstrates how to create CLR User-Defined aggregate that impl ### Contents [About this sample](#about-this-sample)
-[Build the CLR/RegEx functions](#build-functions)
+[Build the CLR/TransitiveClosure aggregate](#build-functions)
[Add RegEx functions to your SQL database](#add-functions)
[Test the functions](#test)
[Disclaimers](#disclaimers)
From b0b6e74501a156d01c7b984e66ef4530d0ba5c4a Mon Sep 17 00:00:00 2001 From: yorek Date: Fri, 22 Dec 2017 10:02:37 -0800 Subject: [PATCH 6/6] updated documentation --- .../sql-clr/TransitiveClosure/README.md | 19 ++++++++++++------- .../TransitiveClosureAggregate.cs | 9 ++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/samples/features/sql-clr/TransitiveClosure/README.md b/samples/features/sql-clr/TransitiveClosure/README.md index 1139e182..945ff5d9 100644 --- a/samples/features/sql-clr/TransitiveClosure/README.md +++ b/samples/features/sql-clr/TransitiveClosure/README.md @@ -1,5 +1,9 @@ # Implementing Transitive Closure Clustering in SQL Server using CLR UDF SQL Database don't have built-in support for transitive closure clustering, so the only workaround is to implement this algorithm in .Net framework and expose it as T-SQL function. +A discussion on the problem, the algorithm and a pure T-SQL based solution can be found here: +- [Transitive Closure Clustering with SQL Server, UDA and JSON](https://medium.com/@mauridb/transitive-closure-clustering-with-sql-server-uda-and-json-dade18953fd2) +- [T-SQL Puzzle Challenge Grouping Connected Items](http://www.itprotoday.com/microsoft-sql-server/t-sql-puzzle-challenge-grouping-connected-items) + This code sample demonstrates how to create CLR User-Defined aggregate that implements clustering. ### Contents @@ -17,7 +21,7 @@ This code sample demonstrates how to create CLR User-Defined aggregate that impl 2. **Key features:** - CLR, JSON 3. **Programming Language:** .NET C# -4. **Author:** Davide Mauri, Jovan Popovic [jovanpop-msft] +4. **Author:** [Davide Mauri](https://github.com/yorek), Jovan Popovic [jovanpop-msft] @@ -65,8 +69,9 @@ Once you create the assembly and expose the aggregate, you can use it to cluster declare @edges table(n1 int, n2 int); insert into @edges -values (1,2),(2,3),(3,4),(4,5),(2,21),(2,22), -              (7,8),(8,9),(9,10); +values + (1,2),(2,3),(3,4),(4,5),(2,21),(2,22), + (7,8),(8,9),(9,10); select TC.CLUSTERING(n1,n2) from @edges; @@ -74,21 +79,21 @@ from @edges; The result will be JSON document that groups the numbers that belong to the same cluster. ```javascript { - "0":[1,2,3,4,5,21,22], - "1":[7,8,9,10] + "0":[1,2,3,4,5,21,22], + "1":[7,8,9,10] } ``` You can transform this JSON document into relational formatusing **OPENJSON** function: ``` select cluster = [key], elements = value from openjson( -       (select TC.CLUSTERING(n1,n2) from @edges) + (select TC.CLUSTERING(n1,n2) from @edges) ); ``` The result of this query is: |cluster|elements| -|----|---| +|---|---| |0|[1,2,3,4,5,21,22]| |1|[7,8,9,10]| diff --git a/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregate.cs b/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregate.cs index 20aa3491..2969b22a 100644 --- a/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregate.cs +++ b/samples/features/sql-clr/TransitiveClosure/TransitiveClosureAggregate.cs @@ -14,11 +14,9 @@ namespace TransitiveClosure public class Group: IEnumerable { private int? _groupRoot = null; - + private Dictionary _group = new Dictionary(); - public Dictionary.KeyCollection Elements => _group.Keys; - public int Count => _group.Keys.Count; public bool ContainsElement(int element) @@ -89,13 +87,12 @@ namespace TransitiveClosure public class GroupSet: IEnumerable { private List _groupSet = new List(); + // This keeps about the distinct numbers and in which group they are private Dictionary _numbers = new Dictionary(); private int _merges = 0; public int Groups => _groupSet.Count; - public int Numbers => _numbers.Count; - public int Merges => _merges; public void Add(Group group) @@ -212,9 +209,7 @@ namespace TransitiveClosure private GroupSet _groupSet; public int Groups => _groupSet.Groups; - public int Numbers => _groupSet.Numbers; - public int Merges => _groupSet.Merges; public void Init()