Hi I have a database where we often need to delete records, however as this can sometimes be mistakenly done I was wondering is there a way to save a sort of history of SQL commands done, and the output to screen log of them? Thanks
Try this:
USE YourDatabase;
GO
CREATE TABLE ddl_log (PostTime datetime, DB_User nvarchar(100), Event nvarchar(100), TSQL nvarchar(2000));
GO
CREATE TRIGGER log
ON DATABASE
FOR DDL_DATABASE_LEVEL_EVENTS
AS
DECLARE #data XML
SET #data = EVENTDATA()
INSERT ddl_log
(PostTime, DB_User, Event, TSQL)
VALUES
(GETDATE(),
CONVERT(nvarchar(100), CURRENT_USER),
#data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)'),
#data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(2000)') ) ;
GO
--Test the trigger
CREATE TABLE TestTable (a int)
DROP TABLE TestTable ;
GO
SELECT * FROM ddl_log ;
GO
Reference
I see that you are using Sql Server.
You can write a trigger as someone suggest or you can configure Change Data Capture. CDC will have better performance.
Related
I have the following DDL trigger:
CREATE TABLE ddl_log (PostTime datetime, DB_User nvarchar(100), Event nvarchar(100), TSQL nvarchar(2000));
GO
CREATE TRIGGER log
ON DATABASE
FOR DDL_DATABASE_LEVEL_EVENTS
AS
DECLARE #data XML;
SET #data = EVENTDATA();
INSERT ddl_log
(PostTime, DB_User, Event, TSQL)
VALUES
(GETDATE(),
CONVERT(nvarchar(100), CURRENT_USER),
#data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)'),
#data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(2000)') ) ;
GO
When I use SSMS (ie. UI) to enable/disable a table trigger, an event is inserted into the table.
However, when I use T-SQL as follows:
ENABLE trigger tr_mytable ON mytable
No event is inserted into the table.
Is this by design?
That is because GUI uses legacy syntax ALTER TABLE DISABLE TRIGGER that causes DDL trigger to fire, but the new syntax 'DISABLE TRIGGER' does not.
It's not catched by default trace too, some time ago you could vote this problem here: Fire a DDL TRIGGER when the new syntax "DISABLE TRIGGER" is executed
Now i's closed as Won't fix
We have a large Database (Many Table, SP, Function and...) and our database programers are over 10 person. All of theme can use the database on our main server computer or on his local system.
Our requirements:
we want each db programmer can apply his definitions (create table, triger, sp, fn &...) and modifications (on tables, sp, trigers, fn &...) by SQL SERVER MANAGMENT STUDIO (or your suggest tool) and get all of this activities as a TSQL Script at the end of each day.
So, we want a automatic log of our db programers activities as a TSQL script (sequence of sub tsql queries). for example:
My Activities:
My first activity>> I open my Customer table as design mode, by right
click on table and click Design. then i change datatype of ID field
from INT to BigInt. then save
My second activity>> I change
PR_Customer_Insert stored procedure and i execute it.
Now i want an executable log like this:
-- UserName: Ram
-- 2013-02-10 10:20:35
Alter Table ALTER TABLE Customer ALTER COLUMN Id TYPE bigint;
Go
-- UserName: Ram
-- 2013-02-10 10:45:00
Drop Sp DROP PROCEDURE dbo.PR_Customer_Insert;
-- UserName: Ram
-- 2013-02-10 10:45:00
Create sp CREATE PROCEDURE PR_Customer_Insert
#id int,
#name nvarchar(30) AS
INSERT INTO Customer
(
#id,
#name
)
GO
We know compare tow database is a solution but we want access to SSMS Query Pipeline...
My Questions:
Is there any way for this request (an automatically generated log that is an executable TSQL script)?
What is the best solutions for generate this scripts automatically???
What do you think about this idea in a team work on database?
Thanks...
Possible use DDL triggers, but on a certain database
Simple example:
--Create table EvtLog
CREATE TABLE EvtLog
(
LoginName NVARCHAR(100),
PostTime DATETIME,
EventType NVARCHAR(100),
TSQLCommand NVARCHAR(2000)
)
GO
--Create the DDL trigger
CREATE TRIGGER trPreventTblChange
ON DATABASE
FOR DROP_TABLE, CREATE_TABLE, ALTER_TABLE,
DROP_PROCEDURE, CREATE_PROCEDURE, ALTER_PROCEDURE
AS
DECLARE #Data XML
SET #Data = EventData()
INSERT EvtLog (LoginName, PostTime, EventType, TSQLCommand)
SELECT #Data.value('(/EVENT_INSTANCE/LoginName)[1]', 'nvarchar(100)'),
#Data.value('(/EVENT_INSTANCE/PostTime)[1]', 'nvarchar(100)'),
#Data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)'),
#Data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(2000)');
GO
Set up a database DDL trigger and use the EVENTDATA Function to find out more information about what was done and insert that into a table that you can query at the end of the day
Example
CREATE TRIGGER [LogDDL] ON DATABASE
FOR DDL_DATABASE_LEVEL_EVENTS
AS
DECLARE #xml XML;
SELECT #xml = EVENTDATA();
INSERT ChangeLog
(
ObjectName,
SQL
)
VALUES
(
#xml.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname'),
#xml.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(max)')
);
We consume a web service that decided to alter the max length of a field from 255. We have a legacy vendor table on our end that is still capped at 255. We are hoping to use a trigger to address this issue temporarily until we can implement a more business-friendly solution in our next iteration.
Here's what I started with:
CREATE TRIGGER [mySchema].[TruncDescription]
ON [mySchema].[myTable]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [mySchema].[myTable]
SELECT SubType, type, substring(description, 1, 255)
FROM inserted
END
However, when I try to insert on myTable, I get the error:
String or binary data would be
truncated. The statement has been
terminated.
I tried experimenting with SET ANSI_WARNINGS OFF which allowed the query to work but then simply didn't insert any data into the description column.
Is there any way to use a trigger to truncate the too-long data or is there another alternative that I can use until a more eloquent solution can be designed? We are fairly limited in table modifications (i.e. we can't) because it's a vendor table, and we don't control the web service we're consuming so we can't ask them to fix it either. Any help would be appreciated.
The error cannot be avoided because the error is happening when the inserted table is populated.
From the documentation:
http://msdn.microsoft.com/en-us/library/ms191300.aspx
"The format of the inserted and deleted tables is the same as the format of the table on which the INSTEAD OF trigger is defined. Each column in the inserted and deleted tables maps directly to a column in the base table."
The only really "clever" idea I can think of is to take advantage of schemas and the default schema used by a login. If you can get the login that the web service is using to reference another table, you can increase the column size on that table and use the INSTEAD OF INSERT trigger to perform the INSERT into the vendor table. A variation of this is to create the table in a different database and set the default database for the web service login.
CREATE TRIGGER [myDB].[mySchema].[TruncDescription]
ON [myDB].[mySchema].[myTable]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [VendorDB].[VendorSchema].[VendorTable]
SELECT SubType, type, substring(description, 1, 255)
FROM inserted
END
With this setup everything works OK for me.
Not to state the obvious but are you sure there is data in the description field when you are testing? It is possible they change one of the other fields you are inserting as well and maybe one of those is throwing the error?
CREATE TABLE [dbo].[DataPlay](
[Data] [nvarchar](255) NULL
) ON [PRIMARY]
GO
and a trigger like this
Create TRIGGER updT ON DataPlay
Instead of Insert
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [tempdb].[dbo].[DataPlay]
([Data])
(Select substring(Data, 1, 255) from inserted)
END
GO
then inserting with
Declare #d as nvarchar(max)
Select #d = REPLICATE('a', 500)
SET ANSI_WARNINGS OFF
INSERT INTO [tempdb].[dbo].[DataPlay]
([Data])
VALUES
(#d)
GO
I am unable to reproduce this issue on SQL 2008 R2 using:
Declare #table table ( fielda varchar(10) )
Insert Into #table ( fielda )
Values ( Substring('12345678901234567890', 1, 10) )
Please make sure that your field is really defined as varchar(255).
I also strongly suggest you use an Insert statement with an explicit field list. While your Insert is syntactically correct, you really should be using an explicit field list (like in my sample). The problem is when you don't specify a field list you are at the mercy of SQL and the table definition for the field order. When you do use a field list you can change the order of the fields in the table (or add new fields in the middle) and not care about your insert statements.
I'm trying to find out what is updating the value of a column and i have very little knowledge of the application. At a quick glance I've noticed about 90% of the applications business logic is handled on the database. Needless to say the depth of SP's, functions, and triggers is crazy.
I'd like to create a trigger on the table in question that will log the SQL that affected the table. What SQL could be used to grab the executed SQL in the context of the table being updated?
Details:
MS SQL Server 2008
Thanks!!
I realise this issue has already been resolved but I was interested in how it could be resolved using SQL Server 2008 extended events. This is my first play with XEvents so I'm sure there's lots to improve!
Script to setup test Database
CREATE TABLE [dbo].[TableWithMysteryUpdate](
[Period] [int] NOT NULL,
[ColumnThatWillBeUpdated] [int] NOT NULL
) ON [PRIMARY]
GO
INSERT [dbo].[TableWithMysteryUpdate] ([Period], [ColumnThatWillBeUpdated]) VALUES (1, 20)
INSERT [dbo].[TableWithMysteryUpdate] ([Period], [ColumnThatWillBeUpdated]) VALUES (2, 23)
GO
CREATE TABLE [dbo].[TestTable](
[foo] [int] IDENTITY(1,1) NOT NULL,
[bar] [nchar](10) NOT NULL,
CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED
(
[foo] ASC
)
)
GO
CREATE TRIGGER [dbo].[triggerCausingMysteryUpdate]
ON [dbo].[TestTable]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE [dbo].[TableWithMysteryUpdate]
SET [Period] = [Period]+1
END
GO
CREATE PROCEDURE [dbo].[Proc4]
AS
BEGIN
INSERT INTO [dbo].[TestTable]
(
[bar])
VALUES
('Test')
END
GO
CREATE PROCEDURE [dbo].[Proc3]
AS
BEGIN
EXEC dbo.Proc4
END
GO
CREATE PROCEDURE [dbo].[Proc2]
AS
BEGIN
EXEC dbo.Proc3
END
GO
CREATE PROCEDURE [dbo].[Proc1]
AS
BEGIN
EXEC dbo.Proc2
END
So the scenario is that TableWithMysteryUpdate is being updated but I'm not sure by what. I'll add an update trigger that does nothing in order to be able to filter on this object.
CREATE TRIGGER [dbo].[triggerAfterUpdate]
ON [dbo].[TableWithMysteryUpdate]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
END
Then run the script to create the XEvents Session, fire the procedure that will eventually down the call stack cause the Update to occur, and then stop the session.
USE TestDB
DECLARE #DynSql nvarchar(max)
SET #DynSql = '
IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name=''test_trace'')
DROP EVENT SESSION [test_trace] ON SERVER;
CREATE EVENT SESSION [test_trace]
ON SERVER
ADD EVENT sqlserver.sql_statement_completed(
ACTION (package0.callstack, sqlserver.session_id, sqlserver.sql_text, sqlserver.tsql_stack)
WHERE (object_id = ' + cast(object_id('[dbo].[triggerAfterUpdate]') as varchar(10)) + ')
)
,
ADD EVENT sqlserver.sp_statement_completed(
ACTION (package0.callstack, sqlserver.session_id, sqlserver.sql_text, sqlserver.tsql_stack)
WHERE (object_id = ' + cast(object_id('[dbo].[triggerAfterUpdate]') as varchar(10)) + ')
)
ADD TARGET package0.asynchronous_file_target
(set filename = ''c:\temp\test_trace.xel'' , metadatafile = ''c:\temp\test_trace.xem'')
ALTER EVENT SESSION [test_trace] ON SERVER STATE = START
'
EXEC sp_executesql #DynSql
GO
EXEC dbo.Proc1
GO
ALTER EVENT SESSION [test_trace] ON SERVER STATE = STOP
The trace data can be read with
SELECT CONVERT (XML, event_data) AS data
FROM sys.fn_xe_file_target_read_file ('C:\Temp\test_trace*.xel', 'C:\Temp\test_trace*.xem', NULL, NULL)
The call stack part is
<action name="tsql_stack" package="sqlserver">
<value><frame level='1' handle='0x03000800E8EA0D0612E4EB00A59D00000000000000000000' line='6' offsetStart='228' offsetEnd='264'/>
<frame level='2' handle='0x03000800921155002C81E700A59D00000000000000000000' line='8' offsetStart='258' offsetEnd='398'/>
<frame level='3' handle='0x03000800CB3549012F81E700A59D00000100000000000000' line='5' offsetStart='90' offsetEnd='284'/>
<frame level='4' handle='0x03000800045A3D022F81E700A59D00000100000000000000' line='5' offsetStart='90' offsetEnd='120'/>
<frame level='5' handle='0x030008003D7E31033081E700A59D00000100000000000000' line='5' offsetStart='90' offsetEnd='120'/>
<frame level='6' handle='0x0300080076A225043081E700A59D00000100000000000000' line='5' offsetStart='90' offsetEnd='120'/>
<frame level='7' handle='0x010008002E775203603D9A0D000000000000000000000000' line='2' offsetStart='4' offsetEnd='-1'/></value>
<text />
</action>
Joining onto the DMVs
WITH CapturedResults AS
( SELECT data.value ( '(/event/#timestamp)[1]', 'DATETIME') AS [TIME],
data.value ( '(/event/data[#name=''cpu'']/value)[1]', 'INT') AS [CPU (ms)],
CONVERT (FLOAT, data.value ('(/event/data[#name=''duration'']/value)[1]', 'BIGINT')) / 1000000 AS [Duration (s)],
data.value ( '(/event/action[#name=''sql_text'']/value)[1]', 'VARCHAR(MAX)') AS [SQL STATEMENT],
CAST(data.value('(/event/action[#name="tsql_stack"]/value)[1]','varchar(MAX)') AS XML) AS [stack_xml]
FROM (SELECT CONVERT (XML, event_data) AS data
FROM sys.fn_xe_file_target_read_file ('C:\Temp\test_trace*.xel', 'C:\Temp\test_trace*.xem', NULL, NULL)
)
entries
)
,
StackData AS
( SELECT frame_xml.value('(./#level)', 'int') AS [frame_level],
frame_xml.value('(./#handle)', 'varchar(MAX)') AS [sql_handle],
frame_xml.value('(./#offsetStart)', 'int') AS [offset_start],
frame_xml.value('(./#offsetEnd)', 'int') AS [offset_end]
FROM CapturedResults CROSS APPLY stack_xml.nodes('//frame') N (frame_xml)
)
SELECT sd.frame_level,
object_name(st.objectid, st.dbid) AS ObjectName,
SUBSTRING(st.text, (sd.offset_start/2)+1, ((
CASE sd.offset_end
WHEN -1
THEN DATALENGTH(st.text)
ELSE sd.offset_end
END - sd.offset_start)/2) + 1) AS statement_text,
qp.query_plan,
qs2.creation_time,
qs2.last_execution_time,
qs2.execution_count
FROM StackData AS sd CROSS APPLY sys.dm_exec_sql_text(CONVERT(VARBINARY(MAX),sd.sql_handle,1)) AS st
LEFT OUTER JOIN sys.dm_exec_query_stats qs2
ON qs2.sql_handle = CONVERT(VARBINARY(MAX),sd.sql_handle,1) OUTER APPLY sys.dm_exec_query_plan(CONVERT(VARBINARY(MAX),qs2.plan_handle,1)) AS qp
gives results (showing the whole tsql call stack)
Besides using triggers that will be fired when a data change occurs, you can use SQL Server traces and analyse them in SQL Profiler, or a third party auditing tool that tracks executed code for DML changes.
Using ApexSQL Comply you can narrow down the auditing only the the specific database object (table in this case) and event type.
The reports show various useful info, including the SQL statement executed that initiated the change.
Disclaimer: I work for ApexSQL as a Support engineer
My Solution
I added a trigger on the table in question that logged information i narrowed down via timestamps from sys.dm_exec_sql_text AND sys.dm_exec_query_stats. This quickly nailed down what i was looking for. Turns out there were a few triggers i didn't know about that were updating data after a UPDATE.
SELECT
qStats.last_execution_time AS [ExecutedAt],
qTxt.[text] AS [Query], qTxt.number
FROM
sys.dm_exec_query_stats AS qStats
CROSS APPLY
sys.dm_exec_sql_text(qStats.sql_handle) AS qTxt
WHERE
qTxt.[dbid] = #DbId
AND qTxt.[text] like '%UPDATE%'
AND qStats.last_execution_time between #StartExecutionSearchTime and #EndExecutionSearchTime
ORDER BY
qStats.last_execution_time DESC
Please vote for this Microsoft Connect item, and have a look at this call stack workaround.
SQL Server 2008 introduced a new feature called Change Data Capture (CDC), rather than use triggers. Read more about it here.
How can i know which sql statement fired through trigger for select, insert, update and delete on table?
As Jonas says, Profiler is your best option (and only option for SELECT queries). For INSERT, UPDATE, DELETEs, the closest you can get without Profiler may be to look at the input buffer via DBCC INPUTBUFFER(##SPID). This will only work for ad-hoc language events, not RPC calls, and will only show you the first 256 characters of the SQL statement (depending on version, I believe). Some example code, (run as dbo):
CREATE TABLE TBL (a int, b varchar(50))
go
INSERT INTO TBL SELECT 1,'hello'
INSERT INTO TBL SELECT 2,'goodbye'
go
GRANT SELECT, UPDATE ON TBL TO guest
go
CREATE TABLE AUDIT ( audittime datetime default(getdate())
, targettable sysname
, loginname sysname
, spid int
, sqltext nvarchar(max))
go
CREATE TRIGGER TR_TBL ON TBL FOR INSERT, UPDATE, DELETE
AS BEGIN
CREATE TABLE #DBCC (EventType varchar(50), Parameters varchar(50), EventInfo nvarchar(max))
INSERT INTO #DBCC
EXEC ('DBCC INPUTBUFFER(##SPID)')
INSERT INTO AUDIT (targettable, loginname, spid, sqltext)
SELECT targettable = 'TBL'
, suser = suser_name()
, spid = ##SPID
, sqltext = EventInfo
FROM #DBCC
END
GO
/* Test the Audit Trigger (can be run as guest) */
UPDATE TBL SET a = 3 WHERE a = 2
First, there are no select dml triggers, only triggers that work on INSERT, UPDATE or DELETE
Secondly, you can't know which sql statement triggered the trigger (at least not in the trigger). However, you can use profiler to debug what's happening in the database. There's a decent explanation of this here.