Dynamic Query Different Behavior While Trigger Creation - tsql

Below is part of my code that dynamically creates Trigger. The problem I am facing here is when I run this scrip using Agent Job, the QUOTED_IDENTIFIER within the trigger is set to OFF by default. But when I run it by myself as adhoc query, the trigger ends up with SET QUOTED_IDENTIFIER ON option. That's the desired behavior.
SET #cmd3 = N'EXEC ' + #dbName + '..sp_executesql N''
CREATE TRIGGER TRG_LogAudit ON DATABASE
FOR
-- Procedures
CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE,
-- Tables
CREATE_TABLE, ALTER_TABLE, DROP_TABLE,
-- Views
CREATE_VIEW, ALTER_VIEW, DROP_VIEW,
-- Functions
CREATE_FUNCTION, ALTER_FUNCTION, DROP_FUNCTION,
-- Schemas
CREATE_SCHEMA, ALTER_SCHEMA, DROP_SCHEMA,
-- Triggers
CREATE_TRIGGER, ALTER_TRIGGER, DROP_TRIGGER,
-- Others
RENAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #EventData XML = EVENTDATA();
DECLARE #ip VARCHAR(32) = ( SELECT client_net_address
FROM sys.dm_exec_connections
WHERE session_id = ##SPID
);
INSERT meta.LogAudit
(EventType
,EventDDL
,EventXML
,DatabaseName
,SchemaName
,ObjectName
,HostName
,IPAddress
,ProgramName
,LoginName
)
SELECT #EventData.value(''''(/EVENT_INSTANCE/EventType)[1]'''', ''''NVARCHAR(100)'''')
,#EventData.value(''''(/EVENT_INSTANCE/TSQLCommand)[1]'''', ''''NVARCHAR(MAX)'''')
,#EventData
,DB_NAME()
,#EventData.value(''''(/EVENT_INSTANCE/SchemaName)[1]'''', ''''NVARCHAR(255)'''')
,#EventData.value(''''(/EVENT_INSTANCE/ObjectName)[1]'''', ''''NVARCHAR(255)'''')
,HOST_NAME()
,#ip
,PROGRAM_NAME()
,SUSER_SNAME();
END;
''';
....
How to modify my script in order to make sure trigger would always have QUOTED_IDENTIFIER option set to ON? I have tried some nested EXEC commands but with not success.

Related

Value not Store in Dynamic SQL

I've different different tables to categorically store data and a log table where all the transactions log are recorded
e.g. 1) VoucherNO, Add, ...
2) VoucherNO, Delete, ..
After I backup the database and restore in another server for my Reporting Purpose. That time I want to ensure all the log data and transaction are available in TestDB if not then I remove log from 'AUD_USER_ACTIVITY'.
To find the transaction exist or not, I create a dynamic sql select statement and check whether record is exist or not.
Basis on #RecExist Value I do the action like if records is not available in TestDB the log will be remove, if record exist immediately break this loop and going for next procedure
But #RecExist variable is not updating in Dynamic SQL Execution. Please guide me
declare #MvDocNo varchar(50)
DECLARE #SCtr as DECIMAL(10,0)
declare #LocationCode varchar(4)
declare #UName Nvarchar(40)
declare #toe varchar(30)
declare #QryTxt as nvarchar(MAX);
Declare #RecExist as INT =0;
SET #RecExist=0
WHILE #RecExist=0
BEGIN
select top 1 #MvDocNo=DOCNO, #SCtr=SrlNo,#LocationCode =DMLTYPE,#UName=TABLENAME
FROM R_AUDDB..AUD_USER_ACTIVITY
WHERE DBNAME='TestDB' and DMLTYPE not in ('AD','D','PD') ORDER BY SRLNO DESC;
select top 1 #toe=docno from TestDB..M_TYPEOFENTRY where TBLNAME=#UName;
set #QryTxt='Select #RecExist=1 From R_TestDB..'+#UName+ ' Where '+#toe+'='''+#MvDocNo+''''
exec (#QryTxt)
IF #RecExist=0
BEGIN
DELETE R_AUDDB..AUD_USER_ACTIVITY WHERE SRLNO=#SCtr
END
END
The following code sample demonstrates how to check for a row in a table with a specific column and value using dynamic SQL. You ought to be able to change the values of the first three variables to reference a table and column in your database for testing.
Note that SQL injection is still possible: there is no validation of the table or column names.
-- Define the table to check and the target column name and value.
declare #TableName as SysName = 'Things';
declare #ColumnName as SysName = 'ThingName';
declare #TestValue as NVarChar(32) = 'Beth';
-- Create a SQL statement to check for a row in the target table with the specified column name and value.
declare #SQL as NVarChar(1024);
declare #Result as Bit;
-- Note that only object names are substituted into the statement at this point and QuoteName() is used to reduce problems.
set #SQL = N'select #iResult = case when exists ( select 42 from dbo.' + QuoteName( #TableName ) +
N' where ' + QuoteName( #ColumnName ) + N' = #iTestValue ) then 1 else 0 end;'
select #SQL as SQL;
-- Execute the SQL statement.
-- Note that parameters are used for all values, i.e. the target value and return value.
execute sp_executesql #stmt = #SQL,
#params = N'#iTestValue NVarChar(32), #iResult Bit output',
#iTestValue = #TestValue, #iResult = #Result output
-- Display the result.
select #Result as Result;

DDL trigger to remove the article from the replication when it is altered

Using SQL Server 2008 for Transnational replication.
Some times my team folks altering the table during development before removing the table from subscription in the replication. So it leads to replication failure in the environment. To avoid that, planned to write a DDL trigger which will remove the table from subscription when it is getting altered.
Below is the trigger i have written to achieve the same.
CREATE TRIGGER RemoveArticleFromRepliction ON DATABASE
FOR DROP_TABLE
, ALTER_TABLE AS
BEGIN
SET NOCOUNT ON;
DECLARE #EventData XML = EVENTDATA();
DECLARE #TableName VARCHAR(255) = #EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
DECLARE #CMD VARCHAR(255) = 'IF EXISTS (SELECT 1 FROM SYS.TABLES WHERE NAME = ''' + #TableName + ''') EXEC sp_mck_DropSubscriptionArticle ''' + #TableName + ''''
EXEC #CMD
END
The trigger applied successfully in SQL server 2008.
Note: sp_mck_DropSubscriptionArticle is a custom procedure. the logic to remove the article from subscription is added in it.
When i execute the alter statement, i am getting the below error
Could not find stored procedure 'IF EXISTS (SELECT 1 FROM SYS.TABLES WHERE NAME = 'NOTE') EXEC sp_mck_DropSubscriptionArticle 'NOTE''.
Please help me resolve this issue or suggest me any other approach to fix this problem
Looks like you are missing the brackets after EXEC. Please try the below changed code
CREATE TRIGGER RemoveArticleFromRepliction ON DATABASE
FOR DROP_TABLE
, ALTER_TABLE AS
BEGIN
SET NOCOUNT ON;
DECLARE #EventData XML = EVENTDATA();
DECLARE #TableName VARCHAR(255) = #EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
DECLARE #CMD VARCHAR(255) = 'IF EXISTS (SELECT 1 FROM SYS.TABLES WHERE NAME = ''' + #TableName + ''') EXEC sp_mck_DropSubscriptionArticle ''' + #TableName + ''''
EXEC (#CMD)
END

Incorrect Stored Procedure Syntax

I am using the below stored procedure to upload files into a db where each file has a correspondence table but there are times when the table not exist in that case i want to add the file name into a table called NewTables.I can not get the stored proc syntax working can someone help me.I believe the mistake is in the first part where i check if the table exist
ALTER proc [dbo].[UploadCSVFiles]
#FilePath varchar(100) ,
#FileName varchar(100),
#TableName varchar(250)
AS
BEGIN
DECLARE #SqlStmt nvarchar(max)
DECLARE #ErrorCode int
SET #SqlStmt='Truncate table dbo.[' + #TableName +']'
EXEC(#SqlStmt);
set #SqlStmt =N'
IF not EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N''[dbo].['+#TableName +N']'') AND type in (N''U''))
BEGIN
INSERT INTO dbo.NewTables ('+#TableName+N','+#FileName+N') Values('+#TableName+N','+#FileName+N')
END
ELSE
BEGIN
INSERT INTO '+#TableName+N'
select *
from openrowset(''MSDASQL''
,''Driver={Microsoft Access Text Driver (*.txt, *.csv)};
DefaultDir='+#FilePath+N'''
,''select * from "'+#FileName+N'"'')
END
'
EXEC(#SqlStmt);
Thanks
Rao
Thanks fpop and Christine, I have made the suggestions you made but still I get an error
Msg 4701, Level 16, State 1, Line 1
Cannot find the object "Customer" because it does not exist or you do not have permissions.
it seems the If statement does not insert the new table
here is the final version
USE [MyDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER proc [dbo].[UploadFiles_2]
#FilePath varchar(100) ,
#FileName varchar(100),
#TableName varchar(250)
AS
BEGIN
DECLARE #SqlStmt nvarchar(max)
DECLARE #ErrorCode int
SET #SqlStmt='Truncate table dbo.[' + #TableName +']'
EXEC sp_executesql #SqlStmt;
set #SqlStmt =N'
IF ( NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N''[dbo].['+#TableName +N']'') AND type in (N''U''))
BEGIN
INSERT INTO dbo.NewTables (TableName,FileName) Values('''+#TableName+N''','''+#FileName+N''')
END
ELSE
BEGIN
INSERT INTO '+#TableName+N'
select *
from openrowset(''MSDASQL''
,''Driver={Microsoft Access Text Driver (*.txt, *.csv)};
DefaultDir='+#FilePath+N'''
,''select * from "'+#FileName+N'"'')
END'
EXEC sp_executesql #SqlStmt;
END
Hint: You can always add PRINT #SQLSTMT to see the code your procedure has generated.
There are two error in the script, both in line:
INSERT INTO dbo.NewTables ('+#TableName+N','+#FileName+N') Values('+#TableName+N','+#FileName+N')
First error: NewTables table must have 2 columns to keep table name and file name. Let's call them COL_TBL, COL_FILE
Second error: You have to add quotes in Values part of statement
Here is how it should look like:
INSERT INTO dbo.NewTables (COL_TBL, COL_FILE) Values('''+#TableName+N''','''+#FileName+N''')
btw, You didn't post code entirely, there is an END at the end missing. Please, next time copy the whole code so others can reproduce the error.
EDIT: Please consider following: using sp_executesql instead of EXEC, to avoid sql injection bugs do not concatenate parameters into dynamic sql and finally check if table exists before truncating it
Try this
IF (NOT EXISTS (SELECT * FROM sys.objects WHERE [type] LIKE 'U' AND name LIKE 'mytable'))
SELECT 'not found';
ELSE
SELECT 'found';

Call stored proc from after insert trigger

Perhaps a stupid question!
If I call a stored proc from an After Insert trigger (T-SQL) - then how do I get the values of the "just inserted" data?
e.g.
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
EXEC createAuditSproc 'I NEED VALUES HERE!'
I don't have any identity columns to worry about - I just want to use some of the "just inserted" values to pass into my sproc.
Edit: For clarification - I need this to call a sproc and not do a direct insert to the table, since the sproc does more than one thing. I'm working with some legacy tables I can't currently amend to do things 'properly' (time/resource/legacy code), so I have to work with what I have :(
You get to the newly 'changed' data by using the INSERTED and DELETED pseudo-tables:
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
INSERT INTO myTableAudit(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
END
Given the example tables
create table myTable
(
ID INT identity(1,1),
Name varchar(10)
)
GO
create table myTableAudit
(
ID INT,
Name varchar(10),
TimeChanged datetime default CURRENT_TIMESTAMP
)
GO
Edit : Apologies, I didn't address the bit about calling a Stored Proc. As per marc_s's comment, note that inserted / deleted can contain multiple rows, which complicates matters with a SPROC. Personally, I would leave the trigger inserting directly into the audit table without the encapsulation of a SPROC. However, if you have SQL 2008, you can use table valued parameters, like so:
CREATE TYPE MyTableType AS TABLE
(
ID INT,
Name varchar(10)
);
GO
CREATE PROC dbo.MyAuditProc #MyTableTypeTVP MyTableType READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO myTableAudit(ID, Name)
SELECT mtt.ID, mtt.Name
FROM #MyTableTypeTVP mtt;
END
GO
And then your trigger would be altered as like so:
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MyTableTypeTVP AS MyTableType;
INSERT INTO #MyTableTypeTVP(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
EXEC dbo.MyAuditProc #MyTableTypeTVP;
END
you can then test that this works for both a single and multiple inserts
insert into dbo.MyTable values ('single');
insert into dbo.MyTable
select 'double'
union
select 'insert';
However, if you are using SQL 2005 or lower, you would probably need to use a cursor to loop through inserted passing rows to your SPROC, something too horrible to contemplate.
As a side note, if you have SQL 2008, you might look at Change Data Capture
Edit #2 : Since you need to call the proc, and if you are certain that you only insert one row ...
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SomeInt INT;
DECLARE #SomeName VARCHAR(10);
SELECT TOP 1 #SomeInt = i.ID, #SomeName = i.Name
FROM INSERTED i;
EXEC dbo.MyAuditProc #SomeInt, #SomeName;
END;

Array-like access to variables in T-SQL

In my stored procedure I have multiple similar variables #V1, #V2 ... #V20 (let's say 20 of them) FETCHED from a record. How would I use dynamic SQL to make 20 calls to another stored procedure using those variables as parameters?
Of course #V[i] syntax is incorrect, but it expresses the intent
fetch next from maincursor into #status, #V1, #V2, ...
while #i<21
begin
-- ??? execute sp_executesql 'SecondSP', '#myParam int', #myParam=#V[i]
-- or
-- ??? execute SecondSP #V[i]
set #i = #i+1
end
As others have said, set up a temporary table, insert the values that you need into it. Then "iterate" through it executing the necessary SQL from those values. This will allow you to have 0 to MANY values to be executed, so you don't have to set up a variable for each.
The following is a complete sample of how you may go about doing that without cursors.
SET NOCOUNT ON
DECLARE #dict TABLE (
id INT IDENTITY(1,1), -- a unique identity column for reference later
value VARCHAR(50), -- your parameter value to be passed into the procedure
executed BIT -- BIT to mark a record as being executed later
)
-- INSERT YOUR VALUES INTO #dict HERE
-- Set executed to 0 (so that the execution process will pick it up later)
-- This may be a SELECT statement into another table in your database to load the values into #dict
INSERT #dict
SELECT 'V1Value', 0 UNION ALL
SELECT 'V2Value', 0
DECLARE #currentid INT
DECLARE #currentvalue VARCHAR(50)
WHILE EXISTS(SELECT * FROM #dict WHERE executed = 0)
BEGIN
-- Get the next record to execute
SELECT
TOP 1 #currentid = id
FROM #dict
WHERE executed = 0
-- Get the parameter value
SELECT #currentvalue = value
FROM #dict
WHERE id = #currentid
-- EXECUTE THE SQL HERE
--sp_executesql 'SecondSP', '#myParam int', #myParam =
PRINT 'SecondSP ' + '#myParam int ' + '#myParam = ' + #currentvalue
-- Mark record as having been executed
UPDATE d
SET executed = 1
FROM #dict d
WHERE id = #currentid
END
Use a #TempTable
if you are at SQL Server 2005 you can create a #TempTable in the parent stored procedure, and it is available in the child stored procedure that it calls.
CREATE TABLE #TempTable
(col1 datatype
,col2 datatype
,col3 datatype
)
INSERT INTO #TempTable
(col1, col2, col3)
SELECT
col1, col2, col3
FROM ...
EXEC #ReturnCode=YourOtherProcedure
within the other procedure, you have access to #TempTable to select, delete, etc...
make that child procedure work on a set of data not on one element at a time
remember, in SQL, loops suck performance away!
Why not just use the table variable instead, and then just loop through the table getting each value.
Basically treat each row in a table as your array cell, with a table that has one column.
Just a thought. :)
This seems like an odd request - will you always have a fixed set of variables? What if the number changes from 20 to 21, and so on, are you constantly going to have to be declaring new variables?
Is it possible, instead of retrieving the values into separate variables, to return them each as individual rows and just loop through them in a cursor?
If not, and you have to use the individual variables as explained, here's one solution:
declare #V1 nvarchar(100)
set #V1 = 'hi'
declare #V2 nvarchar(100)
set #V2 = 'bye'
declare #V3 nvarchar(100)
set #V3 = 'test3'
declare #V4 nvarchar(100)
set #V4 = 'test4'
declare #V5 nvarchar(100)
set #V5 = 'end'
declare aCursor cursor for
select #V1
union select #V2 union select #V3
union select #V4 union select #V5
open aCursor
declare #V nvarchar(100)
fetch next from aCursor into #V
while ##FETCH_STATUS = 0
begin
exec TestParam #V
fetch next from aCursor into #V
end
close aCursor
deallocate aCursor
I don't really like this solution, it seems messy and unscalable. Also, as a side note - the way you phrased your question seems to be asking if there are arrays in T-SQL. By default there aren't, although a quick search on google can point you in the direction of workarounds for this if you absolutely need them.