SQL Server - DDL Trigger to modify Table after its creation - sql

I have a 3rd party program which exports data to SQL Server.
However, such program can not insert, but drops and recreates each tables with the data each time it is processed.
There is a specific schema for that.
I need to run modifications on such table on SQL each time it is re-created.
I have been trying using a DDL trigger on Create, which just writes the name of the table to a LOG if it's in the target schema:
CREATE TRIGGER [tCREATE_TABLE] ON DATABASE
FOR CREATE_TABLE
AS
DECLARE #data XML;
DECLARE #schema sysname;
DECLARE #schema_s nvarchar(128);
DECLARE #object sysname;
DECLARE #object_s nvarchar(128);
DECLARE #eventType sysname;
SET #data = EVENTDATA();
SET #eventType = #data.value('(/EVENT_INSTANCE/EventType)[1]',
'sysname');
SET #schema = #data.value('(/EVENT_INSTANCE/SchemaName)[1]','sysname');
SET #schema_s = CAST(#schema as nvarchar(128));
SET #object = #data.value('(/EVENT_INSTANCE/ObjectName)[1]','sysname');
SET #object_s = CAST(#object as nvarchar(128));
-- COMMIT should unlock the table...??
COMMIT
IF #schema_s = 'TARGET_SCHEMA'
INSERT INTO [TARGET_SCHEMA].[UpdateLOG](TabName,LOAD_UPDATEDATE,LOAD_USER)
VALUES (#object_s,GETDATE(),CURRENT_USER)
-- this is the procedure that modifies the target table
EXEC [ProcessTableProcedure]
#targettab = #object_s
GO
This works for writing to a log, but as soon as I add call the processing procedure it returns an error.
At the end of the trigger, the modifying procedure won't find the table - since the transaction is not committed yet.
I have put a COMMIT before that, but then nothing happens (procedure is not run).
I assume the trigger ends after the COMMIT...?
Any help?

Related

Commit transaction outside the current transaction (like autonomous transaction in Oracle)

I need to write into a log table from a stored procedure.
Now this log info has to survive a rollback offcourse.
I know this question has been asked before, but my situation is different and I cannot find an answer to my problem in these questions.
When there is no error in the stored procedure things are simple, the entry in the logtable will just be there.
When there is an error than things are complicated.
Inside the procedure I can do rollback in the catch and then insert the data into the log table, I know that and I am already doing that.
But the problem is when the stored procedure is called like this :
begin transaction
exec myStoredProcedure
rollback transaction
select * from myLogTable
I know this code makes not much sense, I kept it mimimal to demonstrate my problem.
If the caller of the stored procedure does the commit/rollback then it does not matters what I do in the stored procedure. My logentry will always be rolled back.
I also cannot use the temporary table trick, which is to return the data I want to log and let the caller use that data to insert it into the logtable after it has done the rollback, because the caller is an external application that I do not have the source from.
The logging is done in a seperate procedure that only has one line of code, the insert into the logtable.
What I need is a way to commit the insert in this procedure, outside the current transaction so it survives any rollback.
Is there a way to do this ?
The Solution:
I used lad2025 answer and thus far it is working without problems or performance issues.
But this procedure will only be called about 1000 times each day which is not that much so I guess I don't have to expect any problems either.
It is quite interesting topic so let's check how MS approaches it.
First documentation: Migrating-Oracle-to-SQL-Server-2014-and-Azure-SQL-DB.pdf
Page 152.
Simulating Oracle Autonomous Transactions
This section describes how SSMA for Oracle V6.0 handles autonomous transactions
(PRAGMA AUTONOMOUS_TRANSACTION). These autonomous transactions do not
have direct equivalents in Microsoft SQL Server 2014.
When you define a PL/SQL block (anonymous block, procedure, function, packaged
procedure, packaged function, database trigger) as an autonomous transaction, you
isolate the DML in that block from the caller's transaction context. The block becomes
an independent transaction started by another transaction, referred to as the main
transaction.
To mark a PL/SQL block as an autonomous transaction, you simply include the
following statement in your declaration section:
PRAGMA AUTONOMOUS_TRANSACTION;
SQL Server 2014 does not support autonomous transactions. The only way to isolate a
Transact-SQL block from a transaction context is to open a new connection.
Use the xp_ora2ms_exec2 extended procedure and its extended version
xp_ora2ms_exec2_ex, bundled with the SSMA 6.0 Extension Pack, to open new
transactions. The procedure's purpose is to invoke any stored procedure in a new
connection and help invoke a stored procedure within a function body. The
xp_ora2ms_exec2 procedure has the following syntax:
xp_ora2ms_exec2
<active_spid> int,
<login_time> datetime,
<ms_db_name> varchar,
<ms_schema_name> varchar,
<ms_procedure_name> varchar,
<bind_to_transaction_flag> varchar,
[optional_parameters_for_procedure];
Then you need to install on your server stored procedures and other scripts:
SSMA for Oracle Extension Pack (only SSMA for Oracle Extension Pack.7.5.0.msi).
Your stored procedure will become:
CREATE TABLE myLogTable(i INT IDENTITY(1,1),
d DATETIME DEFAULT GETDATE(),
t NVARCHAR(1000));
GO
CREATE OR ALTER PROCEDURE my_logging
#t NVARCHAR(MAX)
AS
BEGIN
INSERT INTO myLogTable(t) VALUES (#t);
END;
GO
CREATE OR ALTER PROCEDURE myStoredProcedure
AS
BEGIN
-- some work
SELECT 1;
INSERT INTO myLogTable(t)
VALUES ('Standard logging that will perish after rollback');
DECLARE #login_time DATETIME = GETDATE();
DECLARE #custom_text_to_log NVARCHAR(100);
SET #custom_text_to_log=N'some custom loging that should survive rollback';
DECLARE #database_name SYSNAME = DB_NAME();
EXEC master.dbo.xp_ora2ms_exec2_ex
##spid,
#login_time,
#database_name,
'dbo',
'my_logging',
'N',
#custom_text_to_log;
END;
And final call:
begin transaction
exec myStoredProcedure
rollback transaction
select * from myLogTable;
OUTPUT:
i d t
2 2017-08-21 some custom loging that should survive rollback
So you really search for some sort of Autonomous transaction (like in Oracle).
One ugly way to simulate it is to use loopback linked server.
Warning: This is PoC (I would think twice before I would use it in PROD) and do a lot of testing.
DECLARE #servername SYSNAME;
SET #servername = CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'));
EXECUTE sys.sp_addlinkedserver
#server = N'loopback',
#srvproduct = N'',
#provider = N'SQLNCLI',
#datasrc = #servername;
EXECUTE sys.sp_serveroption
#server = N'loopback',
#optname = 'RPC OUT',
#optvalue = 'ON';
EXECUTE sys.sp_serveroption
#server = N'loopback',
#optname = 'remote proc transaction promotion',
#optvalue = 'OFF';
And code:
DROP TABLE IF EXISTS myLogTable;
CREATE TABLE myLogTable(i INT IDENTITY(1,1),
d DATETIME DEFAULT GETDATE(),
t NVARCHAR(1000));
GO
CREATE OR ALTER PROCEDURE my_logging
#t NVARCHAR(MAX)
AS
BEGIN
INSERT INTO myLogTable(t) VALUES (#t);
END;
GO
CREATE OR ALTER PROCEDURE myStoredProcedure
AS
BEGIN
-- some work
SELECT 1;
INSERT INTO myLogTable(t)
VALUES ('Standard logging that will perish after rollback');
EXEC loopback.T1.dbo.my_logging
#t = N'some custom loging that should survive rollback';
END;
Final call:
begin transaction
exec myStoredProcedure
rollback transaction
select * from myLogTable
Output:
i d t
2 2017-08-17 some custom loging that should survive rollback

SQL Data in Trigger was not Commited : The partner transaction manager has disabled its support

I have created an After Insert Trigger on SQL Server.
USE [CSPFContactCenter]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[SendSMS_INSERT]
ON [dbo].[tblSmsSend]
AFTER INSERT
AS
BEGIN
declare #Text nvarchar(max)
declare #MobileNumber nvarchar(max)
set #Text = (SELECT TOP 1 Text FROM tblSmsSend)
set #MobileNumber = (SELECT TOP 1 MobileNumber FROM tblSmsSend)
exec SqlStoredProcedure1 #Text, #MobileNumber
END
The Stored Procedure that will execute in this trigger, want to insert a new record to another database in another server.
I can Run the body of trigger and it work properly:
(I mean this part:)
declare #Text nvarchar(max)
declare #MobileNumber nvarchar(max)
set #Text = (SELECT TOP 1 Text FROM tblSmsSend)
set #MobileNumber = (SELECT TOP 1 MobileNumber FROM tblSmsSend)
exec SqlStoredProcedure1 #Text, #MobileNumber
But When I use it in Trigger, this exception will accure :
Thanks for any helping
Edit :
I have Enabled Network DTC Access, but nothing changed
Its just insert a record, nothing more. But in another server database
So is 'nothing more' than just a distributed transaction. Which your server explicitly forbids, as the message clearly indicates.
When you run the exec SqlStoredProcedure1 on its own you do not create a distributed transaction as you did not start a local transaction. By contrast when running from a trigger you always must be already enrolled in a local transaction which therefore must escalate to a distributed transaction.
There is no solution. Either talk to the administrator of the remote server to allow distributed transaction (I for one would not allow this in his place) or, better, design a solution that does not involve remote servers. Use replication or service broker instead.

Dynamic change schema in SQL procedure

I have a database with multiple schemas. In every schema I got table called [Logs], so my database tables looks like:
[s1].[Logs]
[s2].[Logs]
[s3].[Logs]
...
[sN].[Logs]
Every day I would like to run stored procedure, which will do same operations on every above table. Is there a way to pass schema name into stored procedure? I am using SQL on Azure.
No, it is not - unless the SP Uses then dynamic SQL to execute some SQL String you constructed in the SP.
This happens via the sp_executesql stored procedure
http://technet.microsoft.com/en-us/library/ms188001.aspx
has more information.
Microsoft has a few undocumented procedures that perform "foreach" operations on tables (sp_msforeachtable) and databases (sp_msforeachdb). Both of these rely on another undocumented proc called sp_msforeachworker which you might be able to exploit to create a foreachschema type of routine. Theres an article (reg required) here that demonstrates this approach.
That said, its unlikely Azure supports anything of these, so you might have to fashion your own using a crude loop:
declare #schemas table (i int identity(1,1), name sysname);
insert into #schemas
select name from sys.schemas where name like 's[0-9]%';
declare #i int, #name sysname, #cmd nvarchar(max);
select #i = min(i) from #schemas;
while #i is not null
begin
select #name = name from #schemas where i = #i;
set #cmd = replace(N'select count(*) from [{0}].[Logs];', '{0}', #name);
print #cmd;
--exec(#cmd);
select #i = min(i) from #schemas where i > #i;
end

SQL eventdata()

when using a trigger in SQL SERVER 2005 the eventdata() always return a empty value.
only the date inserted in the Audit table, other fields are NULL valus
pls help me
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [dbo].[TrigDip]
ON [dbo].[Dip]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
DECLARE #ed XML
SET #ed = EVENTDATA()
DECLARE #db varchar(1000)
set #db =EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]', 'varchar(256)')
PRINT 'CREATE TABLE Issued.'
INSERT INTO Audit (PostTime, DatabaseName, Event, ObjectName, TSQL, Login)
VALUES
(
GetDate(),
#db,
EVENTDATA().value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)'),
EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'varchar(256)'),
EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(2000)'),
CONVERT(nvarchar(100), CURRENT_USER)
)
END
EVENTDATA on MSDN states
EVENTDATA returns data only when referenced directly inside of a DDL or logon trigger. EVENTDATA returns null if it is called by other routines, even if those routines are called by a DDL or logon trigger.
Your trigger above is a DML AFTER trigger.
You need
CREATE TRIGGER TRG_CreateTable
ON DATABASE
FOR CREATE_TABLE
...

How to get sql statement in trigger

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