{"id":90236,"date":"2021-03-05T01:41:07","date_gmt":"2021-03-05T01:41:07","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=90236"},"modified":"2021-03-05T01:41:07","modified_gmt":"2021-03-05T01:41:07","slug":"translating-index-key-errors-from-graph-tables-in-sql-server","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/blogs\/translating-index-key-errors-from-graph-tables-in-sql-server\/","title":{"rendered":"Translating Index\/Key Errors from Graph Tables in SQL Server"},"content":{"rendered":"<p>As I am working with graph tables, I find some quirks exist that make them interesting to work with. One of them is how the values show up in some error messages (in particular from unique indexes). Instead of the details you expect\\hope (Something like the JSON value you find in the id columns in the edge and node tables like: {&#8220;type&#8221;:&#8221;node&#8221;,&#8221;schema&#8221;:&#8221;dbo&#8221;,&#8221;table&#8221;:&#8221;Node&#8221;,&#8221;id&#8221;:0}), you get a pair of numbers that you need to decode.<\/p>\n<p>For example, take the following set of tables:<\/p>\n<pre class=\"lang:none theme:none\">CREATE TABLE dbo.Node(Name varchar(20)) AS NODE;\r\nCREATE TABLE dbo.Edge AS EDGE;\r\n\r\nALTER TABLE dbo.Edge ADD CONSTRAINT AKEdge UNIQUE($from_id, $to_id);\r\n\r\nINSERT INTO dbo.Node (Name) VALUES ('Fred');\r\nINSERT INTO dbo.Node (Name) VALUES ('Barney');\r\n\r\nINSERT INTO dbo.Edge ($From_id, $to_id)\r\nSELECT  (SELECT $node_id FROM dbo.Node WHERE name = 'Fred'),\r\n\t\t(SELECT $node_id FROM dbo.Node WHERE name = 'Barney');<\/pre>\n<p>Now, insert a duplicate node using the same code as the previous:<\/p>\n<pre class=\"lang:none theme:none\">INSERT INTO dbo.Edge ($From_id, $to_id)\r\nSELECT  (SELECT $node_id FROM dbo.Node WHERE name = 'Fred'),\r\n\t\t(SELECT $node_id FROM dbo.Node WHERE name = 'Barney')<\/pre>\n<p>This causes the following error message:<\/p>\n<pre class=\"lang:none theme:none\">Msg 2627, Level 14, State 1, Line 14\r\nViolation of UNIQUE KEY constraint 'AKEdge'. Cannot insert duplicate key \r\nin object 'dbo.Edge'. The duplicate key value is (455672671, 0, 455672671, 1).<\/pre>\n<p>So what is this: (455672671, 0, 455672671, 1)? If you look at the $node_id value from the following query:<\/p>\n<pre class=\"lang:none theme:none\">SELECT $node_id FROM dbo.Node WHERE name = 'Fred';<\/pre>\n<p>This returns:<\/p>\n<pre class=\"lang:none theme:none\">$node_id_F1ECB5498FC747CFBC24EF390EBCBCC9\r\n---------------------------------------------------------\r\n{\"type\":\"node\",\"schema\":\"dbo\",\"table\":\"Node\",\"id\":0}<\/pre>\n<p>The 0 for the id maps to the 0 in the error message (455672671, 0, 455672671, 1), and if you check the row for Barney, you will see that it has a 1 for the id.<\/p>\n<p>But what about the other number? This is the object_id for the table. You can see in this sample data it is duplicated but they could be different, but that value can be different (and will be for things like edge constraints where you are disallowing connection from two different node types in an edge.)<\/p>\n<p>To see this, execute:<\/p>\n<pre class=\"lang:none theme:none\">SELECT OBJECT_SCHEMA_NAME(object_id) AS schema_name, name AS ObjectName\r\nFROM   sys.tables\r\nWHERE  tables.object_id = 455672671<\/pre>\n<p>This returns:<\/p>\n<pre class=\"lang:none theme:none\">schema_name      ObjectName\r\n---------------- --------------------------\r\ndbo              Node<\/pre>\n<p>To make this whole process a little easier, I created the following function (download here: <a href=\"https:\/\/github.com\/drsqlgithub\/DRSQL_ORG-Uploads\/blob\/main\/Tools\/\">https:\/\/github.com\/drsqlgithub\/DRSQL_ORG-Uploads\/blob\/main\/Tools\/<\/a> in a file named GraphError.sql):<\/p>\n<pre class=\"lang:none theme:none\">CREATE OR ALTER PROCEDURE Tools.GraphDB$LookupItem\r\n(\r\n\t@ObjectId int,\r\n\t@Id int \r\n)\r\nAS\r\nBEGIN\r\n\tSET NOCOUNT ON;\r\n\tDECLARE @SchemaName sysname = OBJECT_SCHEMA_NAME(@ObjectId),\r\n\t\t    @TableName sysname = OBJECT_NAME(@ObjectId),\r\n\t        @SQLStatement nvarchar(MAX)\r\n\tSET @SQLStatement = CONCAT('SELECT * FROM ', \r\n            QUOTENAME(@SchemaName),'.',QUOTENAME(@TableName),\r\n\t    ' WHERE JSON_VALUE(CAST($node_id AS nvarchar(1000)),''$.id'') = ',@Id)\r\n\tEXECUTE (@SQLStatement)\r\nEND;<\/pre>\n<p>Using this, you can simply paste the values from the error message and execute the stored procedure to see the row that is offending. Execute this next query and get the two rows that represent the errored row, without knowing what table the object is from:<\/p>\n<pre class=\"lang:none theme:none\">EXEC Tools.GraphDB$LookupItem 455672671, 0;\r\nEXEC Tools.GraphDB$LookupItem 455672671, 1;<\/pre>\n<p>This returns:<\/p>\n<pre class=\"lang:none theme:none\">$node_id_F1ECB5498FC747CFBC24EF390EBCBCC9                Name\r\n-------------------------------------------------------- --------------------\r\n{\"type\":\"node\",\"schema\":\"dbo\",\"table\":\"Node\",\"id\":0}     Fred\r\n\r\n$node_id_F1ECB5498FC747CFBC24EF390EBCBCC9                Name\r\n-------------------------------------------------------- --------------------\r\n{\"type\":\"node\",\"schema\":\"dbo\",\"table\":\"Node\",\"id\":1}     Barney<\/pre>\n<p>I kept the code simple and just returned all columns, but it could easily be extended for whatever you need with a few additional tables of metadata&#8230;With a little work you could use the metadata from the objects related to the errored object and produce cleaner output\u2026 Something I may attempt later.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As I am working with graph tables, I find some quirks exist that make them interesting to work with. One of them is how the values show up in some error messages (in particular from unique indexes). Instead of the details you expect\\hope (Something like the JSON value you find in the id columns in&#8230;&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":[],"coauthors":[19684],"class_list":["post-90236","post","type-post","status-publish","format-standard","hentry","category-blogs"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/90236","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=90236"}],"version-history":[{"count":1,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/90236\/revisions"}],"predecessor-version":[{"id":90237,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/90236\/revisions\/90237"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=90236"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=90236"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=90236"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=90236"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}