Is there any SQL
script to check the changes on SQL Server
instance made by other DB admin and i get email alert of that changes. if yes, then please provide me the script and all the steps for applying it. I am using SQL Server 2012.
One solution is to use a DDL trigger
to catch all schema changes (procedures, functions, table definition etc.). This can work for all non-encrypted objects and of course, other admins must not disable it.
More details about how to write such a trigger and persist changes can be found here.
[Later edit]
I remembered I have created such an audit on the development environment and I can provide a custom version based on the indicated article. Besides usual information, I have also included a "distance" between old object text and new object text to have a basic idea about change magnitude.
1) Table definition:
IF OBJECT_ID('dbo.DDLEvents', 'U') IS NULL
BEGIN
CREATE TABLE dbo.DDLEvents
(
EventId INT IDENTITY(1, 1) NOT NULL,
EventDate DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
EventType NVARCHAR(64),
EventXML XML,
DatabaseName NVARCHAR(255),
SchemaName NVARCHAR(255),
ObjectName NVARCHAR(255),
HostName VARCHAR(128),
IPAddress VARCHAR(32),
ProgramName NVARCHAR(255),
LoginName NVARCHAR(255),
ObjectDefinition NVARCHAR(MAX),
LastObjDefinition NVARCHAR(MAX),
Diff INT -- edit distance between last and current object version (gives an idea of how much was changed in the object)
);
create index IDX_DDLEvents_Object ON DDLEvents (SchemaName, ObjectName)
END
go
2) Initial text for existing objects:
IF NOT EXISTS(SELECT * FROM dbo.DDLEvents)
BEGIN
INSERT INTO dbo.DDLEvents
(EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
SELECT 'CREATE_PROCEDURE', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
FROM sys.procedures;
INSERT INTO dbo.DDLEvents
(EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
SELECT 'CREATE_VIEW', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
FROM sys.views;
INSERT INTO dbo.DDLEvents
(EventType, DatabaseName, SchemaName, ObjectName, LoginName, ObjectDefinition)
SELECT 'CREATE_FUNCTION', DB_NAME(), OBJECT_SCHEMA_NAME([object_id]), OBJECT_NAME([object_id]), 'SYSTEM', OBJECT_DEFINITION([object_id])
FROM sys.objects
-- scalar, inline table-valued, table-valued
WHERE type IN ('FN', 'IF', 'TF')
END
go
3) Distance function (CLR):
[Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = false)]
public static int Levenshtein(SqlString S1, SqlString S2)
{
if (S1.IsNull)
S1 = new SqlString("");
if (S2.IsNull)
S2 = new SqlString("");
int maxLen = 4096;
// keeping only the first part of the string (performance reasons)
String SC1 = S1.Value.ToUpper();
String SC2 = S2.Value.ToUpper();
if (SC1.Length > maxLen)
SC1 = SC1.Remove(maxLen);
if (SC2.Length > maxLen)
SC2 = SC2.Remove(maxLen);
int n = SC1.Length;
int m = SC2.Length;
short[,] d = new short[n + 1, m + 1];
int cost = 0;
if (n + m == 0)
{
return 0;
}
else if (n == 0)
{
return 0;
}
else if (m == 0)
{
return 0;
}
for (short i = 0; i <= n; i++)
d[i, 0] = i;
for (short j = 0; j <= m; j++)
d[0, j] = j;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (SC1[i - 1] == SC2[j - 1])
cost = 0;
else
cost = 1;
d[i, j] = (short) System.Math.Min(System.Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
}
}
// double percentage = System.Math.Round((1.0 - ((double)d[n, m] / (double)System.Math.Max(n, m))) * 100.0, 2);
// return percentage;
return d[n, m];
}
4) The DDL trigger definition
if not exists (select * from sys.triggers where name = 'DDL_Audit_Trigger')
exec ('create trigger DDL_Audit_Trigger ON DATABASE FOR CREATE_PROCEDURE AS BEGIN PRINT 1; END')
GO
ALTER TRIGGER [DDL_Audit_Trigger]
ON DATABASE
FOR
CREATE_ASSEMBLY, ALTER_ASSEMBLY, DROP_ASSEMBLY,
CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE,
CREATE_FUNCTION, ALTER_FUNCTION, DROP_FUNCTION,
CREATE_INDEX, ALTER_INDEX, DROP_INDEX,
CREATE_VIEW, ALTER_VIEW, DROP_VIEW,
CREATE_ROLE, ALTER_ROLE, DROP_ROLE,
CREATE_SCHEMA, ALTER_SCHEMA, DROP_SCHEMA,
CREATE_TABLE, ALTER_TABLE, DROP_TABLE,
CREATE_TYPE, DROP_TYPE,
CREATE_USER, ALTER_USER, DROP_USER,
CREATE_TRIGGER, ALTER_TRIGGER, DROP_TRIGGER,
RENAME
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DECLARE @EventData XML = EVENTDATA();
DECLARE @ip VARCHAR(32) = ( SELECT client_net_address FROM sys.dm_exec_connections WHERE session_id = @@SPID);
DECLARE @ObjectSchema NVARCHAR(255) = @EventData.value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(255)')
DECLARE @ObjectName NVARCHAR(255) = @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
-- DECLARE @ObjectFullName NVARCHAR(255) = @ObjectSchema + '.' + @ObjectName
DECLARE @CommandText NVARCHAR(MAX) = @EventData.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'NVARCHAR(MAX)')
DECLARE @LastObjectChange DATETIME = (SELECT TOP 1 EventDate FROM dbo.DDLEvents where SchemaName = @ObjectSchema and ObjectName = @ObjectName ORDER BY EventDate DESC)
DECLARE @LastObjectDefinition NVARCHAR(MAX) = (SELECT TOP 1 ObjectDefinition FROM dbo.DDLEvents where SchemaName = @ObjectSchema and ObjectName = @ObjectName and EventDate = @LastObjectChange ORDER BY EventDate DESC)
INSERT INTO dbo.DDLEvents
(EventType,
EventXML, DatabaseName,
SchemaName, ObjectName,
HostName, IPAddress, ProgramName, LoginName,
ObjectDefinition, LastObjDefinition, Diff
)
SELECT @EventData.value('(/EVENT_INSTANCE/EventType)[1]', 'NVARCHAR(100)'),
@EventData, DB_NAME(),
@ObjectSchema, @ObjectName,
HOST_NAME(), @ip, PROGRAM_NAME(), SUSER_SNAME(),
@CommandText, @LastObjectDefinition, dbo.Levenshtein(@CommandText, @LastObjectDefinition);
END TRY
BEGIN CATCH
INSERT INTO dbo.DDLEventsLog (Error)
SELECT ERROR_MESSAGE()
END CATCH
END
GO