DDL Triggers are AFTER triggers December 24, 2016
Posted by fofo in Sql Server, Sql Server 2005, SQL Server 2008, SQL Server 2012, SQL Server 2014, SQL Server 2016.Tags: AFTER triggers, DDL triggers
add a comment
In this post I would like to look into DDL triggers and explain their functionality.
Let me start with a short introduction on triggers
All SQL Server developers use SQL triggers that basically is a mechanism that is invoked when a particular action occurs on a particular table.
Triggers consist of
- A name
- An action
- The execution
The maximum size of a trigger name is 128 characters.
The action of a trigger can be either a DML statement (INSERT, UPDATE, or DELETE) or a DDL statement (CREATE, ALTER, DROP)
Therefore, there are two trigger forms: DML triggers and DDL triggers.
The AFTER and INSTEAD OF options are two additional options that you can define for a trigger.
- AFTER triggers fire after the triggering action occurs.
- INSTEAD OF triggers are executed instead of the corresponding triggering action.
- AFTER triggers can be created only on tables, while INSTEAD OF triggers can be created on both tables and views
DDL triggers were introduced in SQL Server 2005. DDL triggers are not INSTEAD OF triggers.
They are implemented as AFTER triggers, which means the operation occurs and is then caught in the trigger.
The the operation can be optionally rolled-back, if you put a ROLLBACK statement in the trigger body.
This means they’re not quite as lightweight as you might think. Imagine doing the following:
ALTER TABLE MyTable ADD newcolumn VARCHAR (30) DEFAULT ‘default value’;
If we have a DDL trigger defined for ALTER_TABLE events, or DDL_TABLE_EVENTS, the trigger due to the above T-SQL batch will fire and every row in the table will be expanded to include the new column (as it has a non-null default), and the operation is rolled back by your trigger body.
Type (copy paste) the following T-SQL statements in a new query window in the SSMS
CREATE DATABASE sampleDB; GO USE sampleDB; GO CREATE TABLE Product ( Pid INT PRIMARY KEY IDENTITY , pname NVARCHAR(50) , price DECIMAL(18, 4) ); CREATE TRIGGER Not_Alter_Tables ON DATABASE FOR ALTER_TABLE AS PRINT 'Not Alter Statements are permitted on the tables of this DB' SELECT EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'nvarchar(max)') RAISERROR ('Tables cannot be altered in this database.', 16, 1) ROLLBACK; GO ALTER TABLE dbo.Product ADD newcolumn VARCHAR (30) DEFAULT 'default value';
Let me explain what I do in the code above.
I create a sample database. I create sample empty table. I create a trigger “Not_Alter_tables” on this database. This trigger applies to all the tables in the database.
This trigger is a DDL trigger, hence an AFTER trigger. In the trigger I capture the T-SQL statement that invoked the trigger, I print a statement, raise an error and then rollback the operation.
Then I attempt to add a new column in the table.
In this example, when the trigger is invoked, the table will be expanded to include the new column (as it has a non-null default), and then the operation is rolled in the trigger body.
Have a look at the picture below.
In a nutshell DDL triggers are AFTER triggers and can be quite expensive. In the example above the best way is not to use DDL triggers but to use instead explicit permissions e.g REVOKE/DENY to prevent users from altering the schema of the table.
Hope it helps!!!
Looking into temporary tables in SQL Server December 4, 2013
Posted by fofo in Sql Server, Sql Server 2005, SQL Server 2008, SQL Server 2012.Tags: tempdb, temporary tables
add a comment
I have been delivering a certified course in MS SQL Server 2012 recently and I was asked several questions about temporary tables, how to create them, how to manage them, when to use them and what are the limitations of them.
In this post I will try to shed light on this particular issue with lots of hands-on demos.
Temporary tables and table variables make use of the system tempdb database.
I have installed SQL Server 2012 Enterprise edition in my machine but you can use the SQL Server 2012/2008 Express edition as well.
I am connecting to my local instance through Windows Authentication and in a new query window I type (you can copy paste)
First I am going to create a new temporary table and populate it. Execute the script below.
USE tempdb GO IF OBJECT_ID('tempdb..#footballer') IS NOT NULL DROP TABLE #footballer; GO CREATE TABLE #footballer ( [FootballerID] INT IDENTITY NOT NULL PRIMARY KEY, [lastname] [varchar](15) NOT NULL, [firstname] [varchar](15) NOT NULL, [shirt_no] [tinyint] NOT NULL, [position_played] [varchar](30) NOT NULL, ); GO SET IDENTITY_INSERT [dbo].[#footballer] ON GO INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (1,N'Oliver', N'Regina', 4, N'goalkeeper') INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (2,N'Alexander', N'Roy', 8, N'goalkeeper') INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (3,N'Mueller', N'Dewayne', 10, N'defender') INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (4,N'Buckley', N'Beth', 3, N'midfielder') INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (5,N'Koch', N'Jolene', 7, N'striker') GO SELECT * FROM #footballer
As you can see there is a # prefix in front of the table. This table will be saved in the tempdb.
Finally I select everything from the temporary table.
If I open a new query window and try to select everything (see the query below) from the #footballer table.
USE tempdb GO SELECT * FROM #footballer
You will not receive any results. You will receive an error – Invalid object name ‘#footballer’.
This is a local temporary table and it is in scope only in the current connection-session.
We can also create global temporary tables. In a new query window execute the following script.
USE tempdb GO IF OBJECT_ID('tempdb..##footballernew') IS NOT NULL DROP TABLE ##footballernew; GO CREATE TABLE #footballernew ( [FootballerID] INT IDENTITY NOT NULL PRIMARY KEY, [lastname] [varchar](15) NOT NULL, [firstname] [varchar](15) NOT NULL, [shirt_no] [tinyint] NOT NULL, [position_played] [varchar](30) NOT NULL, ); GO SET IDENTITY_INSERT [dbo].[##footballernew] ON GO INSERT [##footballernew] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (1,N'Oliver', N'Regina', 4, N'goalkeeper') INSERT [##footballernew] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (2,N'Alexander', N'Roy', 8, N'goalkeeper') INSERT [##footballernew] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (3,N'Mueller', N'Dewayne', 10, N'defender') INSERT [##footballernew] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (4,N'Buckley', N'Beth', 3, N'midfielder') INSERT [##footballernew] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (5,N'Koch', N'Jolene', 7, N'striker') GO SELECT * FROM ##footballernew
We denote the global temporary table with ## – ##footballernew
The global temporary table is deleted when all users referencing the table disconnect.
Both global and local temporary tables should be deleted in code rather than depending on automatic drop.
A temporary table created in a stored procedure is visible to other stored procedures executed from within the first procedure.
In a new query window type the following.
USE tempdb GO SELECT * FROM ##footballernew
In this case there will be no error. Global temporary tables persist across sessions-connections.
You can also add columns to temporary tables and alter the definition of existing columns.
In this script I add another column and then alter the definition of an existing column.
USE tempdb GO IF OBJECT_ID('tempdb..#footballer') IS NOT NULL DROP TABLE #footballer; GO CREATE TABLE #footballer ( [FootballerID] INT IDENTITY NOT NULL PRIMARY KEY, [lastname] [varchar](15) NOT NULL, [firstname] [varchar](15) NOT NULL, [shirt_no] [tinyint] NOT NULL, [position_played] [varchar](30) NOT NULL, ); GO SET IDENTITY_INSERT [dbo].[#footballer] ON GO INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (1,N'Oliver', N'Regina', 4, N'goalkeeper') INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (2,N'Alexander', N'Roy', 8, N'goalkeeper') INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (3,N'Mueller', N'Dewayne', 10, N'defender') INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (4,N'Buckley', N'Beth', 3, N'midfielder') INSERT [#footballer] ([FootballerID], [lastname], [firstname], [shirt_no], [position_played]) VALUES (5,N'Koch', N'Jolene', 7, N'striker') GO ALTER TABLE #footballer ADD [is_retired] BIT NULL; GO ALTER TABLE #footballer ALTER COLUMN [lastname] [nvarchar](50); GO
You can use any data type for columns definition in a temporary table. You can also use user-defined data types.
You can also have constraints in temporary tables.If you execute the code below, it will work perfectly fine.
USE tempdb GO IF OBJECT_ID('tempdb..#Movies') IS NOT NULL DROP TABLE #footballer; GO CREATE TABLE #Movies ( MovieID INT PRIMARY KEY , MovieName NVARCHAR(50) , MovieRating TINYINT ) GO ALTER TABLE #Movies WITH CHECK ADD CONSTRAINT CK_Movie_Rating CHECK (MovieRating >= 1 AND MovieRating <= 5)
But you have to be careful when creating-applying foreign keys. FOREIGN KEY constraints are not enforced on local or global temporary tables.
Execute the script below to see what I mean.The foreign key will not be created.
USE tempdb go CREATE TABLE #Persons ( P_Id INT NOT NULL , LastName VARCHAR(255) NOT NULL , FirstName VARCHAR(255) , Address VARCHAR(255) , City VARCHAR(255) , PRIMARY KEY ( P_Id ) ) CREATE TABLE #Orders ( O_Id int NOT NULL PRIMARY KEY, OrderNo int NOT NULL, P_Id int FOREIGN KEY REFERENCES #Persons(P_Id) )
Please bear in mind that you can create temporary tables with clustered and non-clustered indexes on them.
Let’s investigate the behavior of temporary tables and IDENTITY columns.
If you execute the script below , it will fail. This is the same behavior when executing the same script to regular tables. You cannot specify values for the IDENTITY column.If you choose to do so you must set IDENTITY_INSERT ON.
USE tempdb GO IF OBJECT_ID('tempdb..#Persons') IS NOT NULL DROP TABLE #Persons; GO CREATE TABLE #Persons ( P_Id INT PRIMARY KEY CLUSTERED IDENTITY(1,1) , LastName VARCHAR(255) NOT NULL , FirstName VARCHAR(255) , Address VARCHAR(255) , City VARCHAR(255) ) --this will not work INSERT #Persons(P_Id,LastName,FirstName,Address,City) VALUES (1,'Steven','Gerrard','123 liverpool street','liverpool') SET IDENTITY_INSERT [#Persons] ON GO --this will work INSERT #Persons(P_Id,LastName,FirstName,Address,City) VALUES (1,'Steven','Gerrard','123 liverpool street','liverpool')
Αlso note that transactions are honored in temporary tables. If I begin an explicit transaction -an insert- without committing it will insert the row of data but then if a rollback is issued the whole operation will be rolled back
Execute the script below.
USE tempdb GO IF OBJECT_ID('tempdb..#Persons') IS NOT NULL DROP TABLE #Persons; GO CREATE TABLE #Persons ( P_Id INT PRIMARY KEY CLUSTERED IDENTITY(1,1) , LastName VARCHAR(255) NOT NULL , FirstName VARCHAR(255) , Address VARCHAR(255) , City VARCHAR(255) ) SET IDENTITY_INSERT [#Persons] ON GO --this will insert the value BEGIN TRAN INSERT #Persons(P_Id,LastName,FirstName,Address,City) VALUES (1,'Steven','Gerrard','123 liverpool street','liverpool') GO SELECT * FROM #Persons --this will rollback the transaction ROLLBACK TRAN
Hope it helps!!!
Looking into Temp database usage in SQL Server December 4, 2013
Posted by fofo in Sql Server, Sql Server 2005, SQL Server 2008, SQL Server 2012.Tags: tempdb
add a comment
I have been delivering a certified course in MS SQL Server 2012 recently and I was asked several questions about Tempdb usage and temporary objects.
In this post I will try to shed light on this particular issue.
Temporary tables and table variables make use of the system tempdb database.
There is only one tempdb system database per SQL Server instance so if there is a huge usage of temporary objects in this database it can be the point of contention.
When you create an entry in database it needs to allocate space.This is also valid for the tempdb database.There are three types of pages involved in the allocation process in the tempdb data file:
- Page Free Space (PFS)
- Shared Global Allocation Map (SGAM)
- Global Allocation Map (GAM).
When there is a great page allocation contention in tempdb, the whole allocation process can suffer and we can experience with PAGELATCH waits.
In order to address the issue above, you can have a number of tempdb data files that are equal to the number of cores.For example if you have a system with less than 8 cores e.g 6 you should add/set up 6 data files for the tempdb.If you have a system with more than 8 cores you should add 8 data files for the tempdb and then if the contention is still big you can add 4 more data files.
By saying cores in this post I mean logical cores and not physical cores.So if you have 8 physical cores, then you have 16 logical cores and 32 logical cores if hyper-threading is enabled.
I will provide some demos in order to show you what tempdb contention might look like and what are the main wait latches that occur.
I have installed SQL Server 2012 Enterprise edition in my machine but you can use the SQL Server 2012/2008 Express edition as well.
I am connecting to my local instance through Windows Authentication and in a new query window I type (you can copy paste)
In this snippet, I create a new database and then create a new table with some constraints
CREATE DATABASE mytempdbcontention GO USE mytempdbcontention; GO CREATE TABLE dbo.footballer ( [FootballerID] INT IDENTITY NOT NULL PRIMARY KEY, [lastname] [varchar](15) NOT NULL, [firstname] [varchar](15) NOT NULL, [shirt_no] [tinyint] NOT NULL, [position_played] [varchar](30) NOT NULL, ); GO ALTER TABLE dbo.footballer ADD CONSTRAINT CK_Footballer_Shirt_No CHECK (shirt_no >= 1 AND shirt_no <= 11) GO ALTER TABLE dbo.footballer ADD CONSTRAINT CK_Footballer_Position CHECK (position_played IN ('goalkeeper','defender','midfielder','striker')) GO
Now I need to populate the table with 50.000 rows. This is the script you need to execute in order to make this happen.
You can download it here. Rename the insert-footballer.doc to insert-footballer.sql and execute the script in a new query window.
Now I need to create a script that will create tempdb contention. This is the script that creates a temporary object- #footballer, populates the #footballer from the footballer table and then selects from it. Finally it drops the temporary object.
USE mytempdbcontention; GO SET NOCOUNT ON; GO WHILE 1 = 1 BEGIN IF OBJECT_ID('tempdb..#footballer') IS NOT NULL DROP TABLE #footballer; CREATE TABLE #footballer ( [FootballerID] INT IDENTITY NOT NULL PRIMARY KEY, [lastname] [varchar](15) NOT NULL, [firstname] [varchar](15) NOT NULL, [shirt_no] [tinyint] NOT NULL, [position_played] [varchar](30) NOT NULL, ); INSERT INTO #footballer (lastname, firstname, shirt_no, position_played) SELECT TOP 4000 lastname, firstname, shirt_no, position_played FROM dbo.footballer; SELECT lastname FROM #footballer; DROP TABLE #footballer; END GO
Now I am going to create a .cmd file where I will create contention to the tempdb.
You can download it here. Rename the temp-sql.cmd.doc to temp-sql.cmd. Make sure you execute it (by double-clicking it).
This will create lots of contention to the tempdb. We need to see exactly what this contention is and the wait latches that have occurred.
Execute the script below.
USE tempdb go SELECT session_id, wait_duration_ms,wait_type, resource_description from sys.dm_os_waiting_tasks where wait_type like 'PAGE%LATCH_%' and resource_description like '2:%'
As you can see from the picture below, I have PAGEIOLATCH_SH wait types.This wait type occurs when a task is waiting on a latch for a buffer that is in an I/O request. The latch request is in Shared mode.
I have one tempdb data file, the default configurations
I have 8 cores in this machine so I will add 7 mores tempdb data files of equal size (MS recommendation).
Execute the script below.
USE [master] GO ALTER DATABASE [tempdb] MODIFY FILE ( NAME = N'tempdev', SIZE = 500 MB , FILEGROWTH = 100 MB ) GO ALTER DATABASE [tempdb] ADD FILE ( NAME = N'tempdev2', FILENAME = N'd:\DATA\tempdb2.ndf' , SIZE = 500 MB , FILEGROWTH = 100 MB ) GO ALTER DATABASE [tempdb] ADD FILE ( NAME = N'tempdev3', FILENAME = N'd:\DATA\tempdb3.ndf' , SIZE = 500 MB , FILEGROWTH = 100 MB ) GO ALTER DATABASE [tempdb] ADD FILE ( NAME = N'tempdev4', FILENAME = N'd:\DATA\tempdb4.ndf' , SIZE = 500 MB , FILEGROWTH = 100 MB ) GO ALTER DATABASE [tempdb] ADD FILE ( NAME = N'tempdev5', FILENAME = N'd:\DATA\tempdb5.ndf' , SIZE = 500 MB , FILEGROWTH = 100 MB ) GO ALTER DATABASE [tempdb] ADD FILE ( NAME = N'tempdev6', FILENAME = N'd:\DATA\tempdb6.ndf' , SIZE = 500 MB , FILEGROWTH = 100 MB ) GO ALTER DATABASE [tempdb] ADD FILE ( NAME = N'tempdev7', FILENAME = N'd:\DATA\tempdb7.ndf' , SIZE = 500 MB , FILEGROWTH = 100 MB ) GO
Now run the query again and observe the results.
USE tempdb go SELECT session_id, wait_duration_ms,wait_type, resource_description from sys.dm_os_waiting_tasks where wait_type like 'PAGE%LATCH_%' and resource_description like '2:%'
You will see that there are no wait types hence no tempdb contention.
Stop now the temp-sql.cmd file so the contention of the tempdb stops.
Hope it helps!!!
Ownership chain in SQL Server November 26, 2013
Posted by fofo in Sql Server, Sql Server 2005, SQL Server 2008, SQL Server 2012.Tags: ownership chain, stored procedure
1 comment so far
I have been delivering a certified course in MS SQL Server 2012 recently and I was asked a very interesting question about SQL Server stored procedures and ownership chain.
Security is a big thing in SQL Server and can be implemented in various levels.
Before we move on, we should define some basic terms that are very important when we talk about security in SQL Server.
The principal access data. It should be authenticated by SQL Server which basically means that the principal should “prove” that it is , whom it claims to be.
Principal is a user a group or an application granted permission to access securables.So what is a securable? Securable is an object to which access privileges are granted.
Privilege is an action that a principal is allowed to perform an a securable.
I want to show you an example where we want to deny a user to the actual table-data but grant him access through a view or a stored procedure.UserA is the owner of tableA and creates StoredProcA that gets data from tableA. Then if the UserB is granted permission to execute the StoredProcA even though he does not have direct access to tableA he will be able to get access (indirectly) to the table. This is the concept of ownership chaining.
I have installed SQL Server 2012 Enterprise edition in my machine but you can use the SQL Server 2012/2008 Express edition as well.
I am connecting to my local instance through Windows Authentication and in a new query window I type (you can copy paste)
USE master GO --Create a sample database CREATE DATABASE mysampledb; GO -- create a login CREATE LOGIN login1 WITH PASSWORD ='login1', CHECK_POLICY = OFF; USE mysampledb; go --Create a User for that login CREATE USER thelogin1 FOR LOGIN login1; --see the user that you are now (dbo) SELECT user --create table and populate it with sample data CREATE TABLE dbo.person (id INT IDENTITY (1,1), firstname varchar(40), lastname VARCHAR(50) ); go INSERT dbo.person VALUES ('nick','kenteris'),('maria','negri'),('apostolis','kaperis'),('kostas','gekas'); CREATE PROCEDURE dbo.getpersons AS SELECT id,firstname,lastname FROM dbo.person go --give permissions to execute the procedure to the thelogin1 user GRANT EXECUTE ON dbo.getpersons TO thelogin1 --change the current user as thelogin1 EXECUTE AS USER = 'thelogin1' --check if we are indeed thelogin1 user SELECT USER --fails SELECT * FROM dbo.person --works EXECUTE dbo.getpersons --get back to the dbo user REVERT; --see if the user is indeed dbo SELECT USER --we change the stored proc to use dynamic sql ALTER PROCEDURE dbo.getpersons AS EXEC('SELECT id,firstname,lastname FROM dbo.person') GO --change the current user as thelogin1 EXECUTE AS USER = 'thelogin1' --check to see if indeed the user is now thelogin1 SELECT USER --this will fail EXECUTE dbo.getpersons --get back to the dbo user REVERT; --check to see if we are dbo user SELECT user --this will work ALTER PROCEDURE dbo.getpersons WITH EXECUTE AS OWNER AS EXEC('SELECT id,firstname,lastname FROM dbo.person') GO EXECUTE AS USER = 'thelogin1' --check to see if we are dbo user SELECT USER --this will work EXECUTE dbo.getpersons
Let me explain what I am doing in this bit of code
- I am creating a database in line 6
- I create a login in line 11
- I create a user-thelogin1 for that login in line 17
- I create a new table in lines 23-30
- Then I populate the table – dbo.person in lines 32
- Then I create a procedure – dbo.getpersons in lines 34-36
- Then I grant permissions to the stored procedure to the user thelogin1
- Then I execute the statements like user – thelogin1
- If I try to access the table directly then I fail. That makes sense.
- Then I execute the stored procedure as user – thelogin1 in lines 55
- Then I revert back to the dbo user
- Then I make a change in lines 67-70 to the stored procedure. I use in the body of stored procedure dynamic sql.
- Then I change the user to thelogin1 in lines 74
- Then I execute the stored procedure in line 82 . This will fail.The dynamic SQL statement causes a break in the ownership chain.
- Then I revert back to line 85 as the dbo user
- In the lines 93-97 I change the stored procedure again (WITH EXECUTE AS OWNER)
- Then I change the user to thelogin1 in lines 99
- Finally, I execute the stored procedure again in lines 107 and it works
Hope it helps!!!
Tables with no clustered indexes or no indexes at all in SQL Server databases November 18, 2013
Posted by fofo in Sql Server, Sql Server 2005, SQL Server 2008, SQL Server 2012.Tags: catalog views, clustered index
add a comment
I have been delivering a certified course in MS SQL Server 2012 recently and I was asked a very interesting question “If there is a way to find using T-SQL tables in a database that have no clustered indexes or no indexes at all”
I have answered that this is possible and I provided with some scripts.
There a lot of tasks that we cannot perform through the GUI of SSMS.So we have to search through the object metadata to get our answers.We can query the catalog views and get all the information we want
I have installed SQL Server 2012 Enterprise edition in my machine but you can use the SQL Server 2012/2008 Express edition as well.
I am connecting to my local instance through Windows Authentication and in a new query window I type (you can copy paste)
SELECT DB_NAME(DB_ID ('AdventureWorksLT2012')) + '. ' +OBJECT_SCHEMA_NAME(t.object_ID)+ '.' +t.NAME AS [tables without clustered index] FROM sys.tables t WHERE OBJECTPROPERTY(object_id, 'TableHasClustIndex')=0 AND OBJECTPROPERTY(object_id, 'IsUserTable')=1 ORDER BY [tables without clustered index]
If you execute this bit of code , you will see all the tables in the AdventureWorksLT2012 database that have not go a clustered index.
As you can see I query the sys.tables view.You can use this script to query your own database for tables without a clustered index.
If I want to find all the tables in the AdventureWorksLT2012 database or any other database for tables that have not indexes you can execute the following script.
SELECT DB_NAME(DB_ID ('AdventureWorksLT2012')) + '. ' +OBJECT_SCHEMA_NAME(t.object_ID)+ '.' + t.NAME AS [tables_without_an_index] FROM sys.tables t WHERE OBJECTPROPERTY(object_id, 'TableHasIndex')=0 AND OBJECTPROPERTY(object_id, 'IsUserTable')=1 ORDER BY [tables_without_an_index]
Hope it helps!!!