Transitive closure clustering with CLR and JSON

Published Mar 23 2019 05:22 PM 257 Views
Microsoft
First published on MSDN on Dec 20, 2017

Transitive closure is a graph algorithm that tries to follow paths in graph edges and tries to find all elements that can be reached from some element, or groups of elements that are mutually reachable. Although SQL Server still don’t provides native function for transitive closure, this algorithm can be implemented using CLR aggregates that can be placed in SQL database.




Imagine that you have some relation in a database such as customer who bought product 17 also bought product 25, customer who bought product 25 also bought product 48. You can easily find these kinds of relations in foreign keys of the tables - for example, join OrderLines with same OrderID and take ProductIDs from the order lines like in the following query (from WideWorldImporters sample database):
SELECT ol1.StockItemID AS Product1,
ol2.StockItemID AS Product2
FROM   Sales.OrderLines AS ol1
INNER JOIN Sales.OrderLines AS ol2
ON ol1.OrderLineID = ol2.OrderLineID
The harder job is to and create groups of the products that are frequently bought together like [17,25,48] in the example above. In this case, you would need to follow the “edges” and reach all products in the chain.

Davide Mauri (SQL MVP) created an interesting aggregate that implements this clustering algorithm . The aggregate takes the pairs of number that represent related product ids, people ids, etc.:
N1 N2
1 2
2 3
3 4
4 5
2 21
2 22
7 8
8 9
9 10

The values in this table might be related products, friendship relationships or any other pairs of related ids.

In this example you can see that there are two groups (cluster) – one with root 1 and another with root 7. Davide built an CLR aggregate that takes the nodes from the rows that represent relations and returns the groups containing clustered nodes – in this case:
{"0":[1,2,3,4,5,21,22],"1":[7,8,9,10]}
You can download the aggregate from this GitHub repository: yorek/non-scalar-uda-transitive-closure , build solution, and create assembly in SQL database:
CREATE ASSEMBLY TransitiveClosure
FROM 'D:\...\TransitiveClosure\bin\Release\TransitiveClosureAggregatorLibrary.dll';
GO

CREATE SCHEMA TC;
GO

CREATE AGGREGATE TC.CLUSTERING(@id1 INT, @id2 INT)
RETURNS NVARCHAR(MAX)
EXTERNAL NAME TransitiveClosure.[TransitiveClosure.Aggregate];

Clustering in action


The simple code that performs clustering is shown in the following example:
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;
TC.CLUSTERING aggregate performs clustering using transitive closure and returns groups formatted as JSON collection. To unpack these groups, we can use OPENJSON function:
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;

select cluster = [key], elements = value
from openjson(
(select TC.CLUSTERING(n1,n2) from @edges)
);

This code returns the following result:
cluster elements
0 [1,2,3,4,5,21,22]
1 [7,8,9,10]

As you might see, with this CLR aggregate you can easily perform grouping using transitive closure algorithm.
%3CLINGO-SUB%20id%3D%22lingo-sub-385806%22%20slang%3D%22en-US%22%3ETransitive%20closure%20clustering%20with%20CLR%20and%20JSON%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-385806%22%20slang%3D%22en-US%22%3E%0A%20%26lt%3Bmeta%20http-equiv%3D%22Content-Type%22%20content%3D%22text%2Fhtml%3B%20charset%3DUTF-8%22%20%2F%26gt%3B%3CSTRONG%3E%20First%20published%20on%20MSDN%20on%20Dec%2020%2C%202017%20%3C%2FSTRONG%3E%20%3CBR%20%2F%3E%3CP%3E%3CA%20href%3D%22https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FTransitive_closure%22%20target%3D%22_blank%22%20rel%3D%22nofollow%20noopener%20noreferrer%22%3ETransitive%20closure%20%3C%2FA%3E%20is%20a%20graph%20algorithm%20that%20tries%20to%20follow%20paths%20in%20graph%20edges%20and%20tries%20to%20find%20all%20elements%20that%20can%20be%20reached%20from%20some%20element%2C%20or%20groups%20of%20elements%20that%20are%20mutually%20reachable.%20Although%20SQL%20Server%20still%20don%E2%80%99t%20provides%20native%20function%20for%20transitive%20closure%2C%20this%20algorithm%20can%20be%20implemented%20using%20CLR%20aggregates%20that%20can%20be%20placed%20in%20SQL%20database.%3C%2FP%3E%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20Imagine%20that%20you%20have%20some%20relation%20in%20a%20database%20such%20as%20customer%20who%20bought%20product%2017%20also%20bought%20product%2025%2C%20customer%20who%20bought%20product%2025%20also%20bought%20product%2048.%20You%20can%20easily%20find%20these%20kinds%20of%20relations%20in%20foreign%20keys%20of%20the%20tables%20-%20for%20example%2C%20join%20OrderLines%20with%20same%20OrderID%20and%20take%20ProductIDs%20from%20the%20order%20lines%20like%20in%20the%20following%20query%20(from%20WideWorldImporters%20sample%20database)%3A%20%3CBR%20%2F%3E%20SELECT%20ol1.StockItemID%20AS%20Product1%2C%20%3CBR%20%2F%3E%20ol2.StockItemID%20AS%20Product2%20%3CBR%20%2F%3E%20FROM%26nbsp%3B%26nbsp%3B%20Sales.OrderLines%20AS%20ol1%20%3CBR%20%2F%3E%20INNER%20JOIN%20Sales.OrderLines%20AS%20ol2%20%3CBR%20%2F%3E%20ON%20ol1.OrderLineID%20%3D%20ol2.OrderLineID%20%3CBR%20%2F%3E%20The%20harder%20job%20is%20to%20and%20create%20groups%20of%20the%20products%20that%20are%20frequently%20bought%20together%20like%20%5B17%2C25%2C48%5D%20in%20the%20example%20above.%20In%20this%20case%2C%20you%20would%20need%20to%20follow%20the%20%E2%80%9Cedges%E2%80%9D%20and%20reach%20all%20products%20in%20the%20chain.%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20Davide%20Mauri%20(SQL%20MVP)%20created%20an%20interesting%20%3CA%20href%3D%22https%3A%2F%2Fmedium.com%2F%40mauridb%2Ftransitive-closure-clustering-with-sql-server-uda-and-json-dade18953fd2%22%20target%3D%22_blank%22%20rel%3D%22nofollow%20noopener%20noreferrer%22%3E%20aggregate%20that%20implements%20this%20clustering%20algorithm%20%3C%2FA%3E%20.%20The%20aggregate%20takes%20the%20pairs%20of%20number%20that%20represent%20related%20product%20ids%2C%20people%20ids%2C%20etc.%3A%20%3CBR%20%2F%3E%3CTABLE%3E%0A%20%20%20%3CTBODY%3E%3CTR%3E%0A%20%20%20%20%3CTD%3EN1%3C%2FTD%3E%0A%20%20%20%20%3CTD%3EN2%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E1%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E2%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E2%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E3%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E3%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E4%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E4%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E5%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E2%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E21%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E2%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E22%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E7%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E8%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E8%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E9%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E9%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E10%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%3C%2FTBODY%3E%3C%2FTABLE%3E%3CBR%20%2F%3E%20The%20values%20in%20this%20table%20might%20be%20related%20products%2C%20friendship%20relationships%20or%20any%20other%20pairs%20of%20related%20ids.%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20In%20this%20example%20you%20can%20see%20that%20there%20are%20two%20groups%20(cluster)%20%E2%80%93%20one%20with%20root%201%20and%20another%20with%20root%207.%20Davide%20built%20an%20CLR%20aggregate%20that%20takes%20the%20nodes%20from%20the%20rows%20that%20represent%20relations%20and%20returns%20the%20groups%20containing%20clustered%20nodes%20%E2%80%93%20in%20this%20case%3A%20%3CBR%20%2F%3E%20%7B%220%22%3A%5B1%2C2%2C3%2C4%2C5%2C21%2C22%5D%2C%221%22%3A%5B7%2C8%2C9%2C10%5D%7D%20%3CBR%20%2F%3E%20You%20can%20download%20the%20aggregate%20from%20this%20GitHub%20repository%3A%20%3CA%20href%3D%22https%3A%2F%2Fgithub.com%2Fyorek%2Fnon-scalar-uda-transitive-closure%22%20target%3D%22_blank%22%20rel%3D%22noopener%20noreferrer%22%3E%20%3CSTRONG%3E%20yorek%2Fnon-scalar-uda-transitive-closure%20%3C%2FSTRONG%3E%20%3C%2FA%3E%20%2C%20build%20solution%2C%20and%20create%20assembly%20in%20SQL%20database%3A%20%3CBR%20%2F%3E%20CREATE%20ASSEMBLY%20TransitiveClosure%20%3CBR%20%2F%3E%20FROM%20'D%3A%5C...%5CTransitiveClosure%5Cbin%5CRelease%5CTransitiveClosureAggregatorLibrary.dll'%3B%20%3CBR%20%2F%3E%20GO%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20CREATE%20SCHEMA%20TC%3B%20%3CBR%20%2F%3E%20GO%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20CREATE%20AGGREGATE%20TC.CLUSTERING(%40id1%20INT%2C%20%40id2%20INT)%20%3CBR%20%2F%3E%20RETURNS%20NVARCHAR(MAX)%20%3CBR%20%2F%3E%20EXTERNAL%20NAME%20TransitiveClosure.%5BTransitiveClosure.Aggregate%5D%3B%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%3CH2%20id%3D%22toc-hId-1680777251%22%20id%3D%22toc-hId-1732530174%22%3EClustering%20in%20action%3C%2FH2%3E%3CBR%20%2F%3E%20The%20simple%20code%20that%20performs%20clustering%20is%20shown%20in%20the%20following%20example%3A%20%3CBR%20%2F%3E%20declare%20%40edges%20table(n1%20int%2C%20n2%20int)%3B%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20insert%20into%20%40edges%20%3CBR%20%2F%3E%20values%20(1%2C2)%2C(2%2C3)%2C(3%2C4)%2C(4%2C5)%2C(2%2C21)%2C(2%2C22)%2C%20%3CBR%20%2F%3E%20(7%2C8)%2C(8%2C9)%2C(9%2C10)%3B%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20select%20TC.CLUSTERING(n1%2Cn2)%20%3CBR%20%2F%3E%20from%20%40edges%3B%20%3CBR%20%2F%3E%20TC.CLUSTERING%20aggregate%20performs%20clustering%20using%20transitive%20closure%20and%20returns%20groups%20formatted%20as%20JSON%20collection.%20To%20unpack%20these%20groups%2C%20we%20can%20use%20OPENJSON%20function%3A%20%3CBR%20%2F%3E%20declare%20%40edges%20table(n1%20int%2C%20n2%20int)%3B%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20insert%20into%20%40edges%20%3CBR%20%2F%3E%20values%20(1%2C2)%2C(2%2C3)%2C(3%2C4)%2C(4%2C5)%2C(2%2C21)%2C(2%2C22)%2C%20%3CBR%20%2F%3E%20(7%2C8)%2C(8%2C9)%2C(9%2C10)%3B%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20select%20TC.CLUSTERING(n1%2Cn2)%20%3CBR%20%2F%3E%20from%20%40edges%3B%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20select%20cluster%20%3D%20%5Bkey%5D%2C%20elements%20%3D%20value%20%3CBR%20%2F%3E%20from%20openjson(%20%3CBR%20%2F%3E%20(select%20TC.CLUSTERING(n1%2Cn2)%20from%20%40edges)%20%3CBR%20%2F%3E%20)%3B%20%3CBR%20%2F%3E%20%3CBR%20%2F%3E%20This%20code%20returns%20the%20following%20result%3A%20%3CBR%20%2F%3E%3CTABLE%3E%0A%20%20%20%3CTBODY%3E%3CTR%3E%0A%20%20%20%20%3CTD%3Ecluster%3C%2FTD%3E%0A%20%20%20%20%3CTD%3Eelements%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E0%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E%5B1%2C2%2C3%2C4%2C5%2C21%2C22%5D%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%20%3CTR%3E%0A%20%20%20%20%3CTD%3E1%3C%2FTD%3E%0A%20%20%20%20%3CTD%3E%5B7%2C8%2C9%2C10%5D%3C%2FTD%3E%0A%20%20%20%3C%2FTR%3E%0A%20%20%3C%2FTBODY%3E%3C%2FTABLE%3E%3CBR%20%2F%3E%20As%20you%20might%20see%2C%20with%20this%20CLR%20aggregate%20you%20can%20easily%20perform%20grouping%20using%20transitive%20closure%20algorithm.%3C%2FLINGO-BODY%3E%3CLINGO-TEASER%20id%3D%22lingo-teaser-385806%22%20slang%3D%22en-US%22%3EFirst%20published%20on%20MSDN%20on%20Dec%2020%2C%202017%20Transitive%20closure%26nbsp%3Bis%20a%20graph%20algorithm%20that%20tries%20to%20follow%20paths%20in%20graph%20edges%20and%20tries%20to%20find%20all%20elements%20that%20can%20be%20reached%20from%20some%20element%2C%20or%20groups%20of%20elements%20that%20are%20mutually%20reachable.%3C%2FLINGO-TEASER%3E%3CLINGO-LABS%20id%3D%22lingo-labs-385806%22%20slang%3D%22en-US%22%3E%3CLINGO-LABEL%3ESQLServerStorageEngine%3C%2FLINGO-LABEL%3E%3C%2FLINGO-LABS%3E
Version history
Last update:
‎Mar 23 2019 05:22 PM
Updated by: