SQL Server 2014 Create DDL trigger on Create Table that creates a trigger for the table - sql-server

I'm working with a third party tool that creates databases and tables. I would like a trigger on one of those tables being created. I thought I would try to create a server DDL trigger that fires when the table is created in a new database, which in turn creates a trigger on that table. I cannot add the trigger to the 'model' database because this table is created via the tool dynamically.
I've tried the following:
CREATE TRIGGER ddl_trig_createTable
ON ALL SERVER
FOR CREATE_TABLE
AS
DECLARE #databaseName varchar(255)
DECLARE #AffectedTables varchar(255)
SELECT #AffectedTables = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(100)')
IF (#AffectedTables IN ('DynamicallyCreatedTable'))
BEGIN
select #databaseName = CAST(eventdata().query('/EVENT_INSTANCE/DatabaseName[1]/text()') as NVarchar(128))
EXEC('CREATE TRIGGER ' + #databaseName + '.[dbo].[tgrDynamicTableTrigger]
ON
' + #databaseName + '.[dbo].[DynamicallyCreatedTable]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON
-- trigger code here
END')
END
GO
Which produces the following error when the table is created;
Msg 166, Level 15, State 1, Line 1 'CREATE/ALTER TRIGGER' does not
allow specifying the database name as a prefix to the object name.
I tried changing the dynamic sql by replacing the fully qualified table name to attempt a 'use' statement:
--- 8< ---
EXEC('use ' + #databaseName + '
CREATE TRIGGER [dbo].[tgrDynamicTableTrigger]
ON
--- 8< ---
However, that produced the following error when the table was created:
Msg 111, Level 15, State 1, Line 2 'CREATE TRIGGER' must be the first
statement in a query batch.
Any ideas?
I'm using SQL Server 2014.

I believe that I have figured it out, thanks mostly to this answer.
Here's the code:
CREATE TRIGGER ddl_trig_createTable
ON ALL SERVER
FOR CREATE_TABLE
AS
DECLARE #statement nvarchar(max) = 'CREATE TRIGGER [dbo].[tgrDynamicTableTrigger]
ON
[dbo].[DynamicallyCreatedTable]
AFTER UPDATE
AS
BEGIN
-- trigger code here
END'
DECLARE #databaseName varchar(255)
DECLARE #AffectedTables varchar(255)
SELECT #AffectedTables = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(100)')
IF (#AffectedTables IN ('DynamicallyCreatedTable'))
BEGIN
SET #databaseName = CAST(eventdata().query('/EVENT_INSTANCE/DatabaseName[1]/text()') as NVarchar(128))
DECLARE #sql NVARCHAR(MAX) = QUOTENAME(#databaseName) + '.sys.sp_executesql';
EXEC #sql #statement;
END
GO

Related

Could not find stored procedure 'sp_msforeachtable' while looping through server for stats (SSMS)

I've written this to loop through each database on a server, collecting the statistics for each table and storing them in a temp table. Eventually, I'll integrate this into a more permanent structure, but for now I'm just trying to get this working. My problem is, after 57 databases, I get the error stating it can't find the stored procedure sp_msforeachtable.
I've verified that this stored procedure exists on every database on the server and on the server level.
I've excluded this database in the findings by adding it to the "where name not in" condition, and it just moves to the next one in the list and gives the same error.(I've confirmed it exists on the next database also). I've actually done this for the next 6 databases.
This is causing me to not collect accurate information. Am I running out of resources somewhere?
DECLARE #Database TABLE (DbName SYSNAME);
IF OBJECT_ID('tempdb.dbo.#TableLvlSizes', 'U') IS NOT NULL
BEGIN
PRINT 'dropping table'
DROP TABLE tempdb.dbo.#TableLvlSizes;
END
CREATE TABLE #TableLvlSizes (
TableName nvarchar(128)
,NumberOfRows varchar(50)
,ReservedSpace varchar(50)
,TableDataSpace varchar(50)
,IndexSize varchar(50)
,unused varchar(50))
DECLARE #DbName AS SYSNAME;
DECLARE #Sql1 AS VARCHAR(MAX);
SET #DbName = '';
INSERT INTO #Database (DbName)
SELECT NAME
FROM sys.databases
where name not in ('tempdb')
ORDER BY NAME ASC;
WHILE #DbName IS NOT NULL
BEGIN
SET #DbName = (
SELECT MIN(DbName)
FROM #Database
WHERE DbName > #DbName
);
print #DbName;
SET #Sql1 =
'USE ' + #DbName + '; ' + '
Exec sp_msforeachtable
''insert into #TableLvlSizes exec sp_spaceused [?]''
'
Exec (#SQL1);
END
If someone is using Azure SQL, they will not find sp_MSforeachtable since it is not available in Azure SQL.
You may need to create one for yourself.
Since you already verified that the stored procedure does in fact exist, I believe your database is case sensitive. Therefore, the error is still accurate. Basically, the stored procedure with the case you used does not exist. The actual procedure name is sp_MSforeachtable
In your code, you are using the following:
Exec sp_msforeachtable
If you change your code to use the proper case for the stored procedure to be sp_MSforeachtable, it should work:
SET #Sql1 =
'USE ' + #DbName + '; ' + '
Exec sp_MSforeachtable
''insert into #TableLvlSizes exec sp_spaceused [?]'''

Table already exists in a new SQL Server database

I'm writing a script to create a bunch of tables in SQL Server. As I write the script, I want to create and delete the database. The problem is that the I get an error saying that the object already exists.
Here is my example script
DECLARE #db_name varchar(20);
DECLARE #command varchar(100);
SET #db_name='testdb';
SET #command='DROP DATABASE ' + #db_name
IF EXISTS(SELECT * FROM sys.databases WHERE name=#db_name)
exec(#command)
SET #command='CREATE DATABASE ' + #db_name
EXEC(#command)
--listing databaes
SELECT name from master.dbo.sysdatabases
-- using our database
SET #command='USE ' + #db_name
EXEC(#command)
PRINT 'listing tables'
SET #command = 'SELECT table_name FROM ' + #db_name + '.INFORMATION_SCHEMA.TABLES WHERE table_type = "base TABLE"'
EXEC(#command)
CREATE TABLE stuff(
name VARCHAR(30) PRIMARY KEY NOT NULL,
weight INT,
quantity INT)
and the output I get is
name
------------
master
tempdb
model
msdb
testdb
SW
(6 rows affected)
listing tables
table_name
Error:
Msg 2714, Level 16, State 6, Server Orange, Line 22
There is already an object named 'stuff' in the database.
I run this on a Linux mint machine, a freshly installed SQL Server, and I use sqlcmd. I guess I can put a drop/delete command before the creating the table, but this shouldn't happen to begin with. What is going on here?
When you execute a USE statement from dynamic SQL, the database context reverts back to the original database context (master?) when the executed batch completes. You'll need to add a USE to the CREATE TABLE script and execute it using dynamic SQL too:
SET #command = N'USE' + QUOTENAME(#db_name) + N';
CREATE TABLE stuff(
name VARCHAR(30) PRIMARY KEY NOT NULL,
weight INT,
quantity INT);
';
bind the create table statement inside a object existence check. like this
IF OBJECT_ID('stuff') IS NULL
BEGIN
CREATE TABLE stuff(
name VARCHAR(30) PRIMARY KEY--NOT NULL is not needed as the primary key does not allow NULL,
weight INT,
quantity INT)
END

MS SQL loop through all DBs. Script works to alter table but not stored procedure

I use a piece of code to loop through all the databases on an MS SQL server. It works fine for altering a column on a table and also for updating the data. But I continue to get errors when trying to alter a stored procedure. Here is the code:
use master
declare #dbname varchar(100)
,#sql varchar(max)
declare db_cur cursor for
SELECT name
FROM sys.databases where ([name] like 'ce%')
and [state] = 0
open db_cur
fetch next from db_cur into #dbname
while ##FETCH_STATUS = 0
begin
set #sql=
'ALTER TABLE ['+#dbname+'].[dbo].MyStuff
ADD myNewColumn bit NULL DEFAULT(0)
'
exec(#sql)
fetch next from db_cur into #dbname
end
close db_cur
deallocate db_cur
So the code above works perfectly fine. But when I alter that code to instead do an alter stored procedure I receive the message below:
'CREATE/ALTER PROCEDURE' does not allow specifying the database name as a prefix to the object name.
I realized that the message stated I can't use the database name in the front of the procedure like I was doing here: ALTER procedure ['+#dbname+'].[dbo].[spSelectSomething]. But I haven't been able to figure out a way around the issue. Thanks for your help.
You need to nest dynamic SQL for this task because a proc CREATE or ALTER must be the first statement in the batch:
SET #sql= N'EXEC(N''USE ' + QUOTENAME(#dbname) + N';EXEC(N''''CREATE PROC...;'''')'')';

Stored procedure to alter database recovery mode isnt compiling

I am trying to create a stored procedure that would be generic. I am trying to alter a database and set the recovery mode to either simple or full. It would accept database name and mode as parameter.
The SQL query executes in the context of the master database and alters the database specified. I am trying to incorporate it via Execute SQL task in SSIS. I need the stored procedure to reside in the database that is going to perform the operation on. Not sure how that is going to work. USE database keyword is not allowed in the stored procedure...
The original query works fine but I am facing an issue while trying to execute the stored procedure in the database.It says 'RECOVERY' is not a recognized SET option.
Original query:
use master
ALTER DATABASE XYZ
SET RECOVERY FULL
Stored procedure:
USE XYZ
GO
CREATE PROCEDURE DatabaseRecoveryMode
(#mode varchar(10),
#database varchar(50))
AS
BEGIN
ALTER DATABASE #database
SET RECOVERY #mode
END
The ALTER DATABASE documentation shows the recovery model is a keyword, not a variable. You'll need to construct and execute a dynamic SQL statement for this.
CREATE PROCEDURE dbo.DatabaseRecoveryMode
(
#mode nvarchar(11),
#database sysname
)
AS
IF #mode NOT IN(N'SIMPLE', N'BULK_LOGGED', N'FULL')
BEGIN
RAISERROR('Recovery model must be SIMPLE, BULK_LOGGED, OR FULL', 16, 1);
RETURN 1;
END;
DECLARE #SQL nvarchar(MAX) = N'ALTER DATABASE '
+ QUOTENAME(#database)
+ N' SET RECOVERY '+ #mode + N';';
EXECUTE(#SQL);
GO
You need to use dynamic SQL
USE XYZ
GO
Create Procedure DatabaseRecoveryMode
(
#mode varchar(10),
#database varchar(50)
)
AS
begin
DECLARE #SQL NVARCHAR(MAX)
DECLARE #db NVARCHAR(60), #Use NVARCHAR(100)
SET #db = N'master'
SET #Use = N'Use ' + #db
SET #SQL = #Use + N' ALTER DATABASE '+ #database + N' SET RECOVERY ' + #mode ;
--SELECT #SQL
EXEC sys.sp_executesql #SQL ;
end
GO

Create A Trigger Automaticly Every Day

I have a MSSQL GPS database which create every month a database and every day a table. This is a real time database that getting data every 3 seconds. And all operations are making by a GPS program which I have no source code or access.
Databases looks like this :
-Comms201502 (Database)
-Comms201503 (Database)
-Comms201504 (Database)
-GPS20150401 (Database's Table)
-GPS20150402 (Database's Table)
-GPS20150403 (Database's Table)
-GPS20150404 (Database's Table)
-...
I have a trigger that receiving data from a table which I have to create that trigger for every day, and writing to my database.
Is there a anyway to create single trigger or create an automaticly create trigger every day ?
Best regards,
I would create a DDL trigger, this would be raised each time a CREATE TABLE Statement is executed and then adds a trigger to the new table to log the records to your table, such as:
CREATE TRIGGER trg_DDLCreateTrigger ON DATABASE
FOR CREATE_TABLE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #EventData XML = EVENTDATA();
DECLARE #Schema SYSNAME
DECLARE #TableName SYSNAME
SELECT #Schema = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]','nvarchar(max)'),
#TableName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(max)')
DECLARE #sql VARCHAR(MAX)
SET #sql = 'CREATE TRIGGER [trg_' + #TableName + '] ON [' + #Schema + '].[' + #TableName + ']' +
' AFTER INSERT AS ' +
' INSERT INTO MyTable SELECT * FROM inserted' --Change this as required
EXEC (#Sql)
END
GO
You should probably add some validation in the trigger to check that the table is one that you are interested in capturing.
You can probably extend this so that there is a DDL trigger on a create database statement, which adds this trigger to the database.

Resources