{"id":71557,"date":"2017-07-05T13:42:50","date_gmt":"2017-07-05T13:42:50","guid":{"rendered":"https:\/\/www.simple-talk.com\/?p=71557"},"modified":"2026-03-09T14:52:26","modified_gmt":"2026-03-09T14:52:26","slug":"sql-graph-objects-sql-server-2017-good-bad","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/sql-graph-objects-sql-server-2017-good-bad\/","title":{"rendered":"SQL Graph Objects in SQL Server 2017: Pros, Cons &#038; Examples"},"content":{"rendered":"\n<p><em>Editor\u2019s note: This article was written against SQL Server 2017 CTP 2.1. SQL Server 2019 and later versions have added significant graph improvements including SHORTEST_PATH, edge constraints, and merge DML. For current coverage, see our <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/sql-server-graph-databases-part-1-introduction\/\" target=\"_blank\" rel=\"noopener\">5-part SQL Server Graph Databases series starting with Part 1: Introduction.<\/a><\/em><\/p>\n\n\n\n<p>SQL Server 2017 introduced graph database support through node tables (CREATE TABLE\u2026AS NODE) and edge tables (CREATE TABLE\u2026AS EDGE), with the MATCH clause for traversing relationships. This article builds a forum example comparing the relational and graph approaches side by side, evaluating both the strengths (simpler relationship queries, natural many-to-many modeling) and the limitations (no edge constraints, no DML support for MERGE, limited tooling) of the initial implementation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-introduction\">Introduction<\/h2>\n\n\n\n<p>SQL Server 2017 will be bringing us support for some of the functionality of graph databases. Graph databases are unlike relational databases in that they represent data as nodes, edges and properties within a \u2018graph\u2019, which is an abstract data type representing relationships or connections as a set of vertices nodes, points and edges, like a tangled fish-net. They allow us to represent and traverse relationships between entities in easier ways than is possible with the traditional relational database.<\/p>\n\n\n\n<p>Graph objects are built to represent complex relationships. A hierarchy is a special case of a graph and is useful to record such things as relationship between forum posts and their replies, likes in forum posts, and friendship between people. Hierarchies have a root node (forum post and replies, for example), but many graphs don\u2019t have a root node (people friendship, for example).<\/p>\n\n\n\n<p>In this article, we\u2019ll be building a forum example using the new graph model. I will also compare the complexity of the queries between the graph and a relational model and calling your attention for what&#8217;s still missing in this first version. Yes, it&#8217;s a Beta technology in its early stages.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-demonstration-environment\">Demonstration Environment<\/h2>\n\n\n\n<p>I\u2019m using SQL Server 2017 CTP 2.1 which you can download here: <a href=\"https:\/\/www.microsoft.com\/en-us\/sql-server\/sql-server-2017\">https:\/\/www.microsoft.com\/en-us\/sql-server\/sql-server-2017<\/a><\/p>\n\n\n\n<p>I\u2019m also using SSMS v. 17.0 which you can download here: <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/ssms\/download-sql-server-management-studio-ssms\">https:\/\/docs.microsoft.com\/en-us\/sql\/ssms\/download-sql-server-management-studio-ssms<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-creating-the-models\">Creating the models<\/h2>\n\n\n\n<p>This is the Entity-relationship model I will use for comparison:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"844\" height=\"621\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-63.png\" alt=\"\" class=\"wp-image-71558\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>If you would like to follow along and try out some comparisons, you can create this entity-relational model with the script below, or you can go directly to the graph model. However, you need to create the new database, <em>\u2018GraphExample\u2019<\/em>, using SSMS.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">create database GraphExample\n  go\n  -- Trying an entire graph model\n  use GraphExample\n  go\n  create schema Forum\n  go\n  create table Forum.ForumMembers\n  (MemberId int not null primary key Identity(1,1),\n  MemberName varchar(100))\n  go\n  create table Forum.ForumPosts\n  ([PostID] int not null primary key,\n  PostTitle varchar(100),\n  PostBody  varchar(100),\n  OwnerID int,\n  ReplyTo   int)\n  go\n  Create table Forum.Likes\n  (MemberId int,\n  PostId int)\n  go\n  create table Forum.LikeMember\n  (MemberId int,\n   LikedMemberId int)\n   go\n  INSERT Forum.ForumMembers values('Mike'),('Carl'),('Paul'),('Christy'),('Jennifer'),('Charlie')\n  go\n   \n  INSERT INTO [Forum].[ForumPosts] \n             (\n             [PostID]\n             ,[PostTitle]\n             ,[PostBody],OwnerID, ReplyTo\n                   )\n       VALUES\n           (4,'Geography','Im Christy from USA',4,null),\n             (1,'Intro','Hi There This is Carl',2,null)\n  INSERT INTO [Forum].[ForumPosts] \n             (\n             [PostID]\n             ,[PostTitle]\n             ,[PostBody],OwnerID, ReplyTo\n                   )\n       VALUES\n          (8,'Intro','nice to see all here!',1,1),\n          (7,'Intro','I''m Mike from Argentina',1,1),\n           (6,'Re:Geography','I''m Mike from Argentina',1,4),\n          (5,'Re:Geography','I''m Jennifer from Brazil',5,4),\n                (3,'Re: Intro','Hey Paul This is Christy',4,2),\n                   (2,'Intro','Hello I''m Paul',3,1)\n  go\n  INSERT Forum.Likes VALUES (1,4),\n  \t\t(2,7),\n  \t\t(2,8),\n  \t\t(2,2),\n  \t\t(4,5),\n  \t\t(4,6),\n  \t\t(1,2),\n  \t\t(3,7),\n  \t\t(3,8),\n  \t    (5,4)\n  go\n  Insert Forum.LikeMember VALUES (2,1),\n  \t(2,3),\n  \t(4,1),\n  \t(4,5)<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-graph-model\">The Graph Model<\/h3>\n\n\n\n<p>The planning of a graph model is quite different than the relational model. Tables in a graph model can be <strong>Edges<\/strong> or <strong>Nodes<\/strong>, we need to decide which tables will be edges and which tables will be nodes.<\/p>\n\n\n\n<p>This image illustrates our graph model:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"462\" height=\"577\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-64.png\" alt=\"\" class=\"wp-image-71559\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The decision between nodes and edges in our model is simple: All the entities in a logical model will be nodes, while all the relations in the logical model will be edges. In our model, we have <em>\u2018Posts\u2019 <\/em>and <em>\u2018Members\u2019<\/em> as entities, <em>\u2018Reply To\u2019<\/em>, <em>\u2018Like\u2019<\/em> and <em>\u2018Written By\u2019<\/em> as relations.<\/p>\n\n\n\n<p><strong>Read also:<\/strong> <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/sql-server-graph-databases-part-1-introduction\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server Graph Databases series<\/a><\/p>\n\n\n\n<div class=\"note\">\n<p><strong>Architectural Notes<\/strong><\/p>\n<p>Nodes and Edges are nothing more than tables with some special fields. There&#8217;s no restriction that forbids us to create regular relationships between these tables, turning the model into a mix of relational and graph model.<\/p>\n<p>For example, the relationship <em>\u2018Written By\u2019 <\/em>between <em>\u2018Posts\u2019<\/em> and <em>\u2018Members\u2019<\/em> is a simple one-to-many relationship. Instead of using an edge table, we could express it with a regular relation between these tables, creating a mixed model.<\/p>\n<p>When we create a node entity, the entity receives a calculated field named <em>&#8216;$node_id&#8217;<\/em>. We could use this field as a primary key, SQL Server allows calculated fields to be the primary key of a table: However this field is a JSON field, which is not a good option for primary key for many reasons. For this reason, our nodes have to maintain two keys: The business key, an integer, and the <em>\u2018$node_id\u2019 <\/em>key, an auto-generated JSON key that also contains an integer.<\/p>\n<\/div>\n\n\n\n<p>This will be the script for our node entities:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Use GraphExample\n  go\n  CREATE TABLE [dbo].[ForumMembers](\n         [MemberID] [int] IDENTITY(1,1) NOT NULL,\n         [MemberName] [varchar](100) NULL\n  )\n  AS NODE\n  GO\n   \n  CREATE TABLE [dbo].[ForumPosts](\n         [PostID] [int] NULL,\n         [PostTitle] [varchar](100) NULL,\n         [PostBody] [varchar](1000) NULL\n  )\n  AS NODE<\/pre>\n\n\n\n<div class=\"note\">\n<p><strong>Architectural Notes<\/strong><\/p>\n<p>After creating the objects, you can then examine the objects in Object Explorer. You may notice a new folder called \u2018<em>Graph\u2019<\/em> inside the \u2018<em>Tables\u2019<\/em> folder. All graph objects will be inside this folder.<\/p>\n<p>You may also notice the name of the auto-generated fields. Although we can reference these fields with their short-name, for example, <strong>$node_id<\/strong>, the real name of the field includes a GUID. This short name is a pseudo-column and we can use it in our queries.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" width=\"660\" height=\"445\" class=\"wp-image-71560\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-65.png\"><\/figure><p><strong><\/strong><\/p>\n<\/div>\n\n\n\n<p>It is simple to insert the values into the node tables: We just ignore the pseudo-column (<strong>$node_id<\/strong>) and write simple <strong>INSERT<\/strong> statements. These will be our INSERTs:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">INSERT ForumMembers values ('Mike'),('Carl'),('Paul'),('Christy'),('Jennifer'),('Charlie')\n   \n  INSERT INTO [dbo].[ForumPosts] \n             (\n             [PostID]\n             ,[PostTitle]\n             ,[PostBody]\n                   )\n       VALUES\n          (8,'Intro','nice to see all here!'),\n          (7,'Intro','I''m Mike from Argentina'),\n           (6,'Re:Geography','I''m Mike from Argentina'),\n          (5,'Re:Geography','I''m Jennifer from Brazil'),\n           (4,'Geography','Im Christy from USA'),\n                (3,'Re: Intro','Hey Paul This is Christy'),\n             (1,'Intro','Hi There This is Carl')\n                   (2,'Intro','Hello I''m Paul')<\/pre>\n\n\n\n<p>You will see the result of SELECTing the records from the <strong>ForumPosts<\/strong> table in the image below. You may notice the field <strong>$node_id<\/strong>, a JSON field including the type of the entity and an integer id that is automatically generated.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"817\" height=\"249\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-66.png\" alt=\"\" class=\"wp-image-71561\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-creating-the-edge-tables\">Creating the Edge tables<\/h3>\n\n\n\n<p>The edge tables are very simple to create. Edge tables can have properties that would be regular fields in the table, however that&#8217;s not part of our example. This will be the script to create our edge tables:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Create table dbo.[Written_By]\n  as EDGE\n   \n  CREATE TABLE [dbo].[Likes]\n  AS EDGE\n   \n  CREATE TABLE [dbo].[Reply_To]\n  AS EDGE<\/pre>\n\n\n\n<p>Each edge table has three pseudo-columns that we need to deal with:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><strong>$edge_id:<\/strong> The id of the edge record<\/li>\n\n\n\n<li><strong>$from_id:<\/strong> One of the nodes in the edge record<\/li>\n\n\n\n<li><strong>$to_id:<\/strong> The other node in the edge record<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Noticing this definition, you may be asking: &#8220;Oh, &#8216;One of the nodes&#8217; is quite vague, isn&#8217;t it?&#8221;. Yes, sure! That&#8217;s an important point: We need to define, in a logical way, what the <strong>$to_id<\/strong> and <strong>$from_id<\/strong> fields for each edge will mean. You may also be noticing that the name of the edge tables already defines the <strong>$to_id<\/strong> and <strong>$from_id<\/strong> sides. This is a logical choice of the sides and a good use of the names to make things easier for us.<\/p>\n\n\n\n<p>These will be our logical definitions:<\/p>\n\n\n\n<p><strong>Written_By:<\/strong><\/p>\n\n\n\n<p>$from_id will be the post<\/p>\n\n\n\n<p>$to_id will be the member<\/p>\n\n\n\n<p><strong>Likes:<\/strong><\/p>\n\n\n\n<p>$from_id will be who likes<\/p>\n\n\n\n<p>$to_id will be who\/what is liked<\/p>\n\n\n\n<p><strong>Reply_To:<\/strong><\/p>\n\n\n\n<p>$from_id&nbsp;will be the reply to the main post<\/p>\n\n\n\n<p>$to_id &nbsp;will be the main post<\/p>\n\n\n\n<p>There is no technical restriction about these choices, but we need to keep them when inserting new records, never mixing the meaning of each side of the relation.<\/p>\n\n\n\n<p><strong>Read also:<\/strong> <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/sql-server-closure-tables\/\" target=\"_blank\" rel=\"noreferrer noopener\">Closure tables as alternative hierarchy approach<\/a><\/p>\n\n\n\n<div class=\"note\">\n<p><strong>Architectural Notes<\/strong><\/p>\n<p>Besides the three pseudo-columns, all edge tables have some more fields, all of them are hidden fields.<\/p>\n<p>We can see the hidden definition in the field properties and these hidden fields will not appear in query results.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" width=\"668\" height=\"431\" class=\"wp-image-71562\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-67.png\"><\/figure><p><\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" width=\"375\" height=\"38\" class=\"wp-image-71563\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-68.png\"><\/figure><p><\/p>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-inserting-edge-records\">Inserting Edge records<\/h3>\n\n\n\n<p>The insert statement for the edge tables needs to fill both sides of the edge, <strong>$From_id<\/strong> and <strong>$To_id<\/strong>. These fields need to be filled with the <strong>$node_id<\/strong> value of the record of each side.<\/p>\n\n\n\n<p>For example, to relate a member with a forum post, the <em>\u2018Written_By\u2019<\/em> record will have the <strong>$node_id<\/strong> value of the post in the <strong>$From_id<\/strong> field and the <strong>$node_id<\/strong> value of the member in the <strong>$To_id<\/strong> field.<\/p>\n\n\n\n<p>These will be the inserts:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Insert into Written_By ($to_id,$from_id) values \n  \t(\n  \t\t(select $node_id from dbo.ForumMembers where MemberId= 1 ),\n  \t\t(select $node_id from dbo.ForumPosts where PostID=8 ) \n  \t),\n  \t(\n  \t\t(select $node_id from dbo.ForumMembers where MemberId=1  ),\n  \t\t(select $node_id from dbo.ForumPosts where PostID=7 ) \n  \t),\n  \t(\n  \t\t(select $node_id from dbo.ForumMembers where MemberId= 1 ),\n  \t\t(select $node_id from dbo.ForumPosts where PostID= 6) \n  \t),\n  \t(\n  \t\t(select $node_id from dbo.ForumMembers where MemberId=5  ),\n  \t\t(select $node_id from dbo.ForumPosts where PostID=5 ) \n  \t),\n  \t(\n  \t\t(select $node_id from dbo.ForumMembers where MemberId=4  ),\n  \t\t(select $node_id from dbo.ForumPosts where PostID=4 ) \n  \t),\n  \t(\n  \t\t(select $node_id from dbo.ForumMembers where MemberId=3  ),\n  \t\t(select $node_id from dbo.ForumPosts where PostID=3 ) \n  \t),\n  \t(\n  \t\t(select $node_id from dbo.ForumMembers where MemberId=3  ),\n  \t\t(select $node_id from dbo.ForumPosts where PostID=1 ) \n  \t),\n  \t(\n  \t\t(select $node_id from dbo.ForumMembers where MemberId=3  ),\n  \t\t(select $node_id from dbo.ForumPosts where PostID=2 ) \n  \t)<\/pre>\n\n\n\n<div class=\"note\">\n<p><strong>Architectural Notes<\/strong><\/p>\n<p>It&#8217;s complex to build INSERTs like these, isn\u2019t it? We will want to use an object framework with support for graph objects in the database. Today entity framework hasn&#8217;t got this feature, but we can expect to see something new about this in a while, either as a new feature for entity framework or as a new object model to map graph objects.<\/p>\n<\/div>\n\n\n\n<p>Let&#8217;s insert the replies records:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">INSERT Reply_To ($to_id,$from_id) \n  \tVALUES\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 4),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 6)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 1),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 7)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 1),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 8)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 1),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 2)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 4),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 5)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 2),<\/pre>\n\n\n\n<p>(SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 3))<\/p>\n\n\n\n<p>Finally, a let&#8217;s insert a lot of likes:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">INSERT Likes ($to_id,$from_id) \n  \tVALUES\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 4),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 1)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 7),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 2)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 8),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 2)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 2),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 2)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 5),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 4)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 6),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 4)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 2),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 1)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 7),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 3)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 8),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 3)),\n  \t((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 4),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 5))<\/pre>\n\n\n\n<p>The <em>\u2018Likes\u2019<\/em> edge illustrates the capabilities of the edge feature rather well. We just inserted several relations between members and posts, but we now decide that in the application a member can also like another member. No problem, we can use the same edge to relate one member to another. In the relational model we need two tables for this, in the graph model, only a single edge.<\/p>\n\n\n\n<p>Let&#8217;s insert more likes, now between members of the forum:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">INSERT Likes ($to_id,$from_id) \n  \tVALUES\n  \t((SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 1),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 2)),\n  \t((SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 3),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 2)),\n  \t((SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 1),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 4)),\n  \t((SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 5),\n         (SELECT $node_id FROM dbo.ForumMembers WHERE MemberID = 4))<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-good-building-queries-over-the-graph-model\">The Good: Building queries over the Graph Model<\/h2>\n\n\n\n<p>T-SQL brings some new syntax elements into use to query graph tables. We have a special statement to relate nodes and edges in a SELECT.<\/p>\n\n\n\n<p>Let&#8217;s do a walkthrough to build a query that retrieves all the posts and their replies:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>We will retrieve two posts in each record, the post and the reply, so we will need two references to the <em>\u2018ForumPosts\u2019<\/em> table in the FROM clause of the select. Let\u2019s do it with some meaningful aliases:<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">FROM dbo.ForumPosts ReplyPost, dbo.ForumPosts RepliedPost<\/pre><br><p>Although we can, of course, use any alias we choose, it is best to use meaningful aliases when working with graph objects.<\/p><br><\/li>\n\n\n\n<li>We need the relation between the posts, the relation is the table <em>\u2018Reply_to\u2019<\/em>.<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">FROM dbo.ForumPosts ReplyPost, dbo.Reply_to, dbo.ForumPosts RepliedPost<\/pre><br><\/li>\n\n\n\n<li>In the WHERE clause, we need to relate all the tables. We can do this with the new MATCH clause:<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">FROM dbo.ForumPosts ReplyPost, dbo.Reply_to, dbo.ForumPosts RepliedPost<br>WHERE MATCH(ReplyPost-(Reply_to)->RepliedPost)<\/pre><br><p>The syntax is interesting: A single &#8220;-&#8221; (dash) means a relation with the <strong>$From_id <\/strong>field of the edge, and a dash and greather-than (->) means a relation with the <strong>$To_id<\/strong> field of the edge<\/p><br><\/li>\n\n\n\n<li>Knowing which alias has the reply and which alias has the replied post, we can build the field list of the query.<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">-- Posts and their replies<br>  \tselect RepliedPost.PostId,RepliedPost.PostTitle,<br>  \t\tReplyPost.PostId as ReplyId, ReplyPost.PostTitle as ReplyTitle<br>  \tFrom dbo.ForumPosts ReplyPost, dbo.Reply_to, dbo.ForumPosts RepliedPost<br>  \tWhere Match(ReplyPost-(Reply_to)->RepliedPost)<\/pre><br><p>The same query in the relational model would be like this:<\/p><br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">select RepliedPost.PostId,RepliedPost.PostTitle,<br>  \t\tReplyPost.PostId as ReplyId, ReplyPost.PostTitle as ReplyTitle<br>  from Forum.ForumPosts ReplyPost, Forum.ForumPosts RepliedPost<br>  where ReplyPost.PostId=RepliedPost.ReplyTo<\/pre><br><p>These queries are very similar, although it&#8217;s possible to argue that the MATCH syntax is easier to understand<\/p><br><\/li>\n\n\n\n<li>We can already execute this query. The result will be like the image below:<br><figure><img loading=\"lazy\" decoding=\"async\" width=\"365\" height=\"202\" class=\"wp-image-71564\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-69.png\"><\/figure><br><\/li>\n\n\n\n<li>Let&#8217;s include the name of the member who wrote the replied post. We need to include the <em>\u2018ForumMembers\u2019<\/em> node and the <em>\u2018Written_By\u2019<\/em> edge in the FROM clause. The new FROM clause will be like this:<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">FROM dbo.ForumPosts ReplyPost, dbo.Reply_to, dbo.ForumPosts RepliedPost, dbo.ForumMembers RepliedMember, Written_By RepliedWritten_By<\/pre><br><\/li>\n\n\n\n<li>Include the relation inside the MATCH clause:<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">WHERE MATCH(ReplyPost-(Reply_to)->RepliedPost-(RepliedWritten_by)->RepliedMember)<\/pre><br><\/li>\n\n\n\n<li>Include the member name of the replied post in the field list of the SELECT. The final query will be this:<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">-- Posts and members and their replies<br>  \tSELECT RepliedPost.PostId,RepliedPost.PostTitle,RepliedMember.MemberName,<br>  \t\tReplyPost.PostId as ReplyId, ReplyPost.PostTitle as ReplyTitle<br>      FROM dbo.ForumPosts ReplyPost, dbo.Reply_to, dbo.ForumPosts RepliedPost, <br>  \t     dbo.ForumMembers RepliedMember, Written_By RepliedWritten_By<br>  \tWHERE MATCH(ReplyPost-(Reply_to)->RepliedPost-(RepliedWritten_by)->RepliedMember)<\/pre><br><p>The same query against the relational model:<\/p><br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT RepliedPost.PostId,RepliedPost.PostTitle,<br>  \t\tReplyPost.PostId as ReplyId, ReplyPost.PostTitle as ReplyTitle, <br>  \t\tRepliedMember.MemberName<br>  FROM Forum.ForumPosts ReplyPost, Forum.ForumPosts RepliedPost, <br>  \t Forum.ForumMembers RepliedMember<br>  WHERE ReplyPost.PostId=RepliedPost.ReplyTo<br>        and RepliedPost.OwnerId=RepliedMember.MemberId<\/pre><br><p>The image below shows the result:<\/p><br><figure><img loading=\"lazy\" decoding=\"async\" width=\"465\" height=\"202\" class=\"wp-image-71565\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-70.png\"><\/figure><br><\/li>\n\n\n\n<li>We still need to include the member name of the reply post. Again, we need to include the <em>\u2018ForumMembers\u2019<\/em> and the <em>\u2018Written_By\u2019<\/em> tables in the FROM clause:<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">From dbo.ForumPosts ReplyPost, dbo.Reply_to, dbo.ForumPosts RepliedPost,<br>dbo.ForumMembers RepliedMember, Written_By RepliedWritten_By,<br>dbo.ForumMembers ReplyMember, Written_By ReplyWritten_By<\/pre><br><\/li>\n\n\n\n<li>The next step is correcting the MATCH clause. <em>\u2018ReplyMember\u2019<\/em> needs to be related to <em>\u2018ReplyPost\u2019<\/em>, however, how to do this relation without breaking the others? Let&#8217;s do in a different direction:<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">WHERE MATCH(ReplyMember&lt;-(ReplyWritten_By)-ReplyPost-(Reply_to)->RepliedPost-(RepliedWritten_by)->RepliedMember)<\/pre><br><p>Notice the <strong>&lt;-<\/strong> symbol. It&#8217;s in opposite direction than <strong>-><\/strong>, however it&#8217;s the same meaning: a relation between <strong>$to_id<\/strong> of the edge table with the node table.<\/p><br><\/li>\n\n\n\n<li>Finally, let&#8217;s include the name of the member who wrote the reply in the field list and execute the query:<br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">-- Posts and members and their replies and members<br>  \tSELECT RepliedPost.PostId, RepliedPost.PostTitle,RepliedMember.MemberName,<br>  \t\tReplyPost.PostId as ReplyId, ReplyPost.PostTitle as ReplyTitle,<br>  \t\tReplyMember.MemberName [ReplyMemberName]<br>      FROM dbo.ForumPosts ReplyPost, dbo.Reply_to, dbo.ForumPosts RepliedPost, <br>  \t     dbo.ForumMembers RepliedMember, Written_By RepliedWritten_By, <br>  \t     dbo.ForumMembers ReplyMember, Written_By ReplyWritten_By<br>      WHERE MATCH(ReplyMember&lt;-(ReplyWritten_By)-ReplyPost-(Reply_to)->RepliedPost-(RepliedWritten_by)->RepliedMember)<\/pre><br><p>The result is the following:<\/p><br><figure><img loading=\"lazy\" decoding=\"async\" width=\"602\" height=\"203\" class=\"wp-image-71566\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-71.png\"><\/figure><br><p>The same query against the relational model:<\/p><br><pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">SELECT RepliedPost.PostId,RepliedPost.PostTitle, <br>         RepliedMember.MemberName, ReplyPost.PostId as ReplyId, <br>  \t   ReplyPost.PostTitle as ReplyTitle, ReplyMember.MemberName<br>  FROM Forum.ForumPosts ReplyPost, Forum.ForumPosts RepliedPost, <br>       Forum.ForumMembers RepliedMember, Forum.ForumMembers ReplyMember<br>  WHERE ReplyPost.PostId=RepliedPost.ReplyTo<br>        and RepliedPost.OwnerId=RepliedMember.MemberId<br>        and ReplyPost.OwnerId=ReplyMember.MemberId<\/pre><br><p>At this point you may be noticing that according to the increasing of the number of relations the WHERE clause in the relational model will be much harder to read than the MATCH clause in the graph model.<\/p><br><p>Let&#8217;s see some more interesting and useful queries against the graph model.<\/p><br><\/li>\n<\/ol>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-count-of-replies-of-each-post\">Count of Replies of each Post<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Count of replies of each post\n  \tSELECT distinct RepliedPost.PostID,RepliedPost.PostTitle,\n  \t       RepliedPost.PostBody,\n  \t       count(ReplyPost.PostID) over(partition by RepliedPost.PostID) \n  \t\t           as TotalReplies\n  \tFROM dbo.ForumPosts ReplyPost, Reply_To, dbo.ForumPosts RepliedPost\n  \tWHERE MATCH(ReplyPost-(Reply_To)-&gt;RepliedPost)<\/pre>\n\n\n\n<p>In this one we have the count of replies in each post, however only in a single level, not in a tree of replies.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-list-of-root-posts\">List of Root Posts<\/h3>\n\n\n\n<p>We can get all the root posts without the MATCH syntax:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- All the root posts\n  SELECT Post1.PostId,Post1.PostTitle\n  FROM dbo.ForumPosts Post1\n  WHERE $node_id not in (select $from_id from dbo.Reply_To)<\/pre>\n\n\n\n<p>The MATCH syntax only allows us to relate three or more entities (two nodes and one relation). When we want to relate only two of them, we can do a regular join or sub-query, as the above.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-including-a-level-field-in-the-result\">Including a Level field in the result<\/h3>\n\n\n\n<p>It can be useful to include a <em>\u2018Level\u2019<\/em> field to expose the structure as a tree. We have a syntax for this in T-SQL: The recursive CTE (Common Table Expression).<\/p>\n\n\n\n<p>However, there is a catch: We can&#8217;t use the <strong>MATCH<\/strong> syntax over a derived table, in this case the CTE. We can, if needed, use the <strong>MATCH<\/strong> syntax in the CTE, but we can&#8217;t reference the CTE in it. This is a limitation.<\/p>\n\n\n\n<p>We can, however, use the recursive CTE syntax without be <strong>MATCH<\/strong> syntax, using regular relations. The result will be this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- All replies with tree level\n  with root as \n  ( select $node_id as node_id,RootPosts.PostId,\n           RootPosts.PostTitle,\n           1 as Level, 0 as ReplyTo\n     from dbo.ForumPosts RootPosts\n     where $node_id not in (select $from_id from dbo.reply_to) \n  union all\n     select $node_id,ReplyPost.PostId, ReplyPost.PostTitle,\n            Level+1 as [Level], root.PostId as ReplyTo\n     from dbo.ForumPosts ReplyPost, reply_to, root\n     where ReplyPost.$node_id=reply_to.$from_id \n           and root.node_id=reply_to.$to_id\n  )\n  select PostId,PostTitle, Level, ReplyTo\n  from root<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-retrieve-all-replies-of-a-single-post\">Retrieve all replies of a single post<\/h3>\n\n\n\n<p>Using the recursive CTE syntax, we can retrieve all the replies of a single post in a tree style. If we try to retrieve all replies of post 1 using a regular syntax we won&#8217;t retrieve the post 3 and we should, because post 3 is a reply to post 2, which is a reply to post 1.<\/p>\n\n\n\n<p>We can only retrieve post 3 when querying the replies of post 1 using the recursive CTE syntax.<\/p>\n\n\n\n<p>This will be the query to retrieve all replies of a single post:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- All replies of a single post\n  with root as \n  ( select $node_id as node_id,RootPosts.PostId,RootPosts.PostTitle,\n           1 as Level, 0 as ReplyTo\n     from dbo.ForumPosts RootPosts\n     where PostId=1  \n  union all\n     select $node_id,ReplyPost.PostId, ReplyPost.PostTitle,\n            Level+1 as [Level],root.PostId as ReplyTo\n     from dbo.ForumPosts ReplyPost, reply_to, root\n     where ReplyPost.$node_id=reply_to.$from_id \n           and root.node_id=reply_to.$to_id\n  )\n  select PostId,PostTitle, Level, ReplyTo\n  from root<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"304\" height=\"180\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-72.png\" alt=\"\" class=\"wp-image-71567\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>We can also do it in reverse, in order to retrieve all parent posts of a single post in a tree structure. For this we need to do the relations inside the CTE to retrieve the posts and at the outside do some OUTER JOINs to retrieve the parent of each post. It needs to be an OUTER JOIN because the main post has no parent. It also needs to be in the outside of the CTE because the recursive part of the CTE doesn&#8217;t support OUTER JOINs.<\/p>\n\n\n\n<p>This brings another point to our attention: There isn&#8217;t an OUTER in the <strong>MATCH<\/strong> syntax.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- All the parents of a single post, with reference to their parents\n  with root as \n  ( select LeafPost.$node_id as node_id,LeafPost.PostId,\n           LeafPost.PostTitle\n     from dbo.ForumPosts LeafPost\n     where LeafPost.PostId=3  -- Single post\n  union all\n     select RepliedPost.$node_id as node_id,RepliedPost.PostId, \n            RepliedPost.PostTitle\n     from dbo.ForumPosts RepliedPost, Reply_to, root\n     where root.node_id=Reply_to.$from_id \n           and Reply_to.$to_id=RepliedPost.$node_id\n  )\n  select root.PostId,root.PostTitle,\n         RepliedPost.PostId ParentPostId\n  from root\n  left join reply_to \n       on root.node_id=reply_to.$from_id\n  left join dbo.ForumPosts RepliedPost \n       on reply_to.$to_id=RepliedPost.$node_id<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"280\" height=\"130\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-73.png\" alt=\"\" class=\"wp-image-71568\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-recovering-the-posts-of-a-single-user\">Recovering the posts of a single user<\/h3>\n\n\n\n<p>Recovering the information of a single user, instead of a post, is easier because there isn\u2019t a tree in this situation:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- All Post replied by Peter\n  \tSELECT distinct RepliedPost.PostID,RepliedPost.PostTitle,\n  \t        RepliedPost.PostBody\n  \tFROM dbo.ForumPosts ReplyPost, Reply_To, dbo.ForumPosts RepliedPost,\n  \t     dbo.ForumMembers Members,Written_By\n  \tWHERE MATCH(Members&lt;-(Written_By)-ReplyPost-(Reply_To)-&gt;RepliedPost)\n  \tand Members.MemberName='Peter'\n  -- All replies made by Peter\n  \tSELECT ReplyPost.PostID,ReplyPost.PostTitle,ReplyPost.PostBody, \n  \t       RepliedPost.PostId ReplyTo\n  \tFROM dbo.ForumPosts ReplyPost, Reply_To, dbo.ForumPosts RepliedPost,\n  \t     dbo.ForumMembers Members,Written_By\n  \tWHERE MATCH(Members&lt;-(Written_By)-ReplyPost-(Reply_To)-&gt;RepliedPost)\n  \tand Members.MemberName='Peter'<\/pre>\n\n\n\n<p>You may notice the only difference between the queries above is the field list and the use of DISTINCT. The DISTINCT is needed because Peter can reply the same post more than once.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-retrieving-the-likes-from-the-model\">Retrieving the Likes from the model<\/h3>\n\n\n\n<p>The queries to retrieve the <em>\u2018Likes\u2019<\/em> are interesting: The <em>&#8216;Likes&#8217;<\/em> edge has relations between members and between members and posts. Each of these selects brings only one of these kinds of relation and ignores the other.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- All posts that have likes and who liked them\n  SELECT Post.PostID,Post.PostTitle,Member.MemberName\n  FROM   dbo.ForumPosts Post, Likes, \n         dbo.ForumMembers Member\n  WHERE MATCH(Member-(Likes)-&gt;Post)\n  -- All members that have likes and who liked them\n  SELECT Member.MemberId,Member.MemberName LikeMember,\n         LikedMember.MemberName LikedMemberName\n  FROM dbo.ForumMembers Member, Likes, dbo.ForumMembers LikedMember\n  WHERE MATCH(Member-(Likes)-&gt;LikedMember)<\/pre>\n\n\n\n<p>It is also easy to aggregate the information to get the total of likes of each post or each member.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Total likes of each Post\n  select Post.PostId,Post.PostTitle,\n         count(*) totalLikes\n  from dbo.ForumPosts Post,Likes,\n       dbo.ForumMembers Members\n  where Match(Members-(Likes)-&gt;Post)\n  group by PostId,PostTitle\n  -- Total likes of each Member\n  select LikedMembers.MemberId,LikedMembers.MemberName,\n         count(*) totalLikes\n  from dbo.ForumMembers Members,Likes,\n       dbo.ForumMembers LikedMembers\n  where Match(Members-(Likes)-&gt;LikedMembers)\n  group by LikedMembers.MemberId,\n           LikedMembers.MemberName<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-members-who-liked-and-replied-to-the-same-post\">Members who liked and replied to the same post<\/h3>\n\n\n\n<p>We can build even some more interesting queries. For example, who are the members who liked and replied to a post?<\/p>\n\n\n\n<p>This is the query:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Members who liked and replied on same post\n  SELECT Member.MemberName,Member.Memberid,\n         LikedPost.PostId,LikedPost.PostTitle,\n         ReplyPost.PostTitle ReplyTitle\n  FROM dbo.ForumPosts LikedPost, Reply_To, dbo.ForumPosts ReplyPost, \n       Likes, dbo.ForumMembers Member, Written_By\n  WHERE MATCH(Member-(Likes)-&gt;LikedPost&lt;-(Reply_To)-ReplyPost-(Written_By)-&gt;Member)<\/pre>\n\n\n\n<p>You may notice the use of the node <em>&#8216;Member&#8217;<\/em> two times in the same <strong>MATCH<\/strong> expression. This creates a kind of a filter: The member who liked the <em>&#8216;LikedPost&#8217;<\/em> needs to be the same member who wrote the <em>\u2018ReplyPost\u2019<\/em>.<\/p>\n\n\n\n<p>This one, over the relational model, would be like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">select Likes.MemberId,Members.MemberName\n  from Forum.Likes Likes, Forum.ForumPosts Posts, \n       Forum.ForumMembers Members \n  where Likes.MemberId=Posts.OwnerId \n  \t\tand Posts.ReplyTo=Likes.PostId\n  \t\tand Members.MemberId=Likes.MemberId<\/pre>\n\n\n\n<p>It seems for me more difficult to build and read over the relational model.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-members-who-replied-to-multiple-posts\">Members who replied to multiple posts<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT Members.MemberId, Members.MemberName, \n         Count(distinct RepliedPost.PostId) as Total\n  FROM   dbo.ForumPosts ReplyPost, Reply_To, dbo.ForumPosts RepliedPost,\n         Written_By,dbo.ForumMembers Members\n  WHERE  MATCH(Members&lt;-(Written_By)-ReplyPost-(Reply_To)-&gt;RepliedPost)\n  GROUP BY MemberId, Members.MemberName\n  Having Count(RepliedPost.PostId) &gt;1<\/pre>\n\n\n\n<p>Members who replied multiple times to the same post:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT Members.MemberId, Members.MemberName,\n         RepliedPost.PostId RepliedId,count(*) as TotalReplies\n  FROM   dbo.ForumPosts ReplyPost, Reply_To, dbo.ForumPosts RepliedPost,\n       Written_By,dbo.ForumMembers Members\n  WHERE MATCH(Members&lt;-(Written_By)-ReplyPost-(Reply_To)-&gt;RepliedPost)\n  GROUP BY MemberId,MemberName,RepliedPost.PostId\n  Having count(*) &gt;1<\/pre>\n\n\n\n<p>The only differences between the two above are the field list and aggregation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-missing-features-for-queries\">Missing features for queries<\/h3>\n\n\n\n<p>You probably don&#8217;t like the requirement to use recursive CTE to retrieve a tree of nodes. That&#8217;s because a feature called transitive closure, usually present in graph databases, isn&#8217;t available in this first version.<\/p>\n\n\n\n<p>The ability to find any type of node connected to a single node, for example, all replies and likes of a post, is called polymorphism and isn&#8217;t available in this version. We can use <strong>UNION<\/strong> to solve this for small sets of node types.<\/p>\n\n\n\n<p>Some graph-specific functions, such as the ability to find the &#8216;shortest path&#8217; between two forum members, isn&#8217;t available in this version.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-bad-edge-records-validation\">The Bad: Edge records validation<\/h2>\n\n\n\n<p>As well as all the missing features, with a lot of room for improvement, I found problems with the validation of the relations inserted in the model. You may have noticed that it&#8217;s too easy to create an edge table. That&#8217;s because we don\u2019t need to identify the possible relations of that edge.<\/p>\n\n\n\n<p>This creates some validation problems. For example, the <em>\u2018Reply_To\u2019<\/em> edge: A forum message can be a reply to another single forum message, however, I shouldn&#8217;t be able to insert the same forum message as a reply of two others.<\/p>\n\n\n\n<p>The model doesn&#8217;t validate this. We shouldn&#8217;t be able to execute the insert below because the message &#8216;3&#8217; is already a reply to message &#8216;2&#8217;. However, the insert will work:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Insert Reply_to Values\n  ((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 3),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 4))<\/pre>\n\n\n\n<p>After trying the insert, delete the inserted record:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">delete reply_to\n  from reply_to, dbo.forumposts replypost,\n       dbo.forumposts repliedpost\n  where MATCH(replypost-(reply_to)-&gt;repliedpost)\n        and replypost.postid=3 \n        and repliedpost.postid=4<\/pre>\n\n\n\n<p>How can we solve this problem?<\/p>\n\n\n\n<p>The graph calculated field doesn&#8217;t support constraints. The only solution for this problem is the creation of triggers over our edge tables. In this example, we will need a trigger for INSERT and UPDATE.<\/p>\n\n\n\n<p>The following trigger will solve the problem:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">create trigger chkReplies on dbo.Reply_to\n  for insert,update\n  as\n  begin\n      if exists( select 1 from inserted \n  \t\t\t\twhere \n  \t\t\t\t(SELECT count(*)\n  \t\t\t\tFROM dbo.ForumPosts ReplyPost, Reply_To,\n                                  dbo.ForumPosts RepliedPost\n  \t\t\t\tWHERE MATCH(ReplyPost-(Reply_To)-&gt;RepliedPost)\n  \t\t\t\tand ReplyPost.$node_id=inserted.$from_id)\n  \t\t\t    &gt;1 )\n  \tbegin\n  \t    raiserror('One message can only be reply to another single message',13,1)\n  \t\trollback transaction\n  \tend\n  end<\/pre>\n\n\n\n<p>Try the INSERT again and this time you will the error message:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Insert Reply_to Values\n  ((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 3),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 4))<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"762\" height=\"113\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-74.png\" alt=\"\" class=\"wp-image-71569\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-deleting-parent-records-with-children\">Deleting Parent records with Children<\/h3>\n\n\n\n<p>This is another interesting problem. The relation isn&#8217;t enforced, so we can delete parent records even when they have children. You can try this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Begin Transaction\n  -- This delete works, unfortunately.\n  Delete dbo.forumposts where postid=1\n  -- Posts and their replies\n  \tselect RepliedPost.PostId,RepliedPost.PostTitle,\n  \t\tReplyPost.PostId as ReplyId, ReplyPost.PostTitle as ReplyTitle\n  \tFrom dbo.ForumPosts ReplyPost, dbo.Reply_to, dbo.ForumPosts RepliedPost\n  \tWhere Match(ReplyPost-(Reply_to)-&gt;RepliedPost)<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"369\" height=\"207\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-75.png\" alt=\"\" class=\"wp-image-71570\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You will notice that the result in the SELECT will have several NULL values because of the deleted record. We can&#8217;t even insert the record again, because the relation is done by the <strong>$node_id<\/strong> value, which is auto-generated.<\/p>\n\n\n\n<p>Undo the change with a rollback:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Rollback transaction<\/pre>\n\n\n\n<p>We can&#8217;t solve this with a simple relationship, such as foreign key constraint, first because the graph pseudo-columns don&#8217;t support constraints and also because this would be an impossible solution for and edge such as <em>&#8216;Likes&#8217;<\/em>, which accepts multiple types of nodes.<\/p>\n\n\n\n<p>Again, the solution is a trigger:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">create trigger chkNotOrphanReply on dbo.ForumPosts\n  for delete\n  as\n     if exists(select 1 from deleted\n  \t\twhere\n  \t\t\t(SELECT count(*)\n  \t\t\t\tFROM Reply_To\n  \t\t\t\tWHERE $to_id=deleted.$node_id)\n  \t\t\t    &gt;0 \n  \t\t\t)\n  \tbegin\n  \t\traiserror('This message can''t be deleted because it has replies',13,1)\n  \t\trollback transaction\n  \tend<\/pre>\n\n\n\n<p>After creating the trigger, execute this block of code again and you will see the error message:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Begin Transaction\n  -- This delete works, unfortunatelly.\n  Delete dbo.forumposts where postid=1\n  -- Posts and their replies\n  \tselect RepliedPost.PostId,RepliedPost.PostTitle,\n  \t\t   ReplyPost.PostId as ReplyId, \n  \t\t   ReplyPost.PostTitle as ReplyTitle\n  \tFrom dbo.ForumPosts ReplyPost, dbo.Reply_to, \n  \t     dbo.ForumPosts RepliedPost\n  \tWhere Match(ReplyPost-(Reply_to)-&gt;RepliedPost)<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"838\" height=\"129\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-76.png\" alt=\"\" class=\"wp-image-71571\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-one-forum-post-shouldn-t-like-another-forum-post\">One Forum post shouldn\u2019t like another forum post<\/h3>\n\n\n\n<p>The <em>&#8216;Likes&#8217;<\/em> edge is much more flexible, however has also its problems. We shouldn&#8217;t be able to insert a like between two forum posts, but the model will accept it. The following insert will work:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">--This insert should not be accepted, but it is\n  INSERT Likes ($to_id,$from_id) VALUES\n  ((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 8),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 7))<\/pre>\n\n\n\n<p>After trying the insert above, delete the inserted record and any like between posts:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">-- Deleting\n  delete likes \n  from likes, dbo.forumposts post1,\n       dbo.forumposts post2\n  where MATCH(Post1-(likes)-&gt;Post2)<\/pre>\n\n\n\n<p>Again, the solution will be a trigger. However, this time we need to check the type of record inserted in the relation. We can use the function <strong>JSON_VALUE<\/strong> to extract this information. The <strong>JSON_VALUE<\/strong> function can extract information from any <strong>JSON<\/strong> field, including the calculated graph fields.<\/p>\n\n\n\n<p>For example, the following SELECT will extract the type of each record inserted in the <strong>$from_id<\/strong> field part of the like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">select json_value($from_id, '$.schema') as [schema],\n         json_value($from_id, '$.table') as [table] \n   from likes<\/pre>\n\n\n\n<p>We can use this function in a trigger for insert\/update. The trigger will be this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Create trigger chkLikeType on Likes\n  for Insert,Update\n  as\n  if exists (select 1 from inserted\n  \t\t\twhere json_value($from_id, '$.schema')&lt;&gt;'dbo' or\n  \t\t\t\t  json_value($from_id, '$.table')&lt;&gt;'ForumMembers')\n  \tbegin\n  \t\traiserror('Only forum members can like a post or another member',13,1)\n  \t\trollback transaction\n  \tend<\/pre>\n\n\n\n<p>After creating the trigger, you can try the insert again, it will not be accepted:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">--This insert should not be accepted, but it is\n  INSERT Likes ($to_id,$from_id) VALUES\n  ((SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 8),\n         (SELECT $node_id FROM dbo.ForumPosts WHERE PostID = 7))<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"110\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/06\/word-image-77.png\" alt=\"\" class=\"wp-image-71572\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-missing-validation-features\">Missing Validation Features<\/h3>\n\n\n\n<p>All these problems should be solved with some kind of validation syntax on the creation of edge nodes. We should be able to constrain the types of entities that are allowed in the edge node and whether we will allow more than one record with the same entity.<\/p>\n\n\n\n<p>This syntax would avoid the need for triggers. The support for a range of constraints would make the system much more robust.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion<\/h2>\n\n\n\n<p>It&#8217;s a first version, SQL Graph in SQL Server 2017 is very promising, even though there are a lot of missing features at this stage. There is plenty to do before this becomes usable and robust, but there is enough so far to be hopeful that Microsoft can deliver a fully-functional graph database within SQL Server<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>At the moment, there is little or any information about how soon we can expect to see the tools that a development is likely to need. We need an object model with support for this, Entity Framework doesn&#8217;t support these kind of database objects and I&#8217;m not sure if the best option really is to join relational and graph models in the same object model.<\/li>\n\n\n\n<li>We need a tool to design this model. Besides the needed support for validation in the syntax of edge nodes, a tool to design this, such as database diagram is for relational models, will be very useful.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>After reading this article, probably would like to check:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The documentation and architecture of this model. This <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/graphs\/sql-graph-overview\">link<\/a> will be useful<\/li>\n\n\n\n<li>More examples. This <a href=\"https:\/\/blogs.msdn.microsoft.com\/sqlcat\/2017\/04\/21\/build-a-recommendation-system-with-the-support-for-graph-data-in-sql-server-2017-and-azure-sql-db\/\">link<\/a> will be useful, it\u2019s a bigger sample and the only link I found talking also about the performance of the graph model<\/li>\n\n\n\n<li>More references and other <a href=\"http:\/\/sqlblog.com\/blogs\/john_paul_cook\/archive\/2017\/06\/17\/sql-server-2017-graph-database-references.aspx\">links<\/a>. This summary of several references will be useful link.<\/li>\n<\/ul>\n<\/div>\n\n\n<section id=\"my-first-block-block_4062b2df9dea6cfeb4a46ecd08e621c8\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Fast, reliable and consistent SQL Server development&#8230;<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            &#8230;with SQL Toolbelt Essentials. 10 ingeniously simple tools for accelerating development, reducing risk, and standardizing workflows.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/products\/sql-toolbelt-essentials\/\" class=\"btn btn--secondary btn--lg\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Graph databases in SQL Server<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What are the pros and cons of SQL Server graph databases?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Pros: MATCH clause simplifies complex relationship queries, natural modeling for social networks and recommendations, integrates with existing SQL Server tools and T-SQL. Cons (as of 2017, many addressed in later versions): no edge constraints (added in 2019), no MERGE for graph DML, limited visualization tooling, MATCH requires explicit hop counts. SQL Server 2019+ addressed several of these with SHORTEST_PATH, edge constraints, and derived table support in MATCH.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Evaluate SQL Server 2017 graph objects: node tables, edge tables, MATCH queries, with a relational vs graph model comparison. Covers early limitations and practical forum example.&hellip;<\/p>\n","protected":false},"author":50808,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[5134],"coauthors":[6810],"class_list":["post-71557","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-sql-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/71557","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/50808"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=71557"}],"version-history":[{"count":9,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/71557\/revisions"}],"predecessor-version":[{"id":109086,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/71557\/revisions\/109086"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=71557"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=71557"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=71557"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=71557"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}