zoukankan      html  css  js  c++  java
  • (Protecting the Integrity of Your Data).sql

    CREATE DATABASE ProtectionChapter
    go
    USE ProtectionChapter
    Go
    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - Declarative Data Protection
    ------------------------------------------------------------------------------------------------------

    CREATE SCHEMA Music
    GO
    CREATE TABLE Music.Artist
    (
       ArtistId int NOT NULL,
       Name varchar(60) NOT NULL,

       CONSTRAINT PKNameArtist PRIMARY KEY CLUSTERED (ArtistId),
       CONSTRAINT AKNameArtist_Name UNIQUE NONCLUSTERED (Name)
    )
    CREATE TABLE Music.Publisher
    (
            PublisherId              int primary key,
            Name                      varchar(20),
            CatalogNumberMask varchar(100)
            CONSTRAINT DfltNamePublisher_CatalogNumberMask default ('%'),
            CONSTRAINT AKNamePublisher_Name UNIQUE NONCLUSTERED (Name),
    )

    CREATE TABLE Music.Album
    (
       AlbumId int NOT NULL,
       Name varchar(60) NOT NULL,
       ArtistId int NOT NULL,
       CatalogNumber varchar(20) NOT NULL,
       PublisherId int NOT null --not requiring this information

       CONSTRAINT PKAlbum PRIMARY KEY CLUSTERED(AlbumId),
       CONSTRAINT AKAlbum_Name UNIQUE NONCLUSTERED (Name),
       CONSTRAINT FKMusic_Artist$records$Music_Album
                FOREIGN KEY (ArtistId) REFERENCES Music.Artist(ArtistId),
       CONSTRAINT FKMusic_Publisher$published$Music_Album
                FOREIGN KEY (PublisherId) REFERENCES Music.Publisher(PublisherId)
    )
    GO

    INSERT  INTO Music.Publisher (PublisherId, Name, CatalogNumberMask)
    VALUES (1,'Capitol',
            '[0-9][0-9][0-9]-[0-9][0-9][0-9a-z][0-9a-z][0-9a-z]-[0-9][0-9]'),
            (2,'MCA', '[a-z][a-z][0-9][0-9][0-9][0-9][0-9]')

    INSERT  INTO Music.Artist(ArtistId, Name)
    VALUES (1, 'The Beatles'),(2, 'The Who')

    INSERT INTO Music.Album (AlbumId, Name, ArtistId, PublisherId, CatalogNumber)
    VALUES (1, 'The White Album',1,1,'433-43ASD-33'),
           (2, 'Revolver',1,1,'111-11111-11'),
           (3, 'Quadrophenia',2,2,'CD12345')
    GO

    ALTER TABLE Music.Artist WITH CHECK
       ADD CONSTRAINT chkMusic_Artist$Name$NoDuranNames
               CHECK (Name not like '%Duran%')
    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - Declarative Data Protection - [WITH CHECK | WITH NOCHECK]
    ------------------------------------------------------------------------------------------------------

    INSERT INTO Music.Artist(ArtistId, Name)
    VALUES (3, 'Duran Duran')
    GO

    INSERT INTO Music.Artist(ArtistId, Name)
    VALUES (3, 'Madonna')
    GO

    ALTER TABLE Music.Artist WITH NOCHECK
       ADD CONSTRAINT chkMusic_Artist$Name$noMadonnaNames
               CHECK (Name not like '%Madonna%')

    Go

    UPDATE Music.Artist
    SET Name = Name
    GO

    SELECT CHECK_CLAUSE,
           objectproperty(object_id(CONSTRAINT_SCHEMA + '.' +
                                     CONSTRAINT_NAME),'CnstIsNotTrusted') AS NotTrusted
    FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS
    WHERE CONSTRAINT_SCHEMA = 'Music'
      And CONSTRAINT_NAME = 'chkMusic_Artist$Name$noMadonnaNames'


    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - Declarative Data Protection - CHECK Constraints Based on Simple Expressions
    ------------------------------------------------------------------------------------------------------

    INSERT INTO Music.Album ( AlbumId, Name, ArtistId, PublisherId, CatalogNumber )
    VALUES ( 4, '', 1, 1,'dummy value' )
    GO

    INSERT INTO Music.Album ( AlbumId, Name, ArtistId, PublisherId, CatalogNumber )
    VALUES ( 5, '', 1, 1,'dummy value' )
    GO

    DELETE FROM Music.Album
    WHERE  Name = ''
    GO
    ALTER TABLE Music.Album WITH CHECK
       ADD CONSTRAINT chkMusic_Album$Name$noEmptyString
               CHECK (LEN(RTRIM(Name)) > 0)

    GO
    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - Declarative Data Protection
    --  - CHECK Constraints Based on Functions - Example Constraint That Accesses Other Tables (Entry Mask)
    ------------------------------------------------------------------------------------------------------
    CREATE FUNCTION Music.Publisher$CatalogNumberValidate
    (
       @CatalogNumber char(12),
       @PublisherId int --now based on the Artist ID
    )

    RETURNS bit
    AS
    BEGIN
       DECLARE @LogicalValue bit, @CatalogNumberMask varchar(100)

       SELECT @LogicalValue = CASE WHEN @CatalogNumber LIKE CatalogNumberMask
                                          THEN 1
                                   ELSE 0  END
       FROM   Music.Publisher
       WHERE  PublisherId = @PublisherId

       RETURN @LogicalValue
    END

    GO

    SELECT Album.CatalogNumber, Publisher.CatalogNumberMask
    FROM   Music.Album as Album
             JOIN Music.Publisher as Publisher
                ON Album.PublisherId = Publisher.PublisherId
    GO

    ALTER TABLE Music.Album
       WITH CHECK ADD CONSTRAINT
           chkMusic_Album$CatalogNumber$CatalogNumberValidate
           CHECK (Music.Publisher$CatalogNumbervalidate
                              (CatalogNumber,PublisherId) = 1)
    GO

    --to find where your data is not ready for the constraint,
    --you run the following query
    SELECT Album.Name, Album.CatalogNumber, Publisher.CatalogNumberMask
    FROM Music.Album AS Album
           JOIN Music.Publisher AS Publisher
             on Publisher.PublisherId = Album.PublisherId
    WHERE Music.Publisher$CatalogNumbervalidate
                            (Album.CatalogNumber,Album.PublisherId) <> 1
    GO
    INSERT  Music.Album(AlbumId, Name, ArtistId, PublisherId, CatalogNumber)
    VALUES  (4,'who''s next',2,2,'1')
    GO
    INSERT  Music.Album(AlbumId, Name, ArtistId, CatalogNumber, PublisherId)
    VALUES  (4,'who''s next',2,'AC12345',2)

    SELECT * FROM Music.Album
    GO

    SELECT *
    FROM   Music.Album AS Album
              JOIN Music.Publisher AS Publisher
                    on Publisher.PublisherId = Album.PublisherId
    WHERE  Music.Publisher$CatalogNumbervalidate
                            (Album.CatalogNumber,Album.PublisherId) <> 1
    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - Declarative Data Protection
    --  - CHECK Constraints Based on Functions - Example Constraint That Accesses Other Rows (Cardinality Enforcement)
    ------------------------------------------------------------------------------------------------------
    CREATE SCHEMA alt
    go
    CREATE TABLE alt.employee
    (
        employeeId    int NOT NULL CONSTRAINT PKalt_employee PRIMARY KEY,
        employeeNumber char(4) NOT NULL
                          CONSTRAINT AKalt_employee_employeeNumber UNIQUE
    )
    CREATE TABLE alt.office
    (
        officeId int NOT NULL CONSTRAINT PKalt_office PRIMARY KEY,
        officeNumber char(4) NOT NULL
                         CONSTRAINT AKalt_office_officeNumber UNIQUE,
    )
    GO

    CREATE TABLE alt.employeeOfficeAssignment
    (
           employeeId int,
           officeId  int,
           CONSTRAINT PKalt_employeeOfficeAssignment
                    PRIMARY KEY (employeeId, officeId),
           CONSTRAINT FKemployeeOfficeAssignment$assignsAnOfficeTo$employee
                    FOREIGN KEY (employeeId) REFERENCES alt.employee(employeeId),
           CONSTRAINT FKemployeeOfficeAssignment$assignsAnOfficeTo$officeId
                    FOREIGN KEY (officeId) REFERENCES alt.office(officeId)
    )

    GO

    ALTER TABLE alt.employeeOfficeAssignment
        ADD CONSTRAINT AKemployeeOfficeAssignment_employee UNIQUE (employeeId)
    GO

    INSERT alt.employee(employeeId, employeeNumber)
    VALUES (1,'A001'),
           (2,'A002'),
           (3,'A003')

    INSERT INTO alt.office(officeId,officeNumber)
    VALUES (1,'3001'),
           (2,'3002'),
           (3,'3003')
    GO

    CREATE FUNCTION alt.employeeOfficeAssignment$officeEmployeeCount
    ( @officeId int)
    RETURNS int AS
     BEGIN
        RETURN (SELECT count(*)
                FROM   alt.employeeOfficeAssignment
                WHERE  officeId = @officeId
                )
      END
    GO

    ALTER TABLE alt.employeeOfficeAssignment
        ADD CONSTRAINT CHKalt_employeeOfficeAssignment_employeesInOfficeTwoOrLess
             CHECK (alt.employeeOfficeAssignment$officeEmployeeCount(officeId) <= 2)
    GO

    INSERT alt.employeeOfficeAssignment(officeId, employeeId)
    VALUES (1,1)
    GO
    INSERT alt.employeeOfficeAssignment(officeId, employeeId)
    VALUES (1,2)
    GO

    INSERT alt.employeeOfficeAssignment(officeId, employeeId)
    VALUES (1,3)
    GO
    INSERT alt.employeeOfficeAssignment(officeId, employeeId)
    VALUES (2,3)
    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - Declarative Data Protection
    --  - CHECK Constraints Based on Functions - Errors Caused by Constraints
    ------------------------------------------------------------------------------------------------------

    CREATE SCHEMA utility
    CREATE TABLE utility.ErrorMap
    (
        ConstraintName sysname primary key,
        Message         varchar(2000)
    )
    go
    INSERT utility.ErrorMap(constraintName, message)
    VALUES ('chkMusic_Album$CatalogNumber$CatalogNumberValidate',
            'The catalog number does not match the format set up by the Publisher')
    GO

    CREATE PROCEDURE utility.ErrorMap$MapError
    (
        @ErrorNumber  int = NULL,
        @ErrorMessage nvarchar(2000) = NULL,
        @ErrorSeverity INT= NULL,
        @ErrorState INT = NULL
    ) AS
      BEGIN
        --use values in ERROR_ functions unless the user passes in values
        SET @ErrorNumber = Coalesce(@ErrorNumber, ERROR_NUMBER())
        SET @ErrorMessage = Coalesce(@ErrorMessage, ERROR_MESSAGE())
        SET @ErrorSeverity = Coalesce(@ErrorSeverity, ERROR_SEVERITY())
        SET @ErrorState = Coalesce(@ErrorState,ERROR_STATE())

        DECLARE @originalMessage nvarchar(2000)
        SET @originalMessage = ERROR_MESSAGE()


        IF @ErrorNumber = 547
          BEGIN
            SET @ErrorMessage =
                            (SELECT message
                             FROM   utility.ErrorMap
                             WHERE  constraintName =
             --this substring pulls the constraint name from the message
             substring( @ErrorMessage,CHARINDEX('constraint "',@ErrorMessage) + 12,
                                 charindex('"',substring(@ErrorMessage,
                                 CHARINDEX('constraint "',@ErrorMessage) +
                                                                    12,2000))-1)
                                )      END
        ELSE
            SET @ErrorMessage = @ErrorMessage

        SET @ErrorState = CASE when @ErrorState = 0 THEN 1 ELSE @ErrorState END

        --if the error was not found, get the original message
        SET @ErrorMessage = isNull(@ErrorMessage, @originalMessage)
        RAISERROR (@ErrorMessage, @ErrorSeverity,@ErrorState )
      END
    GO
    BEGIN TRY
         INSERT  Music.Album(AlbumId, Name, ArtistId, CatalogNumber, PublisherId)
         VALUES  (5,'who are you',2,'badnumber',2)
    END TRY
    BEGIN CATCH
        EXEC utility.ErrorMap$MapError
    END CATCH
    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - AFTER Triggers
    ------------------------------------------------------------------------------------------------------
    /*
    CREATE TRIGGER <schema>.<tablename>$<actions>[<purpose>]Trigger
    ON <schema>.<tablename>
    AFTER <comma delimited actions> AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET NOCOUNT ON --to avoid the rowcount messages
       SET ROWCOUNT 0 --in case the client has modified the rowcount

       BEGIN TRY
              --[validation section]
              --[modification section]
       END TRY
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE nvarchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    */
    CREATE TABLE utility.ErrorLog(
            ERROR_NUMBER int NOT NULL,
            ERROR_LOCATION sysname NOT NULL,
            ERROR_MESSAGE varchar(4000),
            ERROR_DATE datetime NULL
                  CONSTRAINT dfltErrorLog_error_date  DEFAULT (getdate()),
            ERROR_USER sysname NOT NULL
                  --use original_login to capture the user name of the actual user
                  --not a user they have impersonated
                  CONSTRAINT dfltErrorLog_error_user_name DEFAULT (original_login())
    )
    GO
    CREATE PROCEDURE utility.ErrorLog$insert
    (
            @ERROR_NUMBER int = NULL,
            @ERROR_LOCATION sysname = NULL,
            @ERROR_MESSAGE varchar(4000) = NULL
    ) as
     BEGIN
            BEGIN TRY
               INSERT INTO utility.ErrorLog(ERROR_NUMBER,
                                             ERROR_LOCATION, ERROR_MESSAGE)
               SELECT isnull(@ERROR_NUMBER,ERROR_NUMBER()),
                      isnull(@ERROR_LOCATION,ERROR_MESSAGE()),
                      isnull(@ERROR_MESSAGE,ERROR_MESSAGE())
            END TRY
            BEGIN CATCH
               INSERT INTO utility.ErrorLog(ERROR_NUMBER,
                                             ERROR_LOCATION, ERROR_MESSAGE)
               VALUES (-100, 'utility.ErrorLog$insert',
                            'An invalid call was made to the error log procedure')
            END CATCH
    END
    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - AFTER Triggers
    --   - Range Checks on Multiple Rows
    ------------------------------------------------------------------------------------------------------
    CREATE SCHEMA Accounting
    GO
    CREATE TABLE Accounting.Account
    (
            AccountNumber        char(10)
                      constraint PKAccounting_Account primary key
            --would have other columns
    )

    CREATE TABLE Accounting.AccountActivity
    (
            AccountNumber                char(10)
                constraint Accounting_Account$has$Accounting_AccountActivity
                           foreign key references Accounting.Account(AccountNumber),
           --this might be a value that each ATM/Teller generates
            TransactionNumber            char(20),
            Date                         datetime,
            TransactionAmount            numeric(12,2),
            constraint PKAccounting_AccountActivity
                          PRIMARY KEY (AccountNumber, TransactionNumber)
    )
    GO

    CREATE TRIGGER Accounting.AccountActivity$insertUpdateTrigger
    ON Accounting.AccountActivity
    AFTER INSERT,UPDATE AS
    BEGIN
       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET NOCOUNT ON
       SET ROWCOUNT 0 --in case the client has modified the rowcount

       BEGIN TRY

       --[validation section]
       --disallow Transactions that would put balance into negatives
       IF EXISTS ( SELECT AccountNumber
                   FROM Accounting.AccountActivity as AccountActivity
                   WHERE EXISTS (SELECT *
                                 FROM   inserted
                                 WHERE  inserted.AccountNumber =
                                   AccountActivity.AccountNumber)
                       GROUP BY AccountNumber
                       HAVING sum(TransactionAmount) < 0)
          BEGIN
             IF @rowsAffected = 1
                 SELECT @msg = 'Account: ' + AccountNumber +
                      ' TransactionNumber:' +
                       cast(TransactionNumber as varchar(36)) +
                       ' for amount: ' + cast(TransactionAmount as varchar(10))+
                       ' cannot be processed as it will cause a negative balance'
                 FROM   inserted
            ELSE
              SELECT @msg = 'One of the rows caused a negative balance'
             RAISERROR (@msg, 16, 1)
          END

       --[modification section]
       END TRY
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE varchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    GO

    --create some set up test data
    INSERT into Accounting.Account(AccountNumber)
    VALUES ('1111111111')

    GO
    INSERT  into Accounting.AccountActivity(AccountNumber, TransactionNumber,
                                             Date, TransactionAmount)
    VALUES ('1111111111','A0000000000000000001','20050712',100),
     ('1111111111','A0000000000000000002','20050713',100)
    GO
    INSERT  into Accounting.AccountActivity(AccountNumber, TransactionNumber,
                                             Date, TransactionAmount)
    VALUES ('1111111111','A0000000000000000003','20050713',-300)
    GO

    --create new Account
    INSERT  into Accounting.Account(AccountNumber)
    VALUES ('2222222222')
    GO
    --Now, this data will violate the constraint for the new Account:
    INSERT  into Accounting.AccountActivity(AccountNumber, TransactionNumber,
                                            Date, TransactionAmount)
    VALUES ('1111111111','A0000000000000000004','20050714',100),
           ('2222222222','A0000000000000000005','20050715',100),
           ('2222222222','A0000000000000000006','20050715',100),
           ('2222222222','A0000000000000000007','20050715',-201)

    GO

    --Viewing trigger events

    SELECT sys.trigger_events.type_desc
    FROM sys.trigger_events
             JOIN sys.triggers
                      ON sys.triggers.object_id = sys.trigger_events.object_id
    WHERE sys.triggers.name = 'AccountActivity$insertUpdateTrigger'

    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - AFTER Triggers
    --   - Maintaining Summary Values
    ------------------------------------------------------------------------------------------------------
    ALTER TABLE Accounting.Account
       ADD Balance numeric(12,2)
          CONSTRAINT DfltAccounting_Account_Balance DEFAULT(0.00)

    GO
    SELECT  Account.AccountNumber,
            SUM(coalesce(TransactionAmount,0.00)) AS NewBalance
    FROM   Accounting.Account
            LEFT OUTER JOIN Accounting.AccountActivity
                ON Account.AccountNumber = AccountActivity.AccountNumber
    GROUP  BY Account.AccountNumber
    GO

    WITH  Updater as (
    SELECT  Account.AccountNumber,
            SUM(coalesce(TransactionAmount,0.00)) as NewBalance
    FROM   Accounting.Account
            LEFT OUTER JOIN Accounting.AccountActivity
                On Account.AccountNumber = AccountActivity.AccountNumber
    GROUP  BY Account.AccountNumber, Account.Balance)
    UPDATE Account
    SET    Balance = Updater.NewBalance
    FROM   Accounting.Account
             JOIN Updater
                    on Account.AccountNumber = Updater.AccountNumber
    GO

    ALTER TRIGGER Accounting.AccountActivity$insertUpdateTrigger
    ON Accounting.AccountActivity
    AFTER INSERT,UPDATE AS
    BEGIN
       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET NOCOUNT ON
       SET ROWCOUNT 0 --in case the client has modified the rowcount

       BEGIN TRY

       --[validation section]
       --disallow Transactions that would put balance into negatives
       IF EXISTS ( SELECT AccountNumber
                   FROM Accounting.AccountActivity as AccountActivity
                   WHERE EXISTS (SELECT *
                                 FROM   inserted
                                 WHERE  inserted.AccountNumber =
                                   AccountActivity.AccountNumber)
                       GROUP BY AccountNumber
                       HAVING sum(TransactionAmount) < 0)
          BEGIN
             IF @rowsAffected = 1
                 SELECT @msg = 'Account: ' + AccountNumber +
                      ' TransactionNumber:' +
                       cast(TransactionNumber as varchar(36)) +
                       ' for amount: ' + cast(TransactionAmount as varchar(10))+
                       ' cannot be processed as it will cause a negative balance'
                 FROM   inserted
            ELSE
              SELECT @msg = 'One of the rows caused a negative balance'
             RAISERROR (@msg, 16, 1)
          END

        --[modification section]
        IF UPDATE (TransactionAmount)
            WITH  Updater as (
            SELECT  Account.AccountNumber,
                    SUM(coalesce(TransactionAmount,0.00)) as NewBalance
            FROM   Accounting.Account
                    LEFT OUTER JOIN Accounting.AccountActivity
                        On Account.AccountNumber = AccountActivity.AccountNumber
                   --This where clause limits the summarizations to those rows
                   --that were modified by the DML statement that caused
                   --this trigger to fire.
            WHERE  EXISTS (SELECT *
                           FROM   Inserted
                           WHERE  Account.AccountNumber = Inserted.AccountNumber)
            GROUP  BY Account.AccountNumber, Account.Balance)
            UPDATE Account
            SET    Balance = Updater.NewBalance
            FROM   Accounting.Account
                      JOIN Updater
                          on Account.AccountNumber = Updater.AccountNumber

       END TRY
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE varchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    GO
    INSERT  into Accounting.AccountActivity(AccountNumber, TransactionNumber,
                                            Date, TransactionAmount)
    VALUES ('1111111111','A0000000000000000004','20050714',100)
    GO

    SELECT  Account.AccountNumber,
            SUM(coalesce(TransactionAmount,0.00)) AS NewBalance
    FROM   Accounting.Account
            LEFT OUTER JOIN Accounting.AccountActivity
                ON Account.AccountNumber = AccountActivity.AccountNumber
    GROUP  BY Account.AccountNumber
    GO

    INSERT  into Accounting.AccountActivity(AccountNumber, TransactionNumber,
                                            Date, TransactionAmount)
    VALUES ('1111111111','A0000000000000000005','20050714',100),
           ('2222222222','A0000000000000000006','20050715',100),
           ('2222222222','A0000000000000000007','20050715',100)
    GO

    SELECT  Account.AccountNumber,
            SUM(coalesce(TransactionAmount,0.00)) AS NewBalance
    FROM   Accounting.Account
            LEFT OUTER JOIN Accounting.AccountActivity
                ON Account.AccountNumber = AccountActivity.AccountNumber
    GROUP  BY Account.AccountNumber
    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - AFTER Triggers
    --   - Cascading Inserts
    ------------------------------------------------------------------------------------------------------

    CREATE SCHEMA Internet
    go
    CREATE TABLE Internet.Url
    (
        UrlId int not null identity(1,1) constraint PKUrl primary key,
        Name  varchar(60) not null constraint AKInternet_Url_Name UNIQUE,
        Url   varchar(200) not null constraint AKInternet_Url_Url UNIQUE
    )

    --Not a user manageable table, so not using identity key (as discussed in
    --Chapter 5 when I discussed choosing keys) in this one table.  Others are
    --using identity-based keys in this example
    CREATE TABLE Internet.UrlStatusType
    (
            UrlStatusTypeId  int not null
                          CONSTRAINT PKInternet_UrlStatusType PRIMARY KEY,
            Name varchar(20) NOT NULL
                          CONSTRAINT AKInternet_UrlStatusType UNIQUE,
            DefaultFlag bit NOT NULL,
            DisplayOnSiteFlag bit NOT NULL
    )

    CREATE TABLE Internet.UrlStatus
    (
            UrlStatusId int not null identity(1,1)
                          CONSTRAINT PKInternet_UrlStatus PRIMARY KEY,
            UrlStatusTypeId int NOT NULL
                          CONSTRAINT
                   Internet_UrlStatusType$defines_status_type_of$Internet_UrlStatus
                          REFERENCES Internet.UrlStatusType(UrlStatusTypeId),
            UrlId int NOT NULL
              CONSTRAINT Internet_Url$has_status_history_in$Internet_UrlStatus
                          REFERENCES Internet.Url(UrlId),
            ActiveTime        datetime,
            CONSTRAINT AKInternet_UrlStatus_statusUrlDate
                          UNIQUE (UrlStatusTypeId, UrlId, ActiveTime)
    )
    --set up status types
    INSERT  Internet.UrlStatusType (UrlStatusTypeId, Name,
                                       DefaultFlag, DisplayOnSiteFlag)
    VALUES (1, 'Unverified',1,0),
           (2, 'Verified',0,1),
           (3, 'Unable to locate',0,0)
    GO

    CREATE TRIGGER Internet.Url$afterInsertTrigger
    ON Internet.Url
    AFTER INSERT AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET NOCOUNT ON --to avoid the rowcount messages
       SET ROWCOUNT 0 --in case the client has modified the rowcount

       BEGIN TRY
              --[validation section]

               --[modification section]

              --add a row to the UrlStatus table to tell it that the new row
              --should start out as the default status
              INSERT INTO Internet.UrlStatus (UrlId, UrlStatusTypeId, ActiveTime)
              SELECT INSERTED.UrlId, UrlStatusType.UrlStatusTypeId,
                      current_timestamp
              FROM INSERTED
                    CROSS JOIN (SELECT UrlStatusTypeId
                                FROM   UrlStatusType
                                WHERE  DefaultFlag = 1)  as UrlStatusType
                                               --use cross join with a WHERE clause
                                               --as this is not technically a join
                                               --between INSERTED and UrlType
       END TRY
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  --or this will not get rolled back
                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE varchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    GO
    INSERT  into Internet.Url(Name, Url)
    VALUES ('More info can be found here',
            'http://sqlblog.com/blogs/louis_davidson/default.aspx')

    SELECT * FROM Internet.Url
    SELECT * FROM Internet.UrlStatus

    GO
    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - AFTER Triggers
    --   - Cascading from Child to Parent
    ------------------------------------------------------------------------------------------------------
    --start a schema for entertainment-related tables
    CREATE SCHEMA Entertainment
    go
    CREATE TABLE Entertainment.GamePlatform
    (
        GamePlatformId int CONSTRAINT PKGamePlatform PRIMARY KEY,
        Name  varchar(20) CONSTRAINT AKGamePlatform_Name UNIQUE
    )
    CREATE TABLE Entertainment.Game
    (
        GameId  int CONSTRAINT PKGame PRIMARY KEY,
        Name    varchar(20) CONSTRAINT AKGame_Name UNIQUE
        --more details that are common to all platforms
    )

    --associative entity with cascade relationships back to Game and GamePlatform
    CREATE TABLE Entertainment.GameInstance
    (
        GamePlatformId int,
        GameId int,
        PurchaseDate date,
        CONSTRAINT PKGameInstance PRIMARY KEY (GamePlatformId, GameId),
        CONSTRAINT
        Entertainment_Game$is_owned_on_platform_by$Entertainment_GameInstance
          FOREIGN KEY (GameId)REFERENCES Entertainment.Game(GameId)
                                                   ON DELETE CASCADE,
          CONSTRAINT
            Entertainment_GamePlatform$is_linked_to$Entertainment_GameInstance
          FOREIGN KEY (GamePlatformId)
               REFERENCES Entertainment.GamePlatform(GamePlatformId)
                    ON DELETE CASCADE
    )
    GO
    INSERT  into Entertainment.Game (GameId, Name)
    VALUES (1,'Super Mario Bros'),
           (2,'Legend Of Zelda')

    INSERT  into Entertainment.GamePlatform(GamePlatformId, Name)
    VALUES (1,'Nintendo Wii'),   --Yes, as a matter of fact I am a
           (2,'Nintendo DS')     --Nintendo Fanboy, why do you ask?

    INSERT  into Entertainment.GameInstance(GamePlatformId, GameId, PurchaseDate)
    VALUES (1,1,'20060404'),
           (1,2,'20070510'),
           (2,2,'20070404')

    --the full outer joins ensure that all rows are returned from all sets, leaving
    --nulls where data is missing
    SELECT  GamePlatform.Name as Platform, Game.Name as Game, GameInstance. PurchaseDate
    FROM    Entertainment.Game as Game
                full outer join Entertainment.GameInstance as GameInstance
                        on Game.GameId = GameInstance.GameId
                full outer join Entertainment.GamePlatform
                        on GamePlatform.GamePlatformId = GameInstance.GamePlatformId

    GO

    CREATE TRIGGER Entertainment.GameInstance$afterDeleteTrigger
    ON Entertainment.GameInstance
    AFTER delete AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET NOCOUNT ON --to avoid the rowcount messages
       SET ROWCOUNT 0 --in case the client has modified the rowcount

       BEGIN TRY
            --[validation section]

            --[modification section]
            --delete all Games
            DELETE Game       --where the GameInstance was delete
            WHERE  GameId in (SELECT deleted.GameId
                              FROM   deleted     --and there are no GameInstances
                               WHERE  not exists (SELECT  *        --left
                                                  FROM    GameInstance
                                                  WHERE   GameInstance.GameId =
                                                                   deleted.GameId))
       END TRY
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE varchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    GO
    DELETE  Entertainment.GamePlatform
    WHERE   GamePlatformId = 1
    go
    SELECT  GamePlatform.Name as platform, Game.Name as Game, GameInstance. PurchaseDate
    FROM    Entertainment.Game as Game
                FULL OUTER JOIN Entertainment.GameInstance as GameInstance
                        on Game.GameId = GameInstance.GameId
                FULL OUTER JOIN Entertainment.GamePlatform
                        on GamePlatform.GamePlatformId = GameInstance.GamePlatformId
    GO




    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - AFTER Triggers
    --   - Maintaining an Audit Trail
    ------------------------------------------------------------------------------------------------------
    CREATE SCHEMA hr
    go
    CREATE TABLE hr.employee
    (
        employee_id char(6) CONSTRAINT PKhr_employee PRIMARY KEY,
        first_name  varchar(20),
        last_name   varchar(20),
        salary      money
    )
    CREATE TABLE hr.employee_auditTrail
    (
        employee_id          char(6),
        date_changed         datetime not null --default so we don't have to
                                               --code for it
              CONSTRAINT DfltHr_employee_date_changed DEFAULT (current_timestamp),
        first_name           varchar(20),
        last_name            varchar(20),
        salary               decimal(12,2),
        --the following are the added columns to the original
        --structure of hr.employee
        action               char(6)
              CONSTRAINT ChkHr_employee_action --we don't log inserts, only changes
                                              CHECK(action in ('delete','update')),
        changed_by_user_name sysname
                    CONSTRAINT DfltHr_employee_changed_by_user_name
                                              DEFAULT (original_login()),
        CONSTRAINT PKemployee_auditTrail PRIMARY KEY (employee_id, date_changed)
    )
    GO

    CREATE TRIGGER hr.employee$insertAndDeleteAuditTrailTrigger
    ON hr.employee
    AFTER UPDATE, DELETE AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET NOCOUNT ON --to avoid the rowcount messages
       SET ROWCOUNT 0 --in case the client has modified the rowcount
       BEGIN TRY
              --[validation section]
              --[modification section]
              --since we are only doing update and delete, we just
              --need to see if there are any rows
              --inserted to determine what action is being done.
              DECLARE @action char(6)
              SET @action = case when (SELECT count(*) from inserted) > 0
                            then 'update' else 'delete' end

              --since the deleted table contains all changes, we just insert all
              --of the rows in the deleted table and we are done.
              INSERT employee_auditTrail (employee_id, first_name, last_name,
                                         salary, action)
              SELECT employee_id, first_name, last_name, salary, @action
              FROM   deleted

       END TRY
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE varchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    GO
    INSERT hr.employee (employee_id, first_name, last_name, salary)
    VALUES (1, ' Phillip','Taibul',10000)
    GO
    UPDATE hr.employee
    SET salary = salary * 1.10 --ten percent raise!
    WHERE employee_id = 1

    SELECT *
    FROM   hr.employee
    GO
    SELECT *
    FROM   hr.employee_auditTrail
    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - INSTEAD OF Triggers
    ------------------------------------------------------------------------------------------------------

    /*
    CREATE TRIGGER <schema>.<tablename>$InsteadOf<actions>[<purpose>]Trigger
    ON <schema>.<tablename>
    INSTEAD OF <comma delimited actions> AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET NOCOUNT ON --to avoid the rowcount messages
       SET ROWCOUNT 0 --in case the client has modified the rowcount

       BEGIN TRY
              --[validation section]
              --[modification section]
              --<perform action>
       END TRY
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE nvarchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    */
    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - INSTEAD OF Triggers
    --   - Automatically Maintaining Columns
    ------------------------------------------------------------------------------------------------------
    CREATE SCHEMA school
    Go
    CREATE TABLE school.student
    (
          studentId       int identity not null
                CONSTRAINT PKschool_student PRIMARY KEY,
          studentIdNumber char(8) not null
                CONSTRAINT AKschool_student_studentIdNumber UNIQUE,
          firstName       varchar(20) not null,
          lastName        varchar(20) not null,
    --Note that we add these columns to the implementation model, not to the logical
    --model. These columns do not actually refer to the student being modeled, they are
    --required simply to help with programming and tracking.
          rowCreateDate   datetime not null
                CONSTRAINT dfltSchool_student_rowCreateDate
                                     DEFAULT (current_timestamp),
          rowCreateUser   sysname not null
                CONSTRAINT dfltSchool_student_rowCreateUser DEFAULT (current_user)
    )
    GO

    CREATE TRIGGER school.student$insteadOfInsert
    ON school.student
    INSTEAD OF INSERT AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET ROWCOUNT 0 --in case the client has modified the rowcount
       SET NOCOUNT ON --to avoid the rowcount messages

       BEGIN TRY
              --[validation section]
              --[modification section]
              --<perform action>
              INSERT INTO school.student(studentIdNumber, firstName, lastName,
                                         rowCreateDate, rowCreateUser)
              SELECT studentIdNumber, firstName, lastName,
                                            current_timestamp, suser_sname()
              FROM  inserted   --no matter what the user put in the inserted row
       END TRY         --when the row was created, these values will be inserted
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE nvarchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    GO
    INSERT  into school.student(studentIdNumber, firstName, lastName)
    VALUES ( '0000001',' Gray', ' Tezine' )

    GO
    SELECT * FROM school.student
    GO
    INSERT  school.student(studentIdNumber, firstName, lastName, rowCreateDate,
                           rowCreateUser)
    VALUES ( '000002','Norm', 'Ull','99990101','some user' )
    GO
    SELECT * FROM school.student
    GO

    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - INSTEAD OF Triggers
    --   - Formatting User Input
    ------------------------------------------------------------------------------------------------------
    CREATE FUNCTION Utility.TitleCase
    (
       @inputString varchar(2000)
    )
    RETURNS varchar(2000) AS
    BEGIN
       -- set the whole string to lower
       SET @inputString = LOWER(@inputstring)
       -- then use stuff to replace the first character
       SET @inputString =
       --STUFF in the uppercased character in to the next character,
       --replacing the lowercased letter
       STUFF(@inputString,1,1,UPPER(SUBSTRING(@inputString,1,1)))

       --@i is for the loop counter, initialized to 2
       DECLARE @i int
       SET @i = 1

       --loop from the second character to the end of the string
       WHILE @i < LEN(@inputString)
       BEGIN
          --if the character is a space
          IF SUBSTRING(@inputString,@i,1) = ' '
          BEGIN
             --STUFF in the uppercased character into the next character
             SET @inputString = STUFF(@inputString,@i +
             1,1,UPPER(SUBSTRING(@inputString,@i + 1,1)))
          END
          --increment the loop counter
          SET @i = @i + 1
       END
       RETURN @inputString
    END
    GO

    ALTER TRIGGER school.student$insteadOfInsert
    ON school.student
    INSTEAD OF INSERT AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET ROWCOUNT 0 --in case the client has modified the rowcount
       SET NOCOUNT ON --to avoid the rowcount messages

       BEGIN TRY
              --[validation section]
              --[modification section]
              --<perform action>
              INSERT INTO school.student(studentIdNumber, firstName, lastName,
                                         rowCreateDate, rowCreateUser)

              SELECT studentIdNumber,
                     Utility.titleCase(firstName),
                     Utility.titleCase(lastName),
                     current_timestamp, suser_sname()
              FROM  inserted   --no matter what the user put in the inserted row
       END TRY                 --when the row was created, these values will be inserted
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE nvarchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    GO

    INSERT school.student(studentIdNumber, firstName, lastName)
    VALUES ( '0000007','CaPtain', 'von nuLLY')
    GO

    SELECT *
    FROM school.student

    GO
    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - INSTEAD OF Triggers
    --   - Redirecting Invalid Data to an Exception Table
    ------------------------------------------------------------------------------------------------------

    CREATE SCHEMA Measurements
    go
    CREATE TABLE Measurements.WeatherReading
    (
        WeatherReadingId int identity
              CONSTRAINT PKWeatherReading PRIMARY KEY,
        ReadingTime   datetime
              CONSTRAINT AKMeasurements_WeatherReading_Date UNIQUE,
        Temperature     float
              CONSTRAINT chkMeasurements_WeatherReading_Temperature
                          CHECK(Temperature between -80 and 150)
                          --raised from last edition for global warming
    )
    GO
    INSERT  into Measurements.WeatherReading (ReadingTime, Temperature)
    VALUES ('20080101 0:00',82.00), ('20080101 0:01',89.22),
           ('20080101 0:02',600.32),('20080101 0:03',88.22),
           ('20080101 0:04',99.01)
    GO

    CREATE TABLE Measurements.WeatherReading_exception
    (
        WeatherReadingId  int identity
              CONSTRAINT PKMeasurements_WeatherReading_exception PRIMARY KEY,
        ReadingTime       datetime,
        Temperature       float
    )
    GO

    CREATE TRIGGER Measurements.WeatherReading$InsteadOfInsertTrigger
    ON Measurements.WeatherReading
    INSTEAD OF INSERT AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount

       --no need to continue on if no rows affected
       IF @rowsAffected = 0 return

       SET NOCOUNT ON --to avoid the rowcount messages
       SET ROWCOUNT 0 --in case the client has modified the rowcount

       BEGIN TRY
              --[validation section]
              --[modification section]

              --<perform action>

               --BAD data
              INSERT Measurements.WeatherReading_exception
                                         (ReadingTime, Temperature)
              SELECT ReadingTime, Temperature
              FROM   inserted
              WHERE  NOT(Temperature between -80 and 120)

               --GOOD data
              INSERT Measurements.WeatherReading (ReadingTime, Temperature)
              SELECT ReadingTime, Temperature
              FROM   inserted
              WHERE  (Temperature between -80 and 120)
       END TRY
       BEGIN CATCH
                  IF @@trancount > 0
                      ROLLBACK TRANSACTION

                  EXECUTE utility.ErrorLog$insert

                  DECLARE @ERROR_MESSAGE nvarchar(4000)
                  SET @ERROR_MESSAGE = ERROR_MESSAGE()
                  RAISERROR (@ERROR_MESSAGE,16,1)

         END CATCH
    END
    GO

    INSERT  into Measurements.WeatherReading (ReadingTime, Temperature)
    VALUES ('20080101 0:00',82.00), ('20080101 0:01',89.22),
           ('20080101 0:02',600.32),('20080101 0:03',88.22),
           ('20080101 0:04',99.01)

    SELECT *
    FROM Measurements.WeatherReading
    GO

    SELECT *
    FROM   Measurements.WeatherReading_exception
    GO



    ------------------------------------------------------------------------------------------------------
    -- Automatic Data Protection - DML Triggers - INSTEAD OF Triggers
    --   - Forcing No Action to Be Performed on a Table
    ------------------------------------------------------------------------------------------------------

    CREATE SCHEMA System
    go
    CREATE TABLE System.Version
    (
        DatabaseVersion varchar(10)
    )
    INSERT  into System.Version (DatabaseVersion)
    VALUES ('1.0.12')
    GO

    CREATE TRIGGER System.Version$InsteadOfInsertUpdateDeleteTrigger
    ON System.Version
    INSTEAD OF INSERT, UPDATE, DELETE AS
    BEGIN

       DECLARE @rowsAffected int,    --stores the number of rows affected
               @msg varchar(2000)    --used to hold the error message

       SET @rowsAffected = @@rowcount
       --no need to complain if no rows affected
       IF @rowsAffected = 0 return

       --No error handling necessary, just the message.
       --We just put the kibosh on the action.
       RAISERROR
          ('The System.Version table may not be modified in production',
            16,1)
    END

    GO

    delete system.version
    GO
    ALTER TABLE system.version
        DISABLE TRIGGER version$InsteadOfInsertUpdateDelete
    Go
    --------------------------------------------------------------------------------------------------
    -- Handing Errors from Triggers and Constraints
    --------------------------------------------------------------------------------------------------
    CREATE TABLE alt.errorHandlingTest
    (
        errorHandlingTestId   int CONSTRAINT PKerrorHandlingTest PRIMARY KEY,
        CONSTRAINT ChkAlt_errorHandlingTest_errorHandlingTestId_greaterThanZero
               CHECK (errorHandlingTestId > 0)
    )
    GO

    CREATE TRIGGER alt.errorHandlingTest$afterInsertTrigger
    ON alt.errorHandlingTest
    AFTER INSERT
    AS

        RAISERROR ('Test Error',16,1)
        ROLLBACK TRANSACTION
    GO

    --NO Transaction, Constraint Error
    INSERT alt.errorHandlingTest
    VALUES (-1)
    SELECT 'continues'
    GO

    INSERT alt.errorHandlingTest
    VALUES (1)
    SELECT 'continues'
    GO

    BEGIN TRANSACTION
    BEGIN TRY
        INSERT alt.errorHandlingTest
        VALUES (-1)
        COMMIT
    END TRY
    BEGIN CATCH
        SELECT  CASE XACT_STATE()
                    WHEN 1 THEN 'Committable'
                    WHEN 0 THEN 'No transaction'
                    ELSE 'Uncommitable tran' END as XACT_STATE
                ,ERROR_NUMBER() AS ErrorNumber
                ,ERROR_MESSAGE() as ErrorMessage
        ROLLBACK TRANSACTION
    END CATCH
    GO

    BEGIN TRANSACTION
    BEGIN TRY
        INSERT alt.errorHandlingTest
        VALUES (1)
        COMMIT
    END TRY
    BEGIN CATCH
        SELECT  CASE XACT_STATE()
                    WHEN 1 THEN 'Committable'
                    WHEN 0 THEN 'No transaction'
                    ELSE 'Uncommitable tran' END as XACT_STATE
                ,ERROR_NUMBER() AS ErrorNumber
                ,ERROR_MESSAGE() as ErrorMessage
        ROLLBACK TRANSACTION
    END CATCH
    GO


    BEGIN TRY
        DECLARE @errorMessage nvarchar(4000)
        SET @errorMessage = 'Error inserting data into alt.errorHandlingTest'
        INSERT alt.errorHandlingTest
        VALUES (1)
        COMMIT TRANSACTION
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION

        --I also add in the stored procedure or trigger where the error
        --occurred also when in a coded object
        SET @errorMessage = Coalesce(@errorMessage,'') +
              ' ( System Error: ' + CAST(ERROR_NUMBER() as varchar(10)) +
              ':' + ERROR_MESSAGE() + ': Line Number:' +
              CAST(ERROR_LINE() as varchar(10)) + ')'
        RAISERROR (@errorMessage,16,1)
    END CATCH

  • 相关阅读:
    git 无法提交到远程服务器【转载】
    vscode 常用快捷键
    mongodb nodejs一个有自增id的功能
    C++ lambda表达式与函数对象
    TypeScript的async, await, promise,多参数的调用比较(第2篇)
    了解TypeScript的async,await,promise(第1篇)
    TyepScript判断一个变量是null, or undefined
    MongoClient 对 Mongodb的 增删改查 操作
    TypeScript第一个Promise程序
    C++基类的继承和多态
  • 原文地址:https://www.cnblogs.com/shihao/p/2508821.html
Copyright © 2011-2022 走看看