# SQL Server Stored Procedure ![image](https://hackmd.io/_uploads/rkbOEPqYp.png) ```sql! -- Create USE AdventureWorks2017 GO -- begin a new batch CREATE PROCEDURE sp_product_info AS -- 關鍵字後面的部分是儲存程序的主體 BEGIN SELECT * FROM Production.Product p ORDER BY p.Name DESC END -- Alter USE AdventureWorks2017 GO -- begin a new batch ALTER PROCEDURE sp_product_info AS -- 關鍵字後面的部分是儲存程序的主體 BEGIN SELECT * FROM Production.Product p ORDER BY p.Name ASC END -- Delete DROP PROC sp_product_info ``` ```sql! EXEC sp_product_info ``` # Parameters ![image](https://hackmd.io/_uploads/HJMcCj9Fa.png) ```sql! USE AdventureWorks2017 GO ALTER PROCEDURE [dbo].[spProductList] ( @MinListPrice AS INT, @MaxListPrice AS INT ) AS BEGIN SELECT Name, ListPrice FROM Production.Product WHERE ListPrice >= @MinListPrice AND ListPrice <= @MaxListPrice ORDER BY Name END -- EXEC EXEC spProductList 500, 1000 -- Naming the parameters before the value will not affect how the procedure works EXEC spProductList @MinListPrice = 500, @MaxListPrice = 1000 -- ADD TEXT Parameter USE AdventureWorks2017 GO ALTER PROCEDURE [dbo].[spProductList] ( @MinListPrice AS INT, @MaxListPrice AS INT, @ProdName AS VARCHAR(MAX) ) AS BEGIN SELECT Name, ListPrice FROM Production.Product WHERE ListPrice >= @MinListPrice AND ListPrice <= @MaxListPrice AND Name LIKE '%' + @ProdName + '%' ORDER BY Name END EXEC spProductList @MinListPrice = 500, @MaxListPrice = 1000, @ProdName= 'road' -- ADD NULL to Default USE AdventureWorks2017 GO ALTER PROCEDURE [dbo].[spProductList] ( @MinListPrice AS INT = NULL, @MaxListPrice AS INT = NULL, @ProdName AS VARCHAR(MAX) ) AS BEGIN SELECT Name, ListPrice FROM Production.Product WHERE (@MinListPrice IS NULL OR ListPrice >= @MinListPrice) AND (@MaxListPrice IS NULL OR ListPrice <= @MaxListPrice) AND Name LIKE '%' + @ProdName + '%' ORDER BY Name END EXEC spProductList @ProdName= 'socks' ``` # Variables > A variable is an object that can hold a single data value of specific type ![image](https://hackmd.io/_uploads/BkmfcP3Yp.png) 使用變數前要先宣告,宣告的位置要放在最前面,after use and go statement 將值存在變數裡面,使用關鍵字:`SET` ```sql! -- assign specific value to our variables using set statement USE AdventureWorks2017 GO DECLARE @Date AS DATETIME SET @Date = '2013-10-31' SELECT TransactionID, TransactionDate, 'Transaction' as [Type] FROM Production.TransactionHistory WHERE TransactionDate >= @Date UNION ALL SELECT SalesOrderID, OrderDate, 'SalesOrder' as [Type] FROM Sales.SalesOrderHeader WHERE OrderDate >= @Date -- query result stored in variables USE AdventureWorks2017 GO DECLARE @Date AS DATETIME DECLARE @NumTrend AS INT DECLARE @NumSales AS INT SET @Date = '2013-10-31' SET @NumTrend = (SELECT COUNT(0) FROM Production.TransactionHistory WHERE TransactionDate >= @Date) SET @NumSales = (SELECT COUNT(0) FROM Sales.SalesOrderHeader WHERE OrderDate >= @Date) SELECT 'Nums Of Transaction', @NumTrend UNION SELECT 'Nums Of Sales', @NumSales SELECT TransactionID, TransactionDate, 'Transaction' as [Type] FROM Production.TransactionHistory WHERE TransactionDate >= @Date UNION ALL SELECT SalesOrderID, OrderDate, 'SalesOrder' as [Type] FROM Sales.SalesOrderHeader WHERE OrderDate >= @Date -- show the value of variables in messages tab -- sql server concatenate value must be same data type PRINT 'Nums Of Transaction = ' + CAST(@NumTrend AS VARCHAR(MAX)) PRINT 'Nums of Sales = ' + CAST(@NumSales AS VARCHAR(MAX)) ``` ```sql! -- Refer a record in a set of variables USE AdventureWorks2017 GO DECLARE @SalesOrderId AS INT DECLARE @Name AS VARCHAR(MAX) DECLARE @OrderDate AS DATETIME SELECT TOP 1 @SalesOrderId = so.SalesOrderID, @Name = p.Name, @OrderDate = so.OrderDate FROM Sales.SalesOrderHeader so INNER JOIN Sales.SalesOrderDetail sod ON so.SalesOrderID = sod.SalesOrderID INNER JOIN Production.Product p ON sod.ProductID = p.ProductID WHERE so.OrderDate > '2012-05-01' ORDER BY so.OrderDate DESC SELECT @SalesOrderId, @Name, @OrderDate -- Gather values in variables USE AdventureWorks2017 GO DECLARE @ProdList VARCHAR(MAX) SET @ProdList = '' SELECT @ProdList = @ProdList + Name + ', ' + CHAR(10) FROM Production.Product WHERE YEAR(SellStartDate) = 2012 PRINT @ProdList -- rows afftected by last statement SELECT * FROM Production.Product SELECT @@ROWCOUNT ``` `@@` 這是 global parameters 的前綴 # Output Parameters And Return Values ```sql! ALTER PROCEDURE spSellStartInYear ( @Year Int, @ProductList VARCHAR(MAX) OUTPUT, @ProductCount Int OUTPUT ) AS BEGIN DECLARE @Products VARCHAR(255) SET @Products = '' SELECT @Products = @Products + Name + ', ' FROM Production.Product WHERE YEAR(SellStartDate) = @Year ORDER BY Name ASC SET @ProductCount = @@ROWCOUNT SET @ProductList = @Products END DECLARE @ProdNames VARCHAR(255) DECLARE @COUNT INT EXEC spSellStartInYear @Year = 2013, @ProductList= @ProdNames OUTPUT, @ProductCount= @COUNT OUTPUT SELECT @COUNT AS [Number of Products], @ProdNames AS [List of Products] ``` ![image](https://hackmd.io/_uploads/rkzEZVCtT.png) # If Statements ![image](https://hackmd.io/_uploads/S1lEZM2tp.png) ```sql! USE AdventureWorks2017 GO DECLARE @Accessories INT DECLARE @Clothing INT SET @Accessories = (SELECT COUNT(1) FROM Production.ProductSubcategory WHERE ProductCategoryID = 4) SET @Clothing = (SELECT COUNT(1) FROM Production.ProductSubcategory WHERE ProductCategoryID = 2) IF @Accessories > 7 BEGIN PRINT 'CAUTION' PRINT 'THERE are too many accessories' IF @Clothing > 10 BEGIN PRINT 'more than 10 clothing items' END ELSE BEGIN PRINT 'less than 10 clothing items' END END ELSE BEGIN PRINT 'Less Than 7 Accessories.' END ``` ```SQL! USE AdventureWorks2017 GO Alter PROC spGenderData ( @GetInfo VARCHAR(10) ) AS BEGIN IF @GetInfo = 'All' BEGIN (SELECT * FROM Production.Product) RETURN END IF @GetInfo = 'Ball' BEGIN (SELECT * FROM Production.Product WHERE Name like ('%' + @GetInfo + '%')) RETURN END SELECT 'Not Ball and All' END ``` # While Loop ![image](https://hackmd.io/_uploads/SyLJZHCtp.png) # Temp Table ```sql! -- method 1 SELECT Name, SellStartDate INTO #temp_table FROM [Production].[Product] WHERE Name LIKE '%THIN%' SELECT * FROM #temp_table -- method 2 create a table explicitly IF OBJECT_ID('tempdb..#temp_table2') IS NOT NULL DROP TABLE #temp_table2 GO CREATE TABLE #temp_table2 ( Name VARCHAR(MAX), SellStartDate DATETIME ) INSERT INTO #temp_table2 SELECT Name, SellStartDate FROM [Production].[Product] WHERE Name LIKE '%THIN%' SELECT * FROM #temp_table2 ``` temp tables will be stored as part of the system database folder ![image](https://hackmd.io/_uploads/SyR9wok5T.png) ## Cursor A Cursor enables traversal over the rows of our results set. It allows to process individual row return by a query. ```sql! DECLARE @ProductCategoryID INT DECLARE @ProdCatName VARCHAR(MAX) DECLARE ProdCatCursor CURSOR FOR SELECT Name, ProductCategoryID FROM Production.ProductCategory Open ProdCatCursor FETCH NEXT FROM ProdCatCursor INTO @ProdCatName, @ProductCategoryID WHILE @@FETCH_STATUS = 0 BEGIN PRINT 'Subcategories in Category ' + @ProdCatName SELECT Name FROM Production.ProductCategory WHERE ProductCategoryID = @ProductCategoryID FETCH NEXT FROM ProdCatCursor INTO @ProdCatName, @ProductCategoryID END CLOSE ProdCatCursor DEALLOCATE ProdCatCursor ``` # User Defined Functions ![image](https://hackmd.io/_uploads/rydE7ce9T.png) ![image](https://hackmd.io/_uploads/SyRHQ5gca.png) ```sql! USE AdventureWorks2017 GO ALTER FUNCTION fnLengthyDate ( @EntireDate AS DATETIME ) RETURNS VARCHAR(MAX) AS BEGIN RETURN DATENAME(DW, @EntireDate) + ' ' + DATENAME(D, @EntireDate) + CASE WHEN DAY(@EntireDate) IN (1, 11, 21, 31) THEN 'st' WHEN DAY(@EntireDate) IN (2, 11, 21, 31) THEN 'nd' WHEN DAY(@EntireDate) IN (3, 13, 23) THEN 'rd' ELSE 'th' END + ' ' + DATENAME(M, @EntireDate) + ' ' + DATENAME(YY, @EntireDate) END ``` ```sql! SELECT [dbo].[fnLengthyDate](SellStartDate) FROM Production.Product ``` ```sql! USE AdventureWorks2017 GO CREATE FUNCTION fnFirstWordProdName ( @ProdName AS VARCHAR(MAX) ) RETURNS VARCHAR(MAX) AS BEGIN DECLARE @BlankPosition AS INT DECLARE @OutCome AS VARCHAR(MAX) SET @BlankPosition = CHARINDEX(' ', @ProdName) IF @BlankPosition = 0 SET @OutCome = @ProdName ELSE SET @OutCome = LEFT(@ProdName, @BlankPosition - 1) RETURN @OutCome END ``` ```sql! SELECT [dbo].[fnFirstWordProdName](Name), Name FROM Production.Product ``` # Table Variables ![image](https://hackmd.io/_uploads/B1SNF-ht6.png) ```SQL! USE AdventureWorks2017 DECLARE @TempProducts TABLE ( Name VARCHAR(MAX), SellStartDate DATETIME ) INSERT INTO @TempProducts SELECT Name, SellStartDate FROM Production.Product WHERE SellStartDate < '2012-01-01' SELECT * FROM @TempProducts ``` ## Temp tables VS Table Variables 差異:存續時間 **temporary tables will exist as long as the session that created it remains open** ```sql! -- 需要在最前面去 drop 不然會出問題 IF OBJECT_ID('tempdb..#TempName') IS NOT NULL DROP TABLE #TempName; GO CREATE TABLE #TempName ( Name VARCHAR(MAX), SellStartDate DATETIME ) INSERT INTO #TempName SELECT Name, SellStartDate FROM [Production].[Product] WHERE Name LIKE '%Thin%' SELECT * FROM #TempName ``` ![image](https://hackmd.io/_uploads/BkVhQixqa.png) 要馬寫 drop 要馬關掉 session 對於 Table Variables 而言,變數存續在代碼運行時。It will be created and dropped instantaneously. ## Table Variables 的缺點 暫存表不用一定要宣告表後再寫入資料,可以透過 select into ie create an insert record into a table in one single step. ```sql! IF OBJECT_ID('tempdb..#TempName') IS NOT NULL DROP TABLE #TempName; GO SELECT Name, SellStartDate INTO #TempName FROM [Production].[Product] WHERE Name LIKE '%Ball%' SELECT * FROM #TempName ``` 因為 sql server 中的變數需要被明確的宣告 ![image](https://hackmd.io/_uploads/BJB-Isxc6.png) # Table Values Functions ![image](https://hackmd.io/_uploads/BkWUUse9T.png) 使用 user defined function,schema 是很重要的 ```sql! USE AdventureWorks2017 GO CREATE FUNCTION SellStartDateInYear ( @SellStartDateYear INT ) RETURNS TABLE AS RETURN SELECT * FROM Production.Product WHERE YEAR(SellStartDate) = @SellStartDateYear SELECT * FROM [dbo].[SellStartDateInYear](2011) ``` ## Multiple Statement Table Value Functions ```sql! USE AdventureWorks2017 GO CREATE FUNCTION dbo.ProductNameFilter ( @Name VARCHAR(255) ) RETURNS @t TABLE (ProductID VARCHAR(MAX), Name VARCHAR(255), ProductNumber VARCHAR(30)) AS BEGIN INSERT INTO @t SELECT ProductID, Name, ProductNumber FROM Production.Product WHERE Name Like '%' + @Name + '%' RETURN END SELECT * FROM dbo.ProductNameFilter('BE') ``` # CTE create a temporary set of records which we can use right away for another select statement ![image](https://hackmd.io/_uploads/BkriUoeqT.png) # Dynamic SQL ![image](https://hackmd.io/_uploads/rkisR6gqp.png) ![image](https://hackmd.io/_uploads/ByzR06ecT.png) Def: Dynamic sql is where you can take any valid SQL statement, and change it to a string of text, then run that string of text like it was the original SQL statement. ```sql! -- method 1 EXEC ('SELECT * FROM Production.Product') -- method 2 -- Must be UniCode String EXEC sp_executesql N'SELECT * FROM Production.Product' ``` sp_executesql 接受 unicode string 將他變成有效的 SQL 法一: 1. string 未必要是 unicode 2. no query plan 法二: 1. more efficient: query plan is generated when you utilize system store procedure 2. parameters and extra arguments Note: query plan will be stored in a cache when you pass in whatever `SELECT` statements to sp ```sql! DECLARE @TableName NVARCHAR(128) DECLARE @SQLStrung NVARCHAR(MAX) SET @TableName = N'Production.ProductCategory' SET @SQLStrung = 'SELECT * FROM ' + @TableName EXEC sp_executesql @SQLStrung ``` ![image](https://hackmd.io/_uploads/S1-5AJfq6.png) 因為他嘗試將 SELECT TOP 轉成 int ```sql! DECLARE @Number INT DECLARE @SQLString NVARCHAR(MAX) DECLARE @NumberString NVARCHAR(4) SET @Number = 3 SET @NumberString = CAST(@Number AS NVARCHAR(4)) SET @SQLString = N'SELECT TOP ' + @NumberString + ' * FROM Production.Product' EXEC sp_executesql @SQLString ``` ```sql! -- single parameter CREATE PROC spTableVariable ( @TName VARCHAR(128) ) AS BEGIN DECLARE @SQLStrung NVARCHAR(MAX) SET @SQLStrung = N'SELECT * FROM ' + @TName EXEC sp_executesql @SQLStrung END EXEC spTableVariable 'Production.ProductCategory' ``` ```sql! -- multiple parameters ALTER PROC spTableVariable ( @TName VARCHAR(128), @Number INT ) AS BEGIN DECLARE @SQLStrung NVARCHAR(MAX) DECLARE @NumberStr NVARCHAR(6) SET @NumberStr = CAST(@Number AS NVARCHAR(6)) SET @SQLStrung = N'SELECT TOP ' + @NumberStr + ' * FROM ' + @TName EXEC sp_executesql @SQLStrung END EXEC spTableVariable 'Production.Product', 20 ``` ## Parameters of sp_executesql 參數化 actual sql string ```sql! EXEC sp_executesql N'SELECT * FROM Person.AddressType t INNER JOIN Person.Address a ON t.AddressTypeID = a.AddressID WHERE t.AddressTypeID > @AdressTypeID AND t.Name LIKE @FirstLetter + ''%'' ', N'@AdressTypeID INT, @FirstLetter VARCHAR(3)', @AdressTypeID = 2, @FirstLetter = 'A' ``` ## SQL Injection [The Curse and Blessings of Dynamic SQL ](https://www.sommarskog.se/dynamic_sql.html) # Transaction ![image](https://hackmd.io/_uploads/HJ-uAtN5a.png) ![image](https://hackmd.io/_uploads/H1EtAKE56.png) Naming Transaction 的好處是 easily for read ```sql! BEGIN TRAN UpdateAddressLine2 -- BEGIN TRANSACTION UPDATE Person.Address SET ModifiedDate = GETDATE(), AddressLine2 = 'Karchanaburi' WHERE AddressID = 20 --ROLLBACK TRAN UpdateAddressLine2 COMMIT TRAN UpdateAddressLine2 -- COMMIT, or COMMIT TRANSACTION ``` ## EX - IF Statements to commit or rollback ```sql! IF OBJECT_ID(N'dbo.books', N'U') IS NOT NULL DROP TABLE dbo.books; GO CREATE TABLE dbo.books ( id INT IDENTITY(1, 1), book_name VARCHAR(256), sold_copies INT NOT NULL ) INSERT INTO dbo.books (book_name, sold_copies) VALUES ('book1', 10), ('book2', 20), ('book3', 30), ('book4', 40), ('book5', 50) SELECT * FROM dbo.books; ``` SQL Server 中如果變量已經被聲明,可以在 `SELECT` 語句中直接為變量賦值。 優點:一次 DB 訪問,查詢與賦值同時做,不需要先執行 `SET` 語句來初始化變量 ```sql! -- Conditionally committing or rolling back DECLARE @BookNameCount INT BEGIN TRANSACTION AddBook INSERT INTO dbo.books(book_name, sold_copies) VALUES('book_6', 60) SELECT @BookNameCount = COUNT(0) FROM dbo.books WHERE book_name = 'book_6' IF (@BookNameCount > 1) BEGIN ROLLBACK TRANSACTION AddBook PRINT 'book_6 already exists' END ELSE BEGIN COMMIT TRANSACTION AddBook END ``` ## Error Handling 通常會使用 Error Handling 而非 IF ELSE 來決定 COMMIT or ROLL BACK 在 BEGIN & COMMIT 中間出事,會自動做整組 Transaction 的 ROLL BACK。 ```sql! -- default rollback BEGIN TRANSACTION AddBook INSERT INTO dbo.books(book_name, sold_copies) VALUES('book_8', 80) UPDATE dbo.books SET sold_copies = 'Apple' WHERE book_name = 'book_8' COMMIT TRANSACTION AddBook ``` ## Try and Catch Block ```sql! BEGIN TRY BEGIN TRANSACTION AddBook INSERT INTO dbo.books(book_name, sold_copies) VALUES('book_8', 80) UPDATE dbo.books SET sold_copies = 'Apple' WHERE book_name = 'book_8' COMMIT TRANSACTION AddBook END TRY BEGIN CATCH ROLLBACK TRAN ADDBook PRINT 'ADDING NEW BOOK FAILED' END CATCH ``` ## Nested Transaction 在SQL Server中,`@@TRANCOUNT` 是一個系統函數,用於返回當前連接上已執行的BEGIN TRANSACTION 語句的數目。這個數字反映了嵌套事務的層次 1. 每次執行 BEGIN TRANSACTION 語句時,`@@TRANCOUNT`會增加 1 2. **每次執行 COMMIT TRANSACTION 或 COMMIT WORK 語句時,`@@TRANCOUNT` 會減少 1** 3. 執行 ROLLBACK TRANSACTION 語句會將 `@@TRANCOUNT` 減少到 0 ```sql! BEGIN TRANSACTION TRN1 PRINT @@TRANCOUNT BEGIN TRANSACTION TRN2 PRINT @@TRANCOUNT COMMIT TRANSACTION TRN2 PRINT @@TRANCOUNT COMMIT TRANSACTION TRN1 ``` ![image](https://hackmd.io/_uploads/HJMaoA49a.png) ![image](https://hackmd.io/_uploads/r1be304qT.png) 不只是 rollback inner nested transaction,當執行 rollback, everything is rolled back. 所以他才會說沒辦法 COMMIT 因為已經沒有 TRANSACTION 是處於 OPEN 的。 看起來像是 saved the transaction in the inner transaction. 但這邊的 ROLL BACK 不只 ROLL BACK OUTER TRANSACTION。他會將所有包在 OUTER TRANSACTION 裡面的東西通通 ROLL BACK ![image](https://hackmd.io/_uploads/SyS-eJH96.png) ## SAFE POINTS ![image](https://hackmd.io/_uploads/ry-PH1B9T.png) # Trigger Trigger 可以將它視為另一種類型的 sp, you can attach to different events that occur in DB ![image](https://hackmd.io/_uploads/HyQ6h5E96.png) # DML Trigger Data Manipulation Language Trigger ![image](https://hackmd.io/_uploads/r1R7AKE56.png DML Trigger can be linked to either a table or a view. 會搭配以下三種類型 1. insert 2. update 3. delete DML Trigger 可以被歸類為 after or instead of trigger 1. After trigger will run right afterw the events you set it for 2. Instead of trigger will run in place of the events ```sql! USE AdventureWorks2017 GO CREATE TRIGGER trgAddressLineModified ON Person.Address AFTER INSERT, UPDATE, DELETE AS BEGIN PRINT 'A Change was made to Person.Address table' END GO -- Alter ALTER TRIGGER Person.trgAddressLineModified ON Person.Address AFTER INSERT, UPDATE, DELETE AS BEGIN PRINT 'A Change was made to Person.Address table!' END GO -- Delete DROP TRIGGER Person.trgAddressLineModified ``` ```sql! -- test for trigger USE AdventureWorks2017 GO SET NOCOUNT ON; SELECT * FROM Person.Address UPDATE Person.Address SET ModifiedDate = GETDATE() WHERE AddressID = 20 INSERT INTO Person.Address(AddressLine1, City, StateProvinceID, PostalCode) VALUES('岡山', '高雄', 79, 820) DELETE FROM Person.Address WHERE AddressLine1 = '岡山' ``` > instead of trigger replaces the original event that could have occurred ```sql USE AdventureWorks2017 GO CREATE TRIGGER trgAddressInserted ON Person.Address INSTEAD OF INSERT AS BEGIN RAISERROR('Cannot insert a new address', 16, 1) END ``` ## Inserted & Deleted Tables 1. Inserted Tables: for insert and update events. inserted table shows us any records that have been inserted into a table in an insert event. 2. Deleted Tables: for delete and update events ```sql! Alter TRIGGER Person.trgAddressInserted ON Person.Address AFTER INSERT AS BEGIN SELECT * FROM inserted END ``` # DDL Trigger Data Definition Language Trigger ![image](https://hackmd.io/_uploads/B1auX9E9p.png) 資料庫級別的觸發:anything that modifies the object in the DB ![image](https://hackmd.io/_uploads/B1_vcaVqT.png) 確認作用範圍:single DB or to the entire server Database scope trigger ![image](https://hackmd.io/_uploads/HJn9LyH56.png) Server scope trigger ![image](https://hackmd.io/_uploads/Skv8wkH5p.png) ## EX : Create New DDL trigger is scope to a database Create Trigger ```sql! USE AdventureWorks2017 GO CREATE TRIGGER trgNewTablesNotAllowed -- our trigger will responsd to events in the db that the trigger is being created ON DATABASE FOR CREATE_TABLE -- Google for DDL events AS BEGIN PRINT 'Please do not create new tables' ROLLBACK END ``` > A database scoped trigger only affects events in the database that our trigger is attached to. Alter Trigger ```sql! Alter TRIGGER trgNewTablesNotAllowed -- our trigger will responsd to events in the db that the trigger is being created ON DATABASE FOR CREATE_TABLE, ALTER_TABLE, DROP_TABLE AS BEGIN PRINT 'Please do not create new tables, or make chages to existing tables' ROLLBACK END ``` Delete Trigger ```sql! USE AdventureWorks2017 GO DROP TRIGGER trgNewTablesNotAllowed ON DATABASE ``` ## Trigger Disabling and Enabling ```sql! USE AdventureWorks2017 GO DISABLE TRIGGER trgNewTablesNotAllowed ON DATABASE ENABLE TRIGGER trgNewTablesNotAllowed ON DATABASE -- 同時操作多個 triggers DISABLE TRIGGER ALL ON DATABASE ENABLE TRIGGER ALL ON DATABASE ``` ## Server-scoped trigger creation 將 DATABASE 改成 ALL SERVER ```sql! USE AdventureWorks2017 GO CREATE TRIGGER trgNewTablesNotAllowed ON ALL SERVER FOR CREATE_TABLE, ALTER_TABLE, DROP_TABLE AS BEGIN PRINT 'Please do not create new tables, or make chages to existing tables' ROLLBACK END -- Enable Trigger ENABLE TRIGGER ALL ON DATABASE -- Drop Trigger DROP TRIGGER trgNewTablesNotAllowed ON ALL SERVER ``` ## Modify the execution order of triggers Server Scopes Triggers 會比 Database triggers 優先觸發執行 如果 create 多個 triggers 在同個層級,就可以決定他們的優先順序 ```sql! USE AdventureWorks2017 GO CREATE TRIGGER trgTrigger1 ON DATABASE FOR CREATE_TABLE AS PRINT 'Trigger 1' GO CREATE TRIGGER trgTrigger2 ON DATABASE FOR CREATE_TABLE AS PRINT 'Trigger 2' GO ``` ![image](https://hackmd.io/_uploads/BJQA1gisa.png)