{"id":1879,"date":"2014-10-07T00:00:00","date_gmt":"2014-10-07T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/set-based-constraint-violation-reporting-in-sql-server\/"},"modified":"2021-09-29T16:21:33","modified_gmt":"2021-09-29T16:21:33","slug":"set-based-constraint-violation-reporting-in-sql-server","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/set-based-constraint-violation-reporting-in-sql-server\/","title":{"rendered":"Set-based Constraint Violation Reporting in SQL Server"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>Many of us have experience on more than one SQL-DBMS, but it is almost inevitable that you will become a specialist on one product or another. All SQL-DBMSs are, of course, based on the relational model as originally defined by Edgar Codd (how well SQL follows the relational model <a href=\"https:\/\/www.simple-talk.com\/opinion\/opinion-pieces\/chris-date-and-the-relational-model\/\">is another issue,<\/a> which we won&#8217;t go into here). Though the basic principles of all products are the same, sometimes the details of particular implementations can lead to difficulties when you make the move from one product to another. I have worked with Sybase and Oracle in the past, but today I make my living with Microsoft SQL Server, so I am most up to date with that product and have the deepest knowledge of it.<\/p>\n<p>It can be interesting to look at the way the different products solve the same problems, and to observe what features may be implemented in one and not in the other. One of these differences is the way that Oracle and SQL Server handle the reporting of multiple constraint violations from a single statement. The Oracle approach is elegant and straightforward, in SQL Server we have to put a lot of effort into devising a workaround. \u00a0<\/p>\n<p>Oracle doesn&#8217;t always do better \u00a0in this sort of comparison; SQL Server specialists are often puzzled by the dire warning issued against using triggers by many Oracle experts. To those of you are familiar with SQL Server but not with Oracle, triggers in Oracle can be used effectively but it is considerably more challenging to use them correctly <a href=\"#harmful\">[1].<\/a><\/p>\n<p>On the other hand Oracle specialists may be equally puzzled by what I am about to say about SQL Server.<\/p>\n<h2>The Problem<\/h2>\n<p>For the most part SQL Server deals fairly well with set based operations, but with constraints in general, some serious shortcomings quickly become apparent. Any SQL insert, update or delete command will cause an exception condition to be raised that\u00a0 returns one constraint violation message, no matter how many constraints are violated, how many different types of constraint are violated,\u00a0 or how many of the affected rows violate the constraint.<\/p>\n<p>Before we go any further, let&#8217;s have a closer look at the kind of problems that SQL Server&#8217;s standard constraint violation reporting leads to.<\/p>\n<p>We will use the following small database design for the examples.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE TABLE dbo.customer\r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 customer_id int NOT NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 customer_name varchar(100) NOT NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT \"customer id must be unique\" PRIMARY KEY \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 customer_id \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n);\r\n\r\nINSERT INTO dbo.customer \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 customer_id, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 customer_name\r\n)\r\nVALUES\r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Siemens'\r\n);\r\n\r\nCREATE TABLE dbo.sales_order(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 customer_id int NOT NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number int NOT NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_date date NOT NULL CONSTRAINT sales_order_date_default\u00a0 DEFAULT getdate(),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT \"sales order number must be unique\" PRIMARY KEY CLUSTERED \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ), \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT \"sales order must have belong to valid customer\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOREIGN KEY(customer_id)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 REFERENCES dbo.customer (customer_id)\r\n);\r\n\r\nCREATE TABLE dbo.product(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 product_name varchar(100) NOT NULL,\r\n\u00a0CONSTRAINT \"product name must be unique\" PRIMARY KEY CLUSTERED \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 product_name\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 )\r\n);\r\n\r\n\r\nCREATE TABLE sales_order_line\r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number\u00a0 integer NOT NULL,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 product_name\u00a0\u00a0 varchar(100) NOT NULL, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 quantity\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 integer NOT NULL\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT quantity_default DEFAULT 1, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT \"order number and product name must be unique\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PRIMARY KEY (sales_order_number, product_name), \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT \"sales order line must belong to valid order\"\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOREIGN KEY (sales_order_number) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 REFERENCES sales_order (sales_order_number), \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CONSTRAINT \"sales order line must be for valid product\" \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOREIGN KEY (product_name) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 REFERENCES product (product_name) \r\n);\r\n<\/pre>\n<p>We now attempt the following insert:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">insert into sales_order_Line \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 product_name, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 quantity\r\n)\r\nvalues \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Bolt', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100\r\n)\r\n<\/pre>\n<p>Resulting in the following message:<\/p>\n<pre>\t\tMsg 547, Level 16, State 0, Line 3\r\nThe INSERT statement conflicted with the FOREIGN KEY constraint \"sales order line must belong to valid order\". The conflict occurred in database \"playground\", table \"dbo.sales_order\", column 'sales_order_number'.\r\nThe statement has been terminated.\r\n<\/pre>\n<p>Let&#8217;s say we now add the primary key reference to fix this problem:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">\t\tINSERT INTO sales_order \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 customer_id, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number \r\n)\r\nVALUES \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100\r\n)\r\n<\/pre>\n<p>Attempting the insert on sales_order_line again, we once again get a foreign key violation constraint message, but for a different foreign key.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">insert into sales_order_Line \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 product_name, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 quantity\r\n)\r\nvalues \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'Bolt', \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100\r\n)\r\n<\/pre>\n<p>&nbsp;<\/p>\n<pre>Msg 547, Level 16, State 0, Line 3\r\nThe INSERT statement conflicted with the FOREIGN KEY constraint \"sales order line must be for valid product\". The conflict occurred in database \"playground\", table \"dbo.product\", column 'product_name'.\r\nThe statement has been terminated.\r\n<\/pre>\n<p>Only one key violation is reported at a time. The situation gets even worse if we are inserting multiple rows.\u00a0 First let&#8217;s fix the foreign key violation on product:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">INSERT INTO product (product_name) VALUES ('bolt') \r\n<\/pre>\n<p>Now we attempt to insert several rows into sales_product_line, some of which are valid and some of which will violate foreign key constraints:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">insert into sales_order_Line \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 product_name, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 quantity\r\n)\r\nvalues (100,'Bolt',\u00a0 100),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (100, 'Bolt', 20), \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (200, 'Bolt', 1), \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (100, 'screw', 4) \r\n<\/pre>\n<p>We observe that the insert values contain not just foreign key violations, but also primary key violations.<\/p>\n<p>When issuing the insert command, as usual, only one of the constraint violations is reported:<\/p>\n<pre>Msg 2627, Level 14, State 1, Line 1\r\nViolation of PRIMARY KEY constraint 'order number and product name must be unique'. Cannot insert duplicate key in object 'dbo.sales_order_line'. The duplicate key value is (100, Bolt).\r\nThe statement has been terminated.\r\n<\/pre>\n<p>If your database is at the size of this demonstration, it would seem absurd to consider that this is a difficulty. However, with the enormous amounts of data being routinely imported into, or captured by, databases, a constraint violation exception can lead to a great deal of subsequent forensic work to find the rows that did it.<\/p>\n<p>Our Oracle user friends would probably say at this point that they don&#8217;t understand what the problem is. We just use the LOG ERRORS clause on the insert. This feature has been available since Oracle 10g Version 2.<\/p>\n<p>I created our test database in Oracle and issued the following commands:<\/p>\n<pre>EXECUTE DBMS_ERRLOG.CREATE_ERROR_LOG('sales_order_line', 'err$_sales_order_line');\r\n<\/pre>\n<p>This creates a table to hold the errors with the same columns as sales_order_line and some additional columns to hold the error information.<\/p>\n<p>We then issue the insert with the LOG ERRORS clause added at the end (the syntax has been adapted as Oracle does not support multiple value lines in the insert statement, also I had to shorten the constraint names as Oracle will only accept 30 characters):<\/p>\n<pre>insert into sales_order_Line \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 product_name, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 quantity\r\n)\r\nselect\u00a0 100,'Bolt',\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100 from dual\r\nunion all \r\nselect 100, 'Bolt', 20 from dual\r\nunion all\u00a0 \r\nselect 200, 'Bolt', 1 from dual\r\nunion all\u00a0\u00a0 \r\nselect 100, 'screw', 4 from dual\r\nLOG ERRORS INTO err$_sales_order_line ('INSERT') REJECT LIMIT UNLIMITED;\r\n<\/pre>\n<p>This gives us the following result:<\/p>\n<pre>ORA ERR NUMBERSORA ERR MESG$ORA ERR ROWIDSORA ERR OPTYPSORA ERR TAGSSALES ORDER NUMBERPRODUCT NAMEQUANTITY1ORA-00001: Unique Constraint (SYSTEM.order nr: prod name not unique) verletzt|INSERT100Bolt1001ORA-00001: Unique Constraint (SYSTEM.order nr: prod name not unique) verletzt|INSERT100Bolt202291ORA-02291: Integritats-Constraint (SYSTEM.sales ord line not valid order) verletzt - ubergeordneter Schlussel nicht gefunden|INSERT200Bolt12291ORA-02291: Integritats-Constraint (SYSTEM.sales ord line not valid prod) verletzt - ubergeordneter Schlussel nicht gefunden|INSERT100screw4<\/pre>\n<h2>A Solution<\/h2>\n<p>There are ways of avoiding this problem in SQL Server. Some workarounds are<b>:<\/b><\/p>\n<ul>\n<li><strong>Using Stored procedures or Table-valued functions<\/strong>. Inserts and updates will be done by the application using a stored procedure. This would allow us to do whatever validation and checking is appropriate before the insertion is done.\u00a0 <b> <\/b><\/li>\n<li><strong>Doing inserts a row at a time in a try-catch block<\/strong> This solution has been described here on Simple Talk in <a href=\"https:\/\/www.simple-talk.com\/sql\/t-sql-programming\/handling-constraint-violations-and-errors-in-sql-server\/\"> Handling Constraint Violations and Errors in SQL Server<\/a><b><\/b><\/li>\n<li><strong>The use of an INSTEAD OF trigger<\/strong> would allow us to find a way of executing this INSERT statement\u00a0 so that we get a report of all the constraint violations before we actually apply it.<\/li>\n<\/ul>\n<p><span class=\"MsoHyperlink\">The first two solutions will work well if you are in control of the import process. However, this isn&#8217;t always the case. By using the INSTEAD OF trigger, users can use their existing ways of inserting, deleting or updating data. In addition we can get much closer to the behaviour of the elegant Oracle solution. Clearly this cannot be achieved in a standard trigger on the table, because the database update has already taken place before the trigger has run and, if any constraints are violated, then the\u00a0 trigger code will never be executed. <\/span><\/p>\n<p>Here is the INSTEAD OF insert trigger to report all the constraint violations for the primary key and the two foreign key constraints in a global temporary table.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE TRIGGER dbo_sales_order_line_constraint_check_insert ON dbo.sales_order_line INSTEAD OF INSERT \r\nAS\r\nDECLARE @error bit = 0\r\nIF object_id('tempdb..##err_dbo_sales_order_line') IS NULL\r\nBEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CREATE TABLE ##err_dbo_sales_order_line(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number\u00a0\u00a0 int NOT NULL\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ,product_name varchar(100) NOT NULL\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ,quantity\u00a0\u00a0\u00a0\u00a0 int NULL \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ,spid integer NOT NULL\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ,constraint_name sysname\u00a0 NOT NULL)\r\nEND\r\nDELETE ##err_dbo_sales_order_line WHERE spid = @@spid\r\nINSERT INTO ##err_dbo_sales_order_line(sales_order_number,product_name,quantity, spid, constraint_name)\r\nSELECT\u00a0 sales_order_number,product_name,quantity,@@spid,'order number and product name must be unique' FROM inserted WHERE EXISTS \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT * FROM dbo.sales_order_line WHERE\u00a0 inserted.product_name = dbo.sales_order_line.product_name AND inserted.sales_order_number = dbo.sales_order_line.sales_order_number\r\n)\r\n\u00a0OR EXISTS (SELECT * FROM inserted sub WHERE inserted.sales_order_number = sub.sales_order_number\r\n\u00a0AND inserted.product_name = sub.product_name\r\n\u00a0AND (inserted.quantity != sub.quantity\r\n\u00a0OR (inserted.quantity IS NULL AND sub.quantity IS NOT NULL) OR (inserted.quantity IS NOT NULL and sub.quantity IS NULL)))\r\nIF @@rowcount &gt; 0 SET @error = 1\r\n\r\nINSERT INTO ##err_dbo_sales_order_line(sales_order_number,product_name,quantity, spid, constraint_name)\r\nSELECT sales_order_number,product_name,quantity,@@spid,'sales order line must belong to valid order' FROM inserted \r\n\u00a0WHERE NOT EXISTS (SELECT * FROM dbo.sales_order WHERE \r\n\u00a0dbo.sales_order.sales_order_number = inserted.sales_order_number)\r\nIF @@rowcount &gt; 0 SET @error = 1\r\nINSERT INTO ##err_dbo_sales_order_line(sales_order_number,product_name,quantity, spid, constraint_name)\r\nSELECT sales_order_number,product_name,quantity,@@spid,'sales order line must be for valid product' FROM inserted \r\n\u00a0WHERE NOT EXISTS (SELECT * FROM dbo.product WHERE \r\n\u00a0dbo.product.product_name = inserted.product_name)\r\nIF @@rowcount &gt; 0 SET @error = 1\r\n\r\nIF @error = 0 \r\nBEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INSERT INTO dbo.sales_order_line\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT DISTINCT * FROM inserted\r\nEND\r\nELSE\r\nBEGIN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RAISERROR('Constraint violation(s) - SELECT * FROM\u00a0 ##err_dbo_sales_order_line WHERE spid = @@spid for details', 16, 1)\r\nEND\r\n<\/pre>\n<p><span class=\"MsoHyperlink\"> We now do the insert again: <\/span><\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">insert into sales_order_Line \r\n(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sales_order_number, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 product_name, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 quantity\r\n)\r\nvalues (100,'Bolt',\u00a0 100),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (100, 'Bolt', 20), \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (200, 'Bolt', 1), \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (100, 'screw', 4) \r\n<\/pre>\n<p><span class=\"MsoHyperlink\"> We now get this result: <\/span><\/p>\n<pre>Msg 50000, Level 16, State 1, Procedure dbo_sales_order_line_constraint_check_insert, Line 43\r\nConstraint violation(s) - SELECT * FROM\u00a0 ##err_dbo_sales_order_line WHERE spid = @@spid for details\r\nMsg 3609, Level 16, State 1, Line 1\r\nThe transaction ended in the trigger. The batch has been aborted.\r\n<\/pre>\n<p><span class=\"MsoHyperlink\"> We can now execute the query from the RAISERROR message:<\/span><\/p>\n<pre>SELECT * FROM\u00a0 ##err_dbo_sales_order_line WHERE spid = @@spid<\/pre>\n<p>Which now gives us all the rows that violated constraints and the corresponding constraint name.<\/p>\n<pre>sales_order_number product_name quantity spid\u00a0\u00a0\u00a0\u00a0 constraint_name\r\n100\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Bolt\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 100\u00a0\u00a0\u00a0\u00a0\u00a0 53\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order number and product name must be unique\r\n100\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Bolt\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a020\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a053\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0order number and product name must be unique\r\n200\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Bolt\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 1\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a053\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sales order line must belong to valid order\r\n100\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0screw\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a04\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 53\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sales order line must be for valid product\r\n<\/pre>\n<p>I have deliberately used temporary tables here as the we are probably only concerned with the results during the current session and we can save ourselves a lot of housekeeping by not holding the data permanently. Of course triggers for update and delete would also be required.<\/p>\n<p>Now I can think of a number of objections to this approach.<\/p>\n<ul>\n<li><strong>Maintenance<\/strong> \u00a0First of all, maintaining these triggers looks like a lot of work. We can ease the burden by generating the triggers automatically. The trigger code above was generated by a query (you can find the code for the query at the end of the article). Furthermore, the trigger could be regenerated in a DDL trigger whenever an ALTER TABLE is issued against the table. This way the trigger will always be in step with the constraints defined in the database. <b> <\/b><\/li>\n<li><strong>Scalability.<\/strong> If we do a mass update and every row violates every constraint on the table then we could be faced with a massive number of output rows from the constraint reporting. It would be useful to have some way we could switch off or limit the number of rows returned. We could hard code a limit into the triggers but this seems a little arbitrary and may lead to misleading results.<\/li>\n<\/ul>\n<p>Another refinement would be to have the INSTEAD OF triggers on views rather than on the tables themselves. If the view was updated then all the constraint violations would be reported, a direct update on the table would only report the first constraint on the first row that violated that constraint. Of course this would mean maintaining a whole set of views with the same structure as the underlying tables, but here again DDL triggers could come to our aid to modify the views automatically whenever the table structure changes (we would need a completely consistent naming convention for the views to achieve this).<\/p>\n<h2>Conclusion<\/h2>\n<p>As we have seen, different SQL-DBMS products can vary considerably in how they handle particular issues. Though it can be hard enough to keep up to date on the details of our &#8220;home&#8221; product, it can be very interesting to have a heads up from time to time and look at how the other half lives. Sometimes the grass on the other side of the fence is greener, but sometimes the concrete is grayer.<\/p>\n<p>I think we can be fairly hopeful that Microsoft will eventually bring a similar kind of constraint reporting into the core product, thus matching the Oracle functionality, but until that time I hope that this article has provided some ideas about how a workaround could be developed.<\/p>\n<h2>References<\/h2>\n<p id=\"harmful\" class=\"MsoBodyText\">[1] <a href=\"http:\/\/harmfultriggers.blogspot.de\/\">Oracle: Triggers Considered Harmful, Considered Harmful<\/a><\/p>\n<h2>Acknowledgments<\/h2>\n<p>Though\u00a0 I have been thinking about this problem for some time, I was prompted into action by the following article on Fabian Pascal&#8217;s dbdebunk website : <br \/>\n<a href=\"http:\/\/www.dbdebunk.com\/2014\/09\/weak-entities-referential-constraints.html\">Weak Entities, Referential Constraints &amp; Normalization<\/a><\/p>\n<h2>Appendix: Code to Generate Insert Trigger<\/h2>\n<pre class=\"theme:ssms2012 lang:tsql\"> ALTER VIEW generate_insert_constraint_reporting_trigger \r\nAS\r\nSELECT \r\n\u00a0\u00a0\u00a0 table_schema, \r\n\u00a0\u00a0\u00a0 table_name, \r\n\u00a0\u00a0\u00a0 \/********************************************************\r\n\u00a0\u00a0\u00a0 * Generate the create or alter trigger statement\r\n\u00a0\u00a0\u00a0 ********************************************************\/\r\n\r\n\u00a0\u00a0\u00a0 CASE WHEN EXISTS \r\n\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0SELECT * \r\n\u00a0\u00a0\u00a0\u00a0\u00a0FROM sys.triggers \r\n\u00a0\u00a0\u00a0\u00a0\u00a0WHERE name = tables.table_schema + '_' + tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0+\u00a0 '_constraint_check_insert'\r\n\u00a0\u00a0\u00a0 ) THEN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0'ALTER' \r\n\u00a0\u00a0\u00a0 ELSE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0'CREATE'\r\n\u00a0\u00a0\u00a0 END + \r\n\u00a0\u00a0\u00a0 ' TRIGGER ' + tables.table_schema + '_' + tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0+ '_constraint_check_insert ON ' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0tables.table_schema + '.' + tables.table_name + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0' INSTEAD OF INSERT ' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0'AS' + CHAR(10)\u00a0 + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0'DECLARE @error bit = 0' + CHAR(10) + \r\n\r\n\u00a0\u00a0\u00a0 \/***************************************************\r\n\u00a0\u00a0\u00a0 * Create global temporary table for results \r\n\u00a0\u00a0\u00a0 ***************************************************\/\r\n\r\n\u00a0\u00a0\u00a0 'IF object_id(''tempdb..##err_' + tables.table_schema + '_' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  + tables.table_name + ''') IS NULL' + CHAR(10) + \r\n\u00a0\u00a0\u00a0 'BEGIN' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0CHAR(9) + 'CREATE TABLE ##err_' + tables.table_schema + '_' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  + tables.table_name + '(' + CHAR(10) + CHAR(9) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0STUFF(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CHAR(10) + CHAR(9)\u00a0 + ',' + column_name + char(9) + data_type\u00a0 + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CASE WHEN data_type in ('varchar', 'nvarchar', 'char', 'nchar', 'binary', 'varbinary')\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  \u00a0THEN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 CASE WHEN CHARACTER_MAXIMUM_LENGTH = -1 THEN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'(max)' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'(' + CAST(CHARACTER_MAXIMUM_LENGTH as varchar) + ')' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 END \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN data_type in ('numeric', 'decimal') THEN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'(' + CAST(numeric_precision as varchar) + ',' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0+ CAST(numeric_scale as varchar) + ')' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN data_type\u00a0 = 'float' THEN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'(' + CAST(numeric_precision as varchar) + ')' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ELSE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0''\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0END + CASE WHEN IS_NULLABLE = 'YES' THEN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ' NULL ' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ELSE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ' NOT NULL' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0END\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INFORMATION_SCHEMA.columns \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0columns.TABLE_NAME = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0), 1, 3, '')\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0) + CHAR(10) + CHAR(9) + ',spid integer NOT NULL' +\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CHAR(10) + CHAR(9) + ',constraint_name sysname\u00a0 NOT NULL' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0')' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0'END' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0'DELETE ##err_' + tables.table_schema + '_' + tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  + ' WHERE spid = @@spid' + CHAR(10) + \r\n\r\n\u00a0\u00a0\u00a0 \/***************************************************\r\n\u00a0\u00a0\u00a0 * Generate the primary key constraint check\r\n\u00a0\u00a0\u00a0 ***************************************************\/\r\n\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0 (SELECT\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'INSERT INTO ##err_' + tables.table_schema + '_' + tables.table_name + '(' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 STUFF\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0',' + column_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INFORMATION_SCHEMA.columns \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0table_schema = tables.table_schema \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND table_name = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01, 1, '' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ) + ', spid, constraint_name' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0')' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'SELECT\u00a0 ' + STUFF\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0',' + column_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INFORMATION_SCHEMA.columns \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0table_schema = tables.table_schema \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND table_name = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01, 1, '' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ) + ',@@spid,''' + constraint_name +\u00a0 ''' FROM inserted WHERE EXISTS \r\n\u00a0\u00a0\u00a0\u00a0\u00a0(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SELECT * FROM ' + tables.table_schema + '.' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0+\u00a0 tables.table_name + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0' WHERE ' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0STUFF((\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0' AND inserted.' + column_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 + ' = ' + tables.table_schema + '.' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 +\u00a0 tables.table_name + '.' + column_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INFORMATION_SCHEMA.key_column_usage \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0table_constraints.constraint_name =\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0key_column_usage.constraint_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND\u00a0\u00a0 table_constraints.table_name\u00a0 = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND table_constraints.table_schema = tables.table_schema\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)')\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ,1,4,'') + CHAR(10) + ')' + CHAR(10) + \r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\/**********************************************************\r\n\u00a0\u00a0\u00a0\u00a0\u00a0* There may be duplicates not just in the existing data\r\n\u00a0\u00a0\u00a0\u00a0\u00a0* but in the inserted data too.\r\n\u00a0\u00a0\u00a0\u00a0\u00a0**********************************************************\/\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0' OR EXISTS (SELECT * FROM inserted sub WHERE' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0STUFF((\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CASE WHEN KEY_COLUMN_USAGE.column_name IS NULL \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 AND columns.IS_NULLABLE = 'YES'\u00a0 THEN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0' AND (inserted.' + columns.column_name + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ' != sub.' + columns.column_name +\u00a0 CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0' OR (inserted.' +\u00a0 columns.column_name\u00a0 + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ' IS NULL AND sub.' + columns.column_name + ' IS NOT NULL)' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0' OR (inserted.' + columns.column_name +\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0' IS NOT NULL and sub.' + columns.column_name + ' IS NULL))'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHEN KEY_COLUMN_USAGE.column_name IS NULL \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND columns.IS_NULLABLE = 'NO'\u00a0 THEN\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0' AND inserted.' + columns.column_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0+ ' != sub.' + columns.column_name + CHAR(10) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ELSE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ' AND inserted.' + columns.column_name + ' = sub.' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 + columns.column_name + CHAR(10) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0END \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INFORMATION_SCHEMA.columns \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 LEFT JOIN \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INFORMATION_SCHEMA.key_column_usage \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ON \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0columns.column_name = key_column_usage.COLUMN_NAME\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND table_constraints.constraint_name = key_column_usage.constraint_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0table_constraints.table_name\u00a0 = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND table_constraints.table_schema = tables.table_schema\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND columns.table_schema = tables.table_schema \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND columns.table_name = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)')\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ,1,4,'') + ')' +\u00a0 CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0'IF @@rowcount &gt; 0 SET @error = 1' + CHAR(10) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0FROM INFORMATION_SCHEMA.table_constraints\r\n\u00a0\u00a0\u00a0 WHERE\r\n\u00a0\u00a0\u00a0\u00a0\u00a0tables.TABLE_SCHEMA = table_constraints.table_schema\r\n\u00a0\u00a0\u00a0 AND tables.table_name = table_constraints.table_name \r\n\u00a0\u00a0\u00a0 AND table_constraints.constraint_type\u00a0 = 'primary key'\r\n\u00a0\u00a0\u00a0 ) + CHAR(10) + \r\n\r\n\u00a0\u00a0\u00a0 \/******************************************************\r\n\u00a0\u00a0\u00a0 * Generate the foreign key constraint check \r\n\u00a0\u00a0\u00a0 ******************************************************\/\r\n\r\n\u00a0\u00a0\u00a0 COALESCE((\r\n\u00a0\u00a0\u00a0\u00a0\u00a0SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0'INSERT INTO ##err_' + tables.table_schema + '_' + tables.table_name + '(' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0STUFF\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ',' + column_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 INFORMATION_SCHEMA.columns \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 table_schema = tables.table_schema \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0AND table_name = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 1, 1, '' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0) + ', spid, constraint_name' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0')' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0CHAR(10) + 'SELECT ' + STUFF\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 (\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0',' + column_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 FROM \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0INFORMATION_SCHEMA.columns \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0table_schema = tables.table_schema \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 AND table_name = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0),\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a01, 1, '' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ) + ',@@spid,''' + name +\u00a0 ''' FROM inserted ' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ' WHERE NOT EXISTS (SELECT * FROM ' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 object_schema_name(referenced_object_id) + '.' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 + object_name(referenced_object_id) + ' WHERE ' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 STUFF((\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SELECT \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ' AND ' + object_schema_name(referenced_object_id) + '.' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 + object_name(referenced_object_id)\u00a0 + '.' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0col_name(referenced_object_id, referenced_column_id) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0+ ' = ' + 'inserted.' + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0col_name(parent_object_id, parent_column_id)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FROM \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sys.foreign_key_columns \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 foreign_keys.object_id\u00a0 = foreign_key_columns.constraint_object_id \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)'\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ),1,4,'') + ')' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 'IF @@rowcount &gt; 0 SET @error = 1' + CHAR(10) \r\n\u00a0\u00a0\u00a0\u00a0\u00a0FROM\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0sys.foreign_keys\r\n\u00a0\u00a0\u00a0\u00a0\u00a0WHERE \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0OBJECT_SCHEMA_NAME(parent_object_id) = tables.table_schema \r\n\u00a0\u00a0\u00a0\u00a0\u00a0AND object_name(parent_object_id) = tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)'\r\n\u00a0\u00a0\u00a0 ), '') + CHAR(10) + \r\n\r\n\u00a0\u00a0\u00a0 \/**********************************************\r\n\u00a0\u00a0\u00a0 * If not constraint violations found then \r\n\u00a0\u00a0\u00a0 * do the insert, otherwise raise an error \r\n\u00a0\u00a0\u00a0 **********************************************\/\r\n\r\n\u00a0\u00a0\u00a0 'IF @error = 0 ' + CHAR(10) + \r\n\u00a0\u00a0\u00a0 'BEGIN' + CHAR(10) + \r\n\u00a0\u00a0\u00a0 CHAR(9) + 'INSERT INTO ' + tables.table_schema + '.' + tables.table_name + CHAR(10) + \r\n\u00a0\u00a0\u00a0 CHAR(9) + 'SELECT DISTINCT * FROM inserted' + CHAR(10) +\r\n\u00a0\u00a0\u00a0 'END' + CHAR(10) +\r\n\u00a0\u00a0\u00a0 'ELSE' + CHAR(10) + \r\n\u00a0\u00a0\u00a0 'BEGIN' + CHAR(10) + \r\n\u00a0\u00a0\u00a0 CHAR(9) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0'RAISERROR(''Constraint violation(s) - SELECT * FROM\u00a0 ##err_' \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0+ tables.table_schema + '_' + tables.table_name \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0  +\u00a0 ' WHERE spid = @@spid for details'', 16, 1)' + CHAR(10) + \r\n\u00a0\u00a0\u00a0\u00a0\u00a0CHAR(9) + 'ROLLBACK' + CHAR(10) + \r\n\u00a0\u00a0\u00a0 'END' as trigger_sql\r\nFROM \r\n\u00a0\u00a0\u00a0 information_schema.tables\r\n<\/pre>\n<p>Here is a test example:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">select trigger_sql \r\nfrom generate_insert_constraint_reporting_trigger \r\nwhere table_name = 'sales_order_line'\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>When you&#8217;re importing data into an RDBMS in bulk and an exception condition is raised because of a constraint violation, you generally need to fix the problem with the data and try again.  The error won&#8217;t tell you which rows are causing the violation. What if you&#8217;ve thousands of rows to search  when it happens?  There are solutions, writes William Sisson.&hellip;<\/p>\n","protected":false},"author":213083,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4150,4151,4252],"coauthors":[6806],"class_list":["post-1879","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-sql","tag-sql-server","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1879","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\/213083"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1879"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1879\/revisions"}],"predecessor-version":[{"id":77812,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1879\/revisions\/77812"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1879"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1879"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1879"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1879"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}