{"id":4157,"date":"2013-01-25T00:50:18","date_gmt":"2013-01-25T00:50:18","guid":{"rendered":"http:\/\/craftydba.com\/?p=4157"},"modified":"2017-10-08T16:48:18","modified_gmt":"2017-10-08T16:48:18","slug":"instead-of-delete","status":"publish","type":"post","link":"https:\/\/craftydba.com\/?p=4157","title":{"rendered":"Instead of Delete"},"content":{"rendered":"<p><a href=\"https:\/\/craftydba.com\/wp-content\/uploads\/2012\/03\/plastic-tommy-gun.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/craftydba.com\/wp-content\/uploads\/2012\/03\/plastic-tommy-gun-150x150.jpg\" alt=\"\" title=\"plastic-tommy-gun\" width=\"150\" height=\"150\" class=\"alignleft size-thumbnail wp-image-4106\" \/><\/a><\/p>\n<p>Today, I will be looking at how <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms175089(v=sql.105).aspx\">INSTEAD OF TRIGGERS<\/a> can be used to make our <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms187956.aspx\">VIEW<\/a> correctly work with a DELETE statement.  <\/p>\n<p>I will be using the [AUTOS] sample database for this demonstration.  The [VW_JOIN_MAKES_2_MODELS] view will be enhanced with a instead of trigger so that delete action can be perform on two tables at the same time.<\/p>\n<p>The <a href='https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/exploring-views-part5.sql_.txt'>enclosed<\/a> script has code to perform the following actions in the AUTOS database.   <\/p>\n<p><span style=\"color: #008000;\"><\/p>\n<dl>\n<dd>5A &#8211; Remove aggregate view<\/dd>\n<dd>5B &#8211; Reload tables with good data<\/dd>\n<dd>5C &#8211; Create a instead of trigger (insert)<\/dd>\n<dd>5D &#8211; Test insert actions of instead of trigger<\/dd>\n<dd>5E &#8211; Create a instead of trigger (delete)<\/dd>\n<dd>5F &#8211; Test delete actions of instead of trigger<\/dd>\n<dd>5G &#8211; Create a instead of trigger (update)<\/dd>\n<dd>5H &#8211; Test update actions of instead of trigger<\/dd>\n<\/dl>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p>Our focus today will be on 5E &#8211; defining the instead of delete trigger and 5F &#8211; performing unit testing on said trigger.<\/p>\n<p>Like most Data Definition Language (DDL) constructs, a user defined trigger has three operations associated with it: <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms189799.aspx\">CREATE TRIGGER<\/a>, <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms176072.aspx\">ALTER TRIGGER<\/a> and <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms173497.aspx\">DROP TRIGGER<\/a>.<\/p>\n<p>Since our view joins data between two tables, we need to know if data is going to be deleted from the 1 = MODELS, 2 = MAKES or 3 = BOTH tables.<\/p>\n<p>With the instead of insert trigger, we could use the physical data in the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms191300.aspx\">inserted table<\/a> to determine what action to perform.  The instead of delete table has the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms191300.aspx\">deleted table<\/a> for reference; However it only shows potential records affected by the delete statement.<\/p>\n<p>How do we determine what table to delete from?<\/p>\n<p>We are in luck since the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms187730.aspx\">DBCC INPUTBUFFER<\/a> command can help us.  Calling this function with the current session id returns a table containing information about the event.  I used a table variable below to hold the information.  We can find the exact TSQL statement that was used when the trigger fired.<\/p>\n<p>By parsing this string, we can find the WHERE clause of the delete statement.  Thus, finding what table to delete from.  <\/p>\n<p>Here are some ground rules for our view users.  One requirement of the user is to use the primary key (make or model id) or natural key (make or model name) in the WHERE clause.  Deleting from the model table has no dependencies.  Deleting from the make table can only occur if no model records exist.  An exception to this rule is a delete from both tables when only one record exists in both.  Any broken requirements will end up in a error.  <\/p>\n<p>The code below defines the INSTEAD OF DELETE trigger.<\/p>\n<pre class=\"lang:TSQL theme:familiar mark:1,2-3\" title=\"instead of delete - trigger\">\r\n--\r\n-- Create a instead of trigger (delete)\r\n--\r\n\r\n-- Remove existing trigger\r\nIF OBJECT_ID('DBO.TRG_CORRECT_DEL_ACTION_4_MAKES_2_MODELS') IS NOT NULL \r\nDROP TRIGGER [DBO].[TRG_CORRECT_DEL_ACTION_4_MAKES_2_MODELS];\r\nGO\r\n\r\n-- Create instead of trigger\r\nCREATE TRIGGER [DBO].[TRG_CORRECT_DEL_ACTION_4_MAKES_2_MODELS] \r\n    ON [DBO].[VW_JOIN_MAKES_2_MODELS]\r\nINSTEAD OF DELETE\r\nAS  \r\n\r\n  -- Author:    John Miner \r\n  -- Date:      Jan 2013\r\n  -- Purpose:   Perform the correct delete action for a view. \r\n\r\nBEGIN  \r\n\r\n    -- Declare local variables\r\n    DECLARE @VAR_WHICH_TABLE INT;\r\n    DECLARE @VAR_TSQL NVARCHAR(4000);\r\n    DECLARE @VAR_VAL1 INT;\r\n    DECLARE @VAR_VAL2 INT;\r\n\r\n    -- Declare input buffer variable\r\n    DECLARE @VAR_BUFFER TABLE \r\n    (\r\n        [EventType] NVARCHAR(30),\r\n        [Parameters] INT,\r\n        [EventInfo] NVARCHAR(4000)\r\n    );\r\n\r\n    -- Find the tsql that fired the trigger\r\n    INSERT INTO @VAR_BUFFER\r\n    EXEC sp_executesql N'DBCC INPUTBUFFER(@@spid) WITH NO_INFOMSGS';\r\n\r\n    -- Parse TSQL to get WHERE clause\r\n    SELECT @VAR_TSQL = UPPER(ISNULL(B.EventInfo, '')) FROM @VAR_BUFFER B;\r\n    SELECT @VAR_VAL1 = CHARINDEX('WHERE', @VAR_TSQL, 1);\r\n    IF (@VAR_VAL1 > 0)\r\n        SELECT @VAR_TSQL = SUBSTRING(@VAR_TSQL, @VAR_VAL1, LEN(@VAR_TSQL) - @VAR_VAL1);\r\n\r\n    -- Action being performed\r\n    PRINT 'INSTEAD OF DELETE ACTION - ';\r\n\r\n    -- Find out what is being modified\r\n    SELECT @VAR_WHICH_TABLE = 0;\r\n\r\n    IF ( (CHARINDEX('MODEL_ID', @VAR_TSQL, 1) > 0) OR (CHARINDEX('MODEL_NM', @VAR_TSQL, 1) > 0) )\r\n        SELECT @VAR_WHICH_TABLE = @VAR_WHICH_TABLE + 1;\r\n\r\n    IF ( (CHARINDEX('MAKER_ID', @VAR_TSQL, 1) > 0) OR (CHARINDEX('MAKER_NM', @VAR_TSQL, 1) > 0) )\r\n        SELECT @VAR_WHICH_TABLE = @VAR_WHICH_TABLE + 2;\r\n\r\n\r\n    -- Key fields are null\r\n    IF (@VAR_WHICH_TABLE = 0)\r\n    BEGIN\r\n        -- Action being performed\r\n        PRINT '  NO VALID KEYS SUPPLIED. ';\r\n\r\n\t-- Raise an error on this action\r\n        RAISERROR('Either makers or models id or name must not be null when deleting data.  Please try again.', 16, 1)\r\n    END;\r\n\r\n\r\n    -- Delete from models table\r\n    IF (@VAR_WHICH_TABLE = 1)\r\n    BEGIN\r\n        -- Action being performed\r\n        PRINT '  DELETE FROM MODELS. ';\r\n\r\n\t-- Perform the delete\r\n        DELETE FROM [DBO].[MODELS]\r\n        WHERE MODEL_ID IN\r\n        ( SELECT D.MODEL_ID FROM deleted AS D );\r\n    END;\r\n\r\n\r\n    -- Delete from makes table\r\n    IF (@VAR_WHICH_TABLE = 2)\r\n    BEGIN\r\n        -- Must be one record\r\n\tSELECT @VAR_VAL1 = COUNT(*) FROM deleted;\r\n\r\n\t-- Must not have any children\r\n\tSELECT @VAR_VAL2 = ISNULL(MODEL_ID, 0) FROM deleted;\r\n\r\n\t-- Make sure conditions are meet\r\n\tIF (@VAR_VAL1 = 1) AND (@VAR_VAL2 = 0)\r\n        BEGIN\r\n            -- Action being performed\r\n            PRINT '  DELETE FROM MAKES. ';\r\n\r\n            -- Perform the delete\r\n            DELETE FROM [DBO].[MAKES]\r\n            WHERE MAKER_ID IN\r\n            ( SELECT D.MAKER_ID FROM deleted AS D );\r\n        END\r\n\tELSE\r\n        BEGIN\r\n            -- Action being performed\r\n            PRINT '  TOO MANY CHILD RECORDS EXIST. ';\r\n\r\n            -- Raise an error on this action\r\n            RAISERROR('There must no model records left when deleting data from makes.  Please try again.', 16, 1)\r\n        END;\r\n\r\n    END;\r\n\r\n\r\n    -- Delete from both tables\r\n    IF (@VAR_WHICH_TABLE = 3)\r\n    BEGIN\r\n        -- Action being performed\r\n        PRINT '  DELETE FROM MODELS & MAKES. ';\r\n\r\n        -- Delete from child table first\r\n        DELETE FROM [DBO].[MODELS]\r\n        WHERE MODEL_ID IN\r\n        ( SELECT D.MODEL_ID FROM deleted AS D );\r\n\r\n        -- Delete from parent table second\r\n        DELETE FROM [DBO].[MAKES]\r\n        WHERE MAKER_ID IN\r\n        ( SELECT D.MAKER_ID FROM deleted AS D );\r\n                            \r\n    END;\r\n    \r\nEND \r\n\r\n<\/pre>\n<p>Before testing the new trigger, I want to add another model record (child) to the maker table (parent).  This means on data relationship with have an 1 to 2 cardinality.<\/p>\n<pre class=\"lang:TSQL theme:familiar mark:1,2-3\" title=\"setup test case\">\r\n-- Add data to child table\r\nINSERT INTO DBO.VW_JOIN_MAKES_2_MODELS (MAKER_ID, MODEL_NM, MODEL_YR, MSRP)\r\n    VALUES (6, 'Model X', 2013, 104000);\r\n\r\n<\/pre>\n<p>Lets get the negative testing out of the way by trying to delete data from the view without a natural or primary key.<\/p>\n<pre class=\"lang:TSQL theme:familiar mark:1,2-3\" title=\"negative test case - all records\">\r\n-- Delete should fail\r\nDELETE FROM DBO.VW_JOIN_MAKES_2_MODELS;\r\n<\/pre>\n<p><a href=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-1.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-1-1024x291.jpg\" alt=\"\" title=\"instead-of-delete-unit-test-1\" width=\"665\" height=\"188\" class=\"aligncenter size-large wp-image-4191\" srcset=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-1-1024x291.jpg 1024w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-1-300x85.jpg 300w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-1.jpg 1030w\" sizes=\"auto, (max-width: 665px) 100vw, 665px\" \/><\/a><\/p>\n<p>The first positive test is to delete data from the MODELS table using either the natural or primary key.<\/p>\n<pre class=\"lang:TSQL theme:familiar mark:1,2-3\" title=\"positive test case - models table\">\r\n-- Delete from child table\r\nDELETE FROM DBO.VW_JOIN_MAKES_2_MODELS WHERE MODEL_NM = 'Model X';\r\n\r\n-- Delete from child table\r\nDELETE FROM DBO.VW_JOIN_MAKES_2_MODELS WHERE MODEL_ID = 6;\r\n<\/pre>\n<p><a href=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-2.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-2-1024x343.jpg\" alt=\"\" title=\"instead-of-delete-unit-test-2\" width=\"665\" height=\"222\" class=\"aligncenter size-large wp-image-4192\" srcset=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-2-1024x343.jpg 1024w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-2-300x100.jpg 300w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-2.jpg 1032w\" sizes=\"auto, (max-width: 665px) 100vw, 665px\" \/><\/a><\/p>\n<p>The second positive test is to delete from the MAKES table using the natural key.  We have to make sure no child record exists. <\/p>\n<pre class=\"lang:TSQL theme:familiar mark:1,2-3\" title=\"positive test case - makes table\">\r\n-- Delete from parent table\r\nDELETE FROM DBO.VW_JOIN_MAKES_2_MODELS WHERE MAKER_NM = 'Tesla';\r\n<\/pre>\n<p><a href=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-3.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-3-1024x299.jpg\" alt=\"\" title=\"instead-of-delete-unit-test-3\" width=\"665\" height=\"194\" class=\"aligncenter size-large wp-image-4193\" srcset=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-3-1024x299.jpg 1024w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-3-300x87.jpg 300w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-3.jpg 1029w\" sizes=\"auto, (max-width: 665px) 100vw, 665px\" \/><\/a><\/p>\n<p>The second negative test is delete data from from the MAKES table using the primary key with one existing child record.  This is a no-no according to the rules.<\/p>\n<pre class=\"lang:TSQL theme:familiar mark:1,2-3\" title=\"negative test case - parent with child\">\r\n-- Delete from parent table (fails due to child record)\r\nDELETE FROM DBO.VW_JOIN_MAKES_2_MODELS WHERE MAKER_ID = 7;\r\n<\/pre>\n<p><a href=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-4.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-4-1024x296.jpg\" alt=\"\" title=\"instead-of-delete-unit-test-4\" width=\"665\" height=\"192\" class=\"aligncenter size-large wp-image-4194\" srcset=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-4-1024x296.jpg 1024w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-4-300x86.jpg 300w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-4.jpg 1030w\" sizes=\"auto, (max-width: 665px) 100vw, 665px\" \/><\/a><\/p>\n<p>The third positive test is to validate that deletion of data from both the MAKES and MODELS tables at the same time works. This is a new functionality that is not available in a normal view.<\/p>\n<pre class=\"lang:TSQL theme:familiar mark:1,2-3\" title=\"positive test case - both tables\">\r\n-- Delete from both tables\r\nDELETE FROM DBO.VW_JOIN_MAKES_2_MODELS WHERE MAKER_ID = 7 AND MODEL_ID = 7;\r\n<\/pre>\n<p><a href=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-5.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-5-1024x295.jpg\" alt=\"\" title=\"instead-of-delete-unit-test-5\" width=\"665\" height=\"191\" class=\"aligncenter size-large wp-image-4195\" srcset=\"https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-5-1024x295.jpg 1024w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-5-300x86.jpg 300w, https:\/\/craftydba.com\/wp-content\/uploads\/2013\/01\/instead-of-delete-unit-test-5.jpg 1029w\" sizes=\"auto, (max-width: 665px) 100vw, 665px\" \/><\/a><\/p>\n<p>In summary, we were able to add a INSTEAD OF DELETE trigger to our user defined view to make it function correctly with a DELETE statement. While the next article focus on making our view work correctly with an UPDATE statement, these trigger can be used for other purposes.  <\/p>\n<p>For instance, when designing a system that keeps track of large money transactions, we never want to actually delete a record.  We just want to mark it as deleted just incase the action taken by a high level user was fraudulent.  This would be another pefect use of this trigger.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today, I will be looking at how INSTEAD OF TRIGGERS can be used to make our VIEW correctly work with a DELETE statement. I will be using the [AUTOS] sample database for this demonstration. The [VW_JOIN_MAKES_2_MODELS] view will be enhanced with a instead of trigger so that delete action can be perform on two tables at the same time. The enclosed script has code to perform the following actions in the AUTOS database. 5A &#8211; Remove aggregate view 5B &#8211; Reload tables with good data 5C &#8211; Create a instead&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[398,738,317,316,31,737,33,433,80,563,12,432,736,330,15,28,614,29,613,708],"class_list":["post-4157","post","type-post","status-publish","format-standard","hentry","category-db-dev","tag-alter-trigger","tag-charindex","tag-create-trigger","tag-create-view","tag-database-developer","tag-dbcc-inputbuffer","tag-delete","tag-deleted","tag-drop-trigger","tag-drop-view","tag-free-code","tag-inserted","tag-instead-of-delete","tag-isnull","tag-john-f-miner-iii","tag-sql-server","tag-substring","tag-tsql","tag-upper","tag-user-defined-view"],"_links":{"self":[{"href":"https:\/\/craftydba.com\/index.php?rest_route=\/wp\/v2\/posts\/4157","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/craftydba.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/craftydba.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/craftydba.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/craftydba.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4157"}],"version-history":[{"count":0,"href":"https:\/\/craftydba.com\/index.php?rest_route=\/wp\/v2\/posts\/4157\/revisions"}],"wp:attachment":[{"href":"https:\/\/craftydba.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4157"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/craftydba.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4157"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/craftydba.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4157"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}