{"id":89858,"date":"2021-02-09T00:48:44","date_gmt":"2021-02-09T00:48:44","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=89858"},"modified":"2026-03-09T12:22:17","modified_gmt":"2026-03-09T12:22:17","slug":"visualizing-a-set-of-sql-server-graph-tables-by-generating-tgf-format","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/blogs\/visualizing-a-set-of-sql-server-graph-tables-by-generating-tgf-format\/","title":{"rendered":"Visualize SQL Server Graph Tables: Generate TGF Format"},"content":{"rendered":"\n<p>This article provides a reusable <a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/\" target=\"_blank\" rel=\"noopener\">PowerShell<\/a> and dynamic SQL solution for visualizing <a href=\"https:\/\/www.red-gate.com\/simple-talk\/tag\/sql-server\/\" target=\"_blank\" rel=\"noopener\">SQL Server<\/a> graph tables. It exports node and edge data to Trivial Graph Format (TGF), a simple text-based format that graph visualization tools like yEd can render as interactive diagrams. The code uses the JSON metadata stored in SQL Server\u2019s $node_id, $from_id, and $to_id columns to map relationships dynamically &#8211; you specify which node and edge tables to include, and the tool generates the graph. This works with any SQL Server 2017+ graph database.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-introduction\">Introduction<\/h2>\n\n\n\n<p>In my <a href=\"\\Dropbox\\Writing\\SimpleTalk\\Blog\\using-a-modeling-tool-to-draw-and-import-a-graph-into-sql-server\">last blog<\/a>, I built some PowerShell to take a graph that I had modelled and bring that into SQL Server in a format that could be imported into SQL Server node and edge tables. I have started on a tool to take that imported data and create a set of graph tables to test with, but to test that process, I found it kind of important to be able to visualize a graph to make sure it worked!<\/p>\n\n\n\n<p><a href=\"https:\/\/deep.data.blog\/2017\/11\/03\/how-we-did-it-pass-2017-summit-session-similarity-using-sql-graph-and-python\/\" target=\"_blank\" rel=\"noopener\">I considered using a rich format like Arvind Shyamsundar did in his blog <\/a>by outputting GraphML, but that was kind of overkill since every time I import a set of nodes, I will have to reformat anyhow. It turned out to be much easier to build generic code using TGF (Trivial Graph Format) just like in my export example.<\/p>\n\n\n\n<p>Another goal of mine is to make this tool to work with any graph in SQL Server (I am currently building an some code to do just that to categorize geographies, so this is a double win) so I used dynamic SQL as the basis, so I could pick and choose the node and edge tables, plus dynamically set a column for the nodes to display. (A future enhancement will be to include a filter on the node value so you can just see how a few nodes are related.)<\/p>\n\n\n\n<p>I did some searching for some sample graph data in SQL Server objects, and I found <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/graphs\/sql-graph-sample?view=sql-server-ver15\">this example on the docs.microsoft.com site<\/a>. It contains a complete database with 3 nodes and 3 edges that gave me something to output. This code should work on 2017 or 2019 equally well.<\/p>\n\n\n\n<p>It is a quite a bit of code, none of it super interesting for including directly in the blog, except the base of the code using the JSON metadata values that is a part of the structure of the SQL Server objects. Since an edge can connect any number of nodes, you use these values to know what you can join to and from:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT  JSON_VALUE(CAST($node_id AS nvarchar(1000)),'$.schema') AS NodeSchema, \n\t\tJSON_VALUE(CAST($node_id AS nvarchar(1000)),'$.table') AS NodeTable, \n\t\tJSON_VALUE(CAST($node_id AS nvarchar(1000)),'$.id') AS NodeId\nFROM   dbo.Person; --A node\n\nSELECT \n\t\tJSON_VALUE(CAST($edge_id AS nvarchar(1000)),'$.schema') AS EdgeSchema, \n\t\tJSON_VALUE(CAST($edge_id AS nvarchar(1000)),'$.table') AS EdgeTable, \n\t\tJSON_VALUE(CAST($edge_id AS nvarchar(1000)),'$.id') AS EdgeId, \n\t\t\n\t\tJSON_VALUE(CAST($from_id AS nvarchar(1000)),'$.schema') AS FromNodeSchema, \n\t\tJSON_VALUE(CAST($from_id AS nvarchar(1000)),'$.table') AS FromNodeTable, \n\t\tJSON_VALUE(CAST($from_id AS nvarchar(1000)),'$.id') AS FromNodeId, \n\t\tJSON_VALUE(CAST($To_id AS nvarchar(1000)),'$.schema') AS ToNodeSchema, \n\t\tJSON_VALUE(CAST($to_id AS nvarchar(1000)),'$.table') AS ToNodeTable, \n\t\tJSON_VALUE(CAST($to_id AS nvarchar(1000)),'$.id') AS ToNodeId\nFROM  dbo.friendOf; --An edge<\/pre>\n\n\n\n<p>The output of this query is:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"749\" height=\"284\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-82.png\" alt=\"\" class=\"wp-image-89862\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Each node object has its own surrogate key values that start at 0, so if you are going to use the code for more than one node at a time, you have to make the surrogate values unique for the TGF file (see the <a href=\"\\Dropbox\\Writing\\SimpleTalk\\Blog\\using-a-modeling-tool-to-draw-and-import-a-graph-into-sql-server\">last blog<\/a> on importing for more details on that). In the code I make a temp table to stage the objects, so if you have &gt; 1 node, the second set of keys need to start off where the previous ones left off. So the code uses an identity column, and joins to that identity column by schema, table, and edgeId, outputting the unique key:<\/p>\n\n\n\n<p>The code has a few things to choose in the header. The database, a list of nodes and edges you want in the output (for example, you might wish to graph just persons through the friendOf edge (example later)). There are a few default node and edge type values that control if matching labels or node types are appended to the graph.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">USE GraphDemo\nGO\n\n--parameters values for this database: https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/graphs\/sql-graph-sample?view=sql-server-ver15\n--list of nodes in format schema.nodetable.nameForLabel;schema.nodetable.nameForLabel;\n--done this way because it is a lot easier to manually edit\nDECLARE @NodeList nvarchar(4000) = 'dbo.person.name;dbo.Restaurant.name;dbo.City.name';\n\n--list of edges in format schema.edgeTable\nDECLARE @EdgeList nvarchar(4000) = 'dbo.likes;dbo.friendOf;dbo.locatedIn;dbo.livesIn';\n\n--used to determine formatting of name in output\nDECLARE @DefaultNodeType nvarchar(100) = '?'; --I want them all to have the node type appended in parens\nDECLARE @DefaultEdgeType nvarchar(100) = '?';\nDECLARE @LabelNonDefaultEdgeFlag bit = 1;<\/pre>\n\n\n\n<p>You can find the actual code here from my github repository: <a href=\"https:\/\/github.com\/drsqlgithub\/TGFImport\/blob\/main\/OutputToTGF.sql\">https:\/\/github.com\/drsqlgithub\/TGFImport\/blob\/main\/OutputToTGF.sql<\/a>.<\/p>\n\n\n\n<p>Using the parameters above (and the ones that are in the file), the output of my code from the database from the Microsoft demo script is:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">outputValue\n-----------------------------------\n1 Bellevue (City)\n2 Seattle (City)\n3 Redmond (City)\n4 John (person)\n5 Mary (person)\n6 Alice (person)\n7 Jacob (person)\n8 Julie (person)\n9 Taco Dell (Restaurant)\n10 Ginger and Spice (Restaurant)\n11 Noodle Land (Restaurant)\n#\n4 5 friendOf\n5 6 friendOf\n6 4 friendOf\n7 5 friendOf\n8 7 friendOf\n4 9 likes\n5 10 likes\n6 11 likes\n7 11 likes\n8 11 likes\n4 1 livesIn\n5 2 livesIn\n6 3 livesIn\n7 3 livesIn\n8 1 livesIn\n9 1 locatedIn\n10 2 locatedIn\n11 3 locatedIn<\/pre>\n\n\n\n<p>I chose to label all of the node types, and all of the edges. Saving this output (without the header) as a text file with a TGF extension, you can open and then importing into <a href=\"https:\/\/www.yworks.com\/products\/yed\">yEd<\/a>&nbsp;(a free tool), you see this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1168\" height=\"76\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-83.png\" alt=\"\" class=\"wp-image-89864\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>TGF files have no formatting (and how would you include formatting from a set of SQL Server objects anyhow), so you need to use the tools to format the diagram. There are two steps to make a good looking diagram reasonably quick.<\/p>\n\n\n\n<p>Use the <strong>Tools\\Fit Node to Label<\/strong> menu. This will make the nodes sized so that the text is not hanging over the edge.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1284\" height=\"110\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-85.png\" alt=\"\" class=\"wp-image-89865\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Then use one of tools in the <strong>Layout<\/strong> menu to output your diagram. I find the radial format works pretty good for semi complex, highly connected data. Keep in mind that any model is going to get complex to render (or even hand draw) fast. <strong>Radial<\/strong> put out a model that looks like:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1118\" height=\"1017\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-86.png\" alt=\"\" class=\"wp-image-89867\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>If you only want a single node and edge, you can just set a couple of nodes, like here the subgraph of person and friendOf:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DECLARE @NodeList nvarchar(4000) = 'dbo.person.name';\nDECLARE @EdgeList nvarchar(4000) = 'dbo.friendOf';<\/pre>\n\n\n\n<p>The output is lets you see the following subgraph showing you who is friends with whom without the other noise::<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"847\" height=\"455\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-88.png\" alt=\"\" class=\"wp-image-89868\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>It does kind of make me think about the name &#8220;friendOf&#8221;. Is friendOf a good name for a relationship that can be onesided? Not sure, but I did compare the data and this query:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT Person.Name, 'FriendOf', Friend.Name AS FriendName\nFROM Person, dbo.friendOf, Person AS Friend\nWHERE MATCH(Person - (friendOf) -&gt; Friend);<\/pre>\n\n\n\n<p>Outputs the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Name               FriendName\n--------- -------- --------------\nJohn      FriendOf Mary\nMary      FriendOf Alice\nAlice     FriendOf John\nJacob     FriendOf Mary\nJulie     FriendOf Jacob<\/pre>\n\n\n\n<p>Or you might want to see person and city, with just the edges friendOf and livesIn. Note that you will only see edges that go from person to person, city to city, or city to person (or person to city):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DECLARE @NodeList nvarchar(4000) = 'dbo.person.name;dbo.city.name';\nDECLARE @EdgeList nvarchar(4000) = 'dbo.friendOf;dbo.livesIn';<\/pre>\n\n\n\n<p>This outputs the following graph diagram:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"832\" height=\"837\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2021\/02\/word-image-89.png\" alt=\"\" class=\"wp-image-89870\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Hopefully this will help you if you are trying to draw up some sample data and you want to check to see the relationships graphically.<\/p>\n\n\n\n<p><strong>Read also:<\/strong><br><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 introduction<\/a><br><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/effective-strategies-for-storing-and-parsing-json-in-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">JSON handling in SQL Server<\/a><br><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/tools-sql-server\/automatically-creating-uml-database-diagrams-for-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">UML database diagrams for SQL Server<\/a><\/p>\n\n\n\n<section id=\"my-first-block-block_4d57e9fb1f2428df06a9e1d337ca8f92\" 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\" aria-label=\"Learn more &amp; try for free: Fast, reliable and consistent SQL Server development...\">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: SQL Server graph tables<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. How do you visualize SQL Server graph tables?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Export graph data to a visualization format like TGF (Trivial Graph Format) or GraphML, then open the file in a graph visualization tool like yEd, Gephi, or Cytoscape. SQL Server doesn\u2019t include built-in graph visualization &#8211; SSMS shows graph tables as regular tables. To export, query the $node_id and $from_id\/$to_id JSON metadata from node and edge tables to build the graph structure programmatically.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What is TGF format and how does it work with SQL Server graphs?<\/h3>\n            <div class=\"faq-answer\">\n                <p>TGF (Trivial Graph Format) is a simple text format: each line is either a node (ID followed by label) or an edge (from-ID followed by to-ID). A # separator divides the node section from the edge section. It\u2019s easy to generate from SQL queries and can be opened in yEd Graph Editor, which renders the graph with automatic layout algorithms.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Visualize SQL Server graph tables by generating TGF (Trivial Graph Format). Uses PowerShell and dynamic SQL to export node\/edge relationships for display in yEd or other graph tools.&hellip;<\/p>\n","protected":false},"author":56085,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2],"tags":[6072,136366,4151],"coauthors":[19684],"class_list":["post-89858","post","type-post","status-publish","format-standard","hentry","category-blogs","tag-graph","tag-sql-graph","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/89858","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\/56085"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=89858"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/89858\/revisions"}],"predecessor-version":[{"id":109095,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/89858\/revisions\/109095"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=89858"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=89858"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=89858"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=89858"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}