I'm trying to create a trigger dynamically and need to replace part of the create statement, in particular the database name that a join is done on.
CREATE TRIGGER [dbo].[tr_UseType_update] ON [dbo].[UseType] FOR UPDATE AS
BEGIN
SET NOCOUNT ON
INSERT [dbo].[UseType]
SELECT 'Updated', i.*
FROM inserted
INNER JOIN [(SELECT DB_NAME())].[dbo].[UseType] i ON inserted.[UseTypeID] = i.[UseTypeID]
END
I've tried this but obviously it just uses the text it doesn't actually evaluate SELECT DB_NAME(), is there a way to get it to evaluate this value.
I'm using sql 2005/8. We have a deployment tool that uses sqlcmd so a fallback will be to change the tool to take in variables that can be passed to sqlcmd but if I can reduce the amount of work and do it in the script itself (which is generated) that would be handy.
The only way would be to use dynamic sql.
There's no other way to parameterize database names or table names.
That being said, dynamically creating a trigger like this seems like a perilous idea.
The easiest way to do what you want will be something like:
USE MyDataBase
DECLARE #DB varchar(100) = 'MyOtherDatabaseThatIAmJoiningToInTheInsert'
DECLARE #SQL Varchar (MAX)
SET #SQL = '
CREATE TRIGGER [dbo].[tr_UseType_update] ON [dbo].[UseType] FOR UPDATE AS
BEGIN
SET NOCOUNT ON
INSERT [dbo].[UseType]
SELECT ''Updated'', i.*
FROM inserted
INNER JOIN ' + #DB + '.[dbo].[UseType] i ON inserted.[UseTypeID] = i.[UseTypeID]
END'
It's mandatory to read this before doing any dynamic sql code, though!
You could potentially execute dynamic sql and load that into a temp table which in turn would be available to the trigger.
CREATE TRIGGER [dbo].[tr_UseType_update] ON [dbo].[UseType]
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #t (userTypeId INT)
DECLARE #sql NVARCHAR(500) SET #sql = 'insert into #t select userTypeID from ' + DB_NAME() + '.dbo.UseType'
EXEC sp_executeSQL #sql
INSERT [dbo].[UseType]
SELECT 'Updated',
i.*
FROM inserted
INNER JOIN #t i ON inserted.[UseTypeID] = i.[UseTypeID]
END
Related
I have a procedure in which there are calculations being done and the final result is inserted into a permanent table. I want to remove the permanent table and I cannot use Temp table as well. So i want to use a dynamic table name, which is stored in a variable:
Current scenario:
Insert into xyz_table
Select col1,col2,sum(col3)
from BaseTable
(In reality, there are lot of columns and a lot of calculations)
What I want:
Select col1,col2,sum(col3) into #DynamicTableName
from BaseTable
where the name of the table would be dynamic in nature i.e.,
#DynamicTableName = 'xyz ' + cast(convert(date,getdate()) as nvarchar)+' '+convert(nvarchar(5),getdate(),108)
It will have date and time in its name every time the procedure is run.
I want to use this name in the "Select * into statement"
How can I achieve this?
i tried it with the some short code. But since my procedure has a lot of calculations and UNIONS , I cannot use that code for this. Any help would be appreciated.
declare #tablename nvarchar(30)= 'xyz ' + cast(convert(date,getdate()) as nvarchar)+' '+convert(nvarchar(5),getdate(),108)
declare #SQL_Statement nvarchar(100)
declare #SQL_Statement2 nvarchar(100)
declare #dropstatement nvarchar(100)
SET #SQL_Statement = N'SELECT * Into ' +'['+#tablename +'] '+'FROM '+ 'dimBranch'
print #SQL_Statement
EXECUTE sp_executesql #SQL_Statement
SET #SQL_Statement= N'select * from ' + '['+#tablename + '] '
print #SQL_Statement
EXECUTE sp_executesql #SQL_Statement
set #dropstatement = 'DROP TABLE' + '['+#tablename + '] '
PRINT #dropstatement
exec sp_executesql #dropstatement
Reason why I want this is because I use this procedure in ETL job as well as in SSRS report. And if someone runs the package and the SSRS report at the same time, the incorrect or weird data gets stored in the table. Therefore I need a dynamic name of the table with date and time.
You can't parameterize an identifier in SQL, only a value
--yes
select * from table where column = #value
--no
select * from #tablename where #columnname = #value
The only thin you can do to make these things dynamic is to build an sql string and execute it dynamically, but your code is already doing this with sp_executesql
More telling is your complaint at the bottom of your question, that if the procedure is invoked simultaneously it gives problems. Perhaps you should consider using local table variables for temporary data storage that the report is using rather than pushing data back into the db
DECLARE #temp TABLE(id INT, name varchar100);
INSERT INTO #temp SELECT personid, firstname FROM person;
-- work with temp data
select count(*) from #temp;
--when #temp goes out of scope it is lost,
--no other procedure invoked simultaneously can access this procedure'a #temp
Consider a local temp table, which is automatically session scoped without the need for dynamic SQL. For example:
SELECT *
INTO #YourTempTable
FROM dimBranch;
The local temp table will automatically be dropped when the proc completes so there is no need for an explict drop in the proc code.
I have dynamically created tables, like XXX_JOURNAL.
Where XXX - is table prefix (variable), and _JOURNAL - is constant in table name.
I need create UPDATE trigger on database, not on particular table, and use table name (prefix) as variable:
CREATE TRIGGER triggerName ON %_JOURNAL
FOR UPDATE
AS
UPDATE XXX_JOURNAL
SET COMPANY_ID = LEFT(tableName,3) //tableName = current table (XXX_JOURNAL)
WHERE ID = ID FROM inserted
So here I have two difficulties:
How to create one trigger for all tables LIKE %_JOURNAL?
How to use table name as the keyword for current table?
I know there are a lot of mistakes in syntax. For example, I cannot use '%_JOURNAL' as table name on trigger creation. It's just for explanation, that I need create one trigger for all dynamically created tables in future.
Any ideas?
You can use stored procedure with dynamic SQL:
CREATE PROCEDURE TriggerCreationForJournals
#XXX as nvarchar(3)
AS
BEGIN
DECLARE #sql nvarchar(max),
#triggerName nvarchar(max) = #XXX + N'_JOURNAL_UPDATE',
#objectCheck int,
#checkSQL nvarchar(max),
#params nvarchar(max) = N'#objectCheck int OUTPUT'
SELECT #checkSQL = N'SELECT #objectCheck = OBJECT_ID(N'''+#triggerName+''')'
EXEC sp_executesql #checkSQL, #params, #objectCheck = #objectCheck OUTPUT
IF #objectCheck IS NULL
BEGIN
SELECT #sql = N'
CREATE TRIGGER '+QUOTENAME(#triggerName)+' ON ['+#XXX+'_JOURNAL]
FOR UPDATE
AS
UPDATE x
SET COMPANY_ID = '''+#XXX+'''
FROM ['+#XXX+'_JOURNAL] x
INNER JOIN inserted i
ON i.ID = x.ID'
EXEC sp_executesql #sql
END
ELSE
BEGIN
PRINT 'Trigger '+QUOTENAME(#triggerName)+' already exists'
END
END
Then run this:
DECLARE #sql nvarchar(max)
SELECT #sql = (
SELECT 'EXEC TriggerCreationForJournals '''+LEFT([name],3) +''';' +CHAR(10)
FROM sys.tables
WHERE [name] LIKE '%JOURNAL'
FOR XML PATH('')
)
EXEC sp_executesql #sql
To create triggers for all tables.
In #sql there will be query like:
EXEC TriggerCreationForJournals 'AFG';
EXEC TriggerCreationForJournals 'DFG';
The purpose of stored procedure is to check if trigger on table exists - if so skip its creation, you can modify the SP to drop them if exists.
The second part is a creation of script and running the SP for all tables you need.
Hope, this answer helps you with your questions.
I have insert, update, delete triggers for every tables to logging actions.
I am retrieving before and after datas from deleted, inserted and wrapping these into xml.
But some logs can't show before and update values.
My sql statement is:
USE [cop]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[Delete] ON [dbo].[Seanslar]
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON
DECLARE #deleted AS XML
SET #deleted = (select * from deleted for xml AUTO, ELEMENTS XSINIL)
DECLARE #logIslem TINYINT
SET #logIslem = 3
DECLARE #tableName VARCHAR(200)
SELECT #tableName = OBJECT_SCHEMA_NAME( parent_id ) + '.' + OBJECT_NAME( parent_id )
FROM sys.triggers
WHERE object_id = ##PROCID
DECLARE #xmlToChar NVARCHAR(MAX)
SET #xmlToChar = CAST(#deleted AS nvarchar(MAX))
IF LEN(#xmlToChar)<10
BEGIN
IF EXISTS(select * from deleted)
select #xmlToChar = CAST(seans_id AS NVARCHAR(MAX)) from deleted
ELSE
SET #xmlToChar = 'Deleted is empty!'
END
DECLARE #allXml AS XML
SET #allXml = '<'+#tableName+'>'+ #xmlToChar +'</'+#tableName+'>'
INSERT INTO [dbo].[Logla]
([logIslem], [trgKullanici_id], [tabloAdi], [logXml])
VALUES
(#logIslem, SUSER_NAME(), #tableName, #allXml)
END
Is there any way to learn "sql statement" executed inside trigger?
There is no practical way to capture the executing SQL statement text inside of a DML Trigger fired by that statement.
You can do this with a DDL (metadata) Trigger, but not a DML (normal) Trigger.
And yes, there are one or two very impractical ways to do it, but I really do not recommend them unless:
You are very, very SQL proficient, and
You really, really need to get it, and
You can afford a lot of development and testing time
There are bunch of databases to the SQL server I am connected.
How should I query the sysobjects in order to spot in what database a stored procedure with name 'myStoredProcedure' is located ?
The query should return the database name.
Thanks
I know you are not asking for this, but I'd really download RedGate's Sql Search add-in for SSMS and use that. It allows you to find any object (proc, table, view, column, etc) on any database easily.
And it's free!
I'd give this a try:
CREATE TABLE ##DatabaseList
(
DatabaseName varchar(50)
)
EXECUTE SP_MSForEachDB 'USE [?]; INSERT INTO ##DatabaseList SELECT DB_NAME() FROM [sys].[objects] WHERE name = "MyStoredProcedure" AND type_desc = "SQL_STORED_PROCEDURE"'
SELECT * FROM ##DatabaseList
DROP TABLE ##DatabaseList
That's using the undocumented/ unsupported system stored procedure SP_MSForEachDb and writing any hits to a global temp table, then outputting the contents to the Results window before dropping the table. If you just need to know which database (or databases - there may of course be more than one) has an appropriately named SP, this should do it. If you want to use the output elsewhere as a parameter, it may take a little more work.
By the way, I'm only learning this stuff myself over the last few months so if anyone can critique the above and suggest a better way to go at it I'm happy to receive feedback. Equally, I can answer any further questions posted here to the best of my ability.
Cheers
So out of curiosity I decided to try write this myself, especially since ADG mentioned his solution was using an unsupported, undocumented procedure. This could also be expanded to take a 2nd parameter so where it checks the type = P (stored Proc) you could probably change it to look for other things like views / tables etc.
My solution is a bit long but here goes:
CREATE PROCEDURE spFindProceduresInDatabases
(
#ProcedureName NVARCHAR(99)
)
AS
BEGIN
-- Get all the database names and put them into a table
DECLARE #Db TABLE (DatabaseName Varchar(99))
INSERT INTO #Db SELECT name FROM Sys.databases
-- Declare a table to hold our results
DECLARE #results TABLE (DatabaseName VARCHAR(99))
-- Make a Loop
-- Declare a variable to be incremented
DECLARE #count INT
SET #count = 0
-- Declare the end condition
DECLARE #endCount INT
SELECT #endCount = COUNT(*) FROM #Db
-- Loop through the databases
WHILE (#count < #endCount )
BEGIN
-- Get the database we are going to look into
DECLARE #dbWeAreChecking VARCHAR(99)
SELECT TOP 1 #dbWeAreChecking = DatabaseName FROM #Db
DELETE FROM #Db WHERE DatabaseName = #dbWeAreChecking
-- Create and execute our query
DECLARE #Query NVARCHAR(3000)
SET #Query = N'SELECT #outParam = COUNT(*) FROM '+#dbWeAreChecking+'.sys.sysobjects WHERE type = ''P'' and name = #ProcedureName'
Declare #outParam INT
print (#Query)
DECLARE #ParmDefinition NVARCHAR(500)
DECLARE #IntVariable INT
SET #ParmDefinition = N'#ProcedureName VARCHAR(99),#outParam INT OUTPUT'
SET #IntVariable = 35
EXECUTE sp_executesql
#Query ,
#ParmDefinition,
#ProcedureName,
#outParam = #outParam OUTPUT
-- If we have a result insert it into the results table
If (#outParam > 0)
BEGIN
INSERT INTO #results(DatabaseName) VALUES(#dbWeAreChecking)
END
-- Increment the counter
SET #count = (#count + 1)
END
-- SELECT ALL OF THE THINGS!!!
SELECT * FROM #results
END
Is there a way to select a database from a variable?
Declare #bob as varchar(50);
Set #bob = 'SweetDB';
GO
USE #bob
Unfortunately, no.
Unless you can execute the rest of your batch as dynamic SQL.
Using execute to dynamically execute SQL will change the context for the scope of the execute statement, but will not leave a lasting effect on the scope you execute the execute statement from.
In other words, this:
DECLARE #db VARCHAR(100)
SET #db = 'SweetDB'
EXECUTE('use ' + #db)
Will not set the current database permanently, but if you altered the above code like this:
DECLARE #db VARCHAR(100)
SET #db = 'SweetDB'
EXECUTE('use ' + #db + ';select * from sysobjects')
select * from sysobjects
Then the result of those two queries will be different (assuming you're not in SweetDB already), since the first select, executed inside execute is executing in SweetDB, but the second isn't.
declare #NewDB varchar(50)
set #NewDB = 'NewDB'
execute('use ' + #NewDB)
#TempTables will presist across GOs
you can create the table in the first batch, insert/select data as necessary in that or any following batch.
here is some sample syntax:
CREATE TABLE #YourTableName
(
col1 int not null primary key identity(1,1)
,col2 varchar(10)
)