Script to check changes on SQL Server Instance? - sql-server-2012
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
Related
How to return true=1 or false=0 when updating
ALTER PROCEDURE [dbo].[Proc_Erp_Branch_Update] #Id int, #Code varchar(20), #Name nvarchar(50), #IsActive bit=1, #UpdateBy varchar(50), #ImgId int AS BEGIN IF EXISTS (SELECT* FROM Branch WHERE Id = #id AND IsDelete =0) BEGIN UPDATE Branch SET Code = ISNULL(#Code,Code), Name = ISNULL( #Name,Name), IsActive = ISNULL( #IsActive,IsActive), UpdatedBy = ISNULL( #UpdateBy,UpdatedBy), ImageId = ISNULL( #ImgId,ImageId) output deleted.Id WHERE Id = #Id AND IsDelete = 0 END ELSE BEGIN SELECT 0 END END Why does the procedure always return 0 even though the update statement appears to be successful in the [dbo].[Proc_Erp_Branch_Update]? How do I resolve this? var query = "Proc_Erp_Position_Update"; var res = await con.QueryFirstOrDefaultAsync<int>(query, new { Id = model.Id, Code = model.Code, Name = model.Name, IsAtive = model.IsActive, UpdatBy = username, ImageId = imgid.HasValue? imgid : 0 }, commandType: System.Data.CommandType.StoredProcedure); return res; res always returns 0
res will always be 0 since if your procedure does find the row and update it, it will not select any value - and the default for int in dot net is 0. However, there is another problem in your procedure - there is a chance, though admittedly a small chance, that the relevant row will be changed or deleted between the select statement in the exists condition and the update statement that follows. Luckily, there's an easy fix for that - simply remove the if. Executing a stored procedure will return, by default, the number of rows effected, so you can simply use con.ExecuteAsync to get that number. SQL: ALTER PROCEDURE [dbo].[Proc_Erp_Branch_Update] #Id int, #Code varchar(20), #Name nvarchar(50), #IsActive bit=1, #UpdateBy varchar(50), #ImgId int AS BEGIN UPDATE Branch SET Code = ISNULL(#Code,Code), Name = ISNULL( #Name,Name), IsActive = ISNULL( #IsActive,IsActive), UpdatedBy = ISNULL( #UpdateBy,UpdatedBy), ImageId = ISNULL( #ImgId,ImageId) WHERE Id = #Id AND IsDelete = 0 END C#: var query = "Proc_Erp_Position_Update"; var res = await con.ExecuteAsync(query, new { Id = model.Id, Code = model.Code, Name = model.Name, IsAtive = model.IsActive, UpdatBy = username, ImageId = imgid.HasValue? imgid : 0 }, commandType: System.Data.CommandType.StoredProcedure); return res; Note: I'm assuming you're using c# and Dapper based on the code you've shown in your question.
SQL For Json path - auto inserted '\' if parameter content '/'
Declare #ContractCode nvarchar(100) = N'02/HĐLĐ-NGG'; Declare #DateStartWork date = '2020-06-01'; Declare #DeptID int = 2; Declare #PositionID int = 4; Declare #HRDecisionID int = 614 Select #ContractCode as ContractCode, #DateStartWork as DateStartWork, #DeptID as DeptID, #HRDecisionID as HRDecisionID For Json Path Then the Result = [{"ContractCode":"02\/HĐLĐ-NGG","DateStartWork":"2020-06-01","DeptID":2,"HRDecisionID":614}] Instead of right value like: [{"ContractCode":"02/HĐLĐ-NGG","DateStartWork":"2020-06-01","DeptID":2,"HRDecisionID":614}] So please help me to answer Why it automatically inserted \ before '/' into ContractCode.
Issue with SQL Stored Procedure Note Returning Values when passing in NULLS
I have a stored procedure that I created and sometimes there are parameters that pass in a NULL value. I set up the stored procedure to take this into account, but every time I pass in a NULL value it doesn't return anything even if some of the other parameters are passed in. Can you please help? Here is my stored procedure. I'm not sure if it's the join that isn't correct or what. Thanks. #ProductID int = NULL, #CollectionID int = NULL, #ApplicationID int = NULL, #StyleID int = NULL AS SELECT dbo.CrossoverDesignTable.ColorNum, dbo.CrossoverDesignTable.CrossoverID, dbo.ImagesWebsite.Description, dbo.DesignNameTable.DesignDescription + ' ' + dbo.CrossoverDesignTable.ColorNum AS DesignColor, dbo.CollectionTable.CollectionDescription FROM dbo.CrossoverDesignTable INNER JOIN dbo.DesignNameTable ON dbo.CrossoverDesignTable.DesignNameID = dbo.DesignNameTable.DesignNameID INNER JOIN dbo.ImagesWebsite ON dbo.CrossoverDesignTable.ProductImageID = dbo.ImagesWebsite.ProductImageID INNER JOIN dbo.CollectionTable ON dbo.CrossoverDesignTable.CollectionID = dbo.CollectionTable.CollectionID WHERE (dbo.CrossoverDesignTable.ProductID = #ProductID OR #ProductID IS NULL) AND (dbo.CrossoverDesignTable.CollectionID = #CollectionID OR dbo.CrossoverDesignTable.CollectionID IS NULL) AND (dbo.CrossoverDesignTable.ApplicationID = #ApplicationID OR #ApplicationID IS NULL) AND (dbo.CrossoverDesignTable.ShowOnWeb = 'Yes') AND (dbo.CrossoverDesignTable.StyleID = #StyleID OR #StyleID IS NULL) RETURN
What I do in these scenarios is change the default for #ProductId to -1 DO NOT SEND ANY VALUE FOR THE PARAMETER, IF YOU SEND A NULL THEN IT WILL USE THAT VALUE. DEFAULT VALUES ARE ONLY USED WHEN THE PARAMETER IS NOT SENT. #ProductID int = -1, #CollectionID int = NULL, #ApplicationID int = NULL, #StyleID int = NULL WHERE (#ProductID = -1 OR dbo.CrossoverDesignTable.ProductID = #ProductID) AND .....
Assuming -1 is a value you don't have, I recommend you use this: ISNULL(dbo.CrossoverDesignTable.ProductID, -1) = ISNULL(#ProductID, -1) instead of: dbo.CrossoverDesignTable.ProductID = #ProductID OR #ProductID IS NULL and do the other comparisons in the same way. If you want to keep your approach, you should do it this way, I think. ( (#ProductID is null) AND (dbo.CrossoverDesignTable.ProductID is null) ) OR (dbo.CrossoverDesignTable.ProductID = #ProductID)
SQL SubString from Special Kind of ntext column
I have a DB table with the column of type ntext filled with the values like A<1,?,'attrdisplay'=A<1,?,'1361147_2'='2','1361147_3'='3','1361147_4'='4','1361147_5'='5','1361147_6'='6','1361147_7'='1'>,'ClassificationInheritance'=A<1,?,'DisabledIds'=A<1,?>>,'Confidential'=0,'CreationNotification'=A<1,?,'mail'='Ärende har skapats','recipients'=A<1,?,1414822=-1,1414823=-1,1414824=-1,1414825=-1,1414826=-1,1414827=-1,1415811=-1>>,'IsSubBinder'=1,'name'=A<1,?,'fullname'='Ärendemall5','mlNames'=A<1,?,'sv'='Ärendemall5'>,'name'='Ärendemall5','nameFormat'=':name:','ok'=true,'refnr'='SJCM-2013-00014'>,'showDocumentsFirst'=true,'WorkItem'=A<1,?,'id'=-1,'Name'=''>> I have to extract only this value SJCM-2013-00014 for each such row in the table. Any idea how I can do that ? Thank you in advance!
I guess what you really want is to search for and extract the refnr? If you have the possibility to add CLR-functions to the server, in order to be able to use regular expressions, this is by far the easiest way: CREATE TABLE #test (data ntext) INSERT INTO #test VALUES('A<1,?,''attrdisplay''=A<1,?,''1361147_2''=''2'',''1361147_3''=''3'',''1361147_4''=''4'',''1361147_5''=''5'',''1361147_6''=''6'',''1361147_7''=''1''>,''ClassificationInheritance''=A<1,?,''DisabledIds''=A<1,?>>,''Confidential''=0,''CreationNotification''=A<1,?,''mail''=''Ärende har skapats'',''recipients''=A<1,?,1414822=-1,1414823=-1,1414824=-1,1414825=-1,1414826=-1,1414827=-1,1415811=-1>>,''IsSubBinder''=1,''name''=A<1,?,''fullname''=''Ärendemall5'',''mlNames''=A<1,?,''sv''=''Ärendemall5''>,''name''=''Ärendemall5'',''nameFormat''='':name:'',''ok''=true,''refnr''=''SJCM-2013-00014''>,''showDocumentsFirst''=true,''WorkItem''=A<1,?,''id''=-1,''Name''=''''>>') SELECT *, [dbo].[RegexMatchGroupClr]( CAST(data as nvarchar(max)), 'refnr''=''(?<refnr>[^'']+)''', 'refnr' ) FROM #test This is done using regex in a CLR-function that looks like this in C#: [SqlFunction] public static SqlChars RegexMatchGroupClr(SqlChars input, string pattern, string groupName) { if (input.IsNull) return input; string inputString = new string(input.Value); rx.Match match = rx.Regex.Match(inputString, pattern); if (match.Success && match.Groups[groupName].Success) { string resultString = match.Groups[groupName].Value; SqlChars result = new SqlChars(resultString.ToCharArray()); return result; } return null; } If you don't have the possibility to add CLR-functions, it's still doable, but way more cumbersome: CREATE TABLE #test (data ntext) INSERT INTO #test VALUES('A<1,?,''attrdisplay''=A<1,?,''1361147_2''=''2'',''1361147_3''=''3'',''1361147_4''=''4'',''1361147_5''=''5'',''1361147_6''=''6'',''1361147_7''=''1''>,''ClassificationInheritance''=A<1,?,''DisabledIds''=A<1,?>>,''Confidential''=0,''CreationNotification''=A<1,?,''mail''=''Ärende har skapats'',''recipients''=A<1,?,1414822=-1,1414823=-1,1414824=-1,1414825=-1,1414826=-1,1414827=-1,1415811=-1>>,''IsSubBinder''=1,''name''=A<1,?,''fullname''=''Ärendemall5'',''mlNames''=A<1,?,''sv''=''Ärendemall5''>,''name''=''Ärendemall5'',''nameFormat''='':name:'',''ok''=true,''refnr''=''SJCM-2013-00014''>,''showDocumentsFirst''=true,''WorkItem''=A<1,?,''id''=-1,''Name''=''''>>') DECLARE #SearchFor varchar(10) = 'refnr''='''; DECLARE #EndsWith varchar(10) = ''''; WITH converted AS ( SELECT CAST(data as nvarchar(max)) as data FROM #test ), startPos AS ( SELECT * ,CHARINDEX(#SearchFor, data) + LEN(#SearchFor) as startPos FROM converted ), endPos AS ( SELECT * ,CHARINDEX(#EndsWith, data, startPos) as endPos FROM startPos ) SELECT * ,SUBSTRING(data, startPos, endPos - startPos) as refnr FROM endPos And if you can't use Common Table Expressions, it gets even more unreadable: CREATE TABLE #test (data ntext) INSERT INTO #test VALUES('A<1,?,''attrdisplay''=A<1,?,''1361147_2''=''2'',''1361147_3''=''3'',''1361147_4''=''4'',''1361147_5''=''5'',''1361147_6''=''6'',''1361147_7''=''1''>,''ClassificationInheritance''=A<1,?,''DisabledIds''=A<1,?>>,''Confidential''=0,''CreationNotification''=A<1,?,''mail''=''Ärende har skapats'',''recipients''=A<1,?,1414822=-1,1414823=-1,1414824=-1,1414825=-1,1414826=-1,1414827=-1,1415811=-1>>,''IsSubBinder''=1,''name''=A<1,?,''fullname''=''Ärendemall5'',''mlNames''=A<1,?,''sv''=''Ärendemall5''>,''name''=''Ärendemall5'',''nameFormat''='':name:'',''ok''=true,''refnr''=''SJCM-2013-00014''>,''showDocumentsFirst''=true,''WorkItem''=A<1,?,''id''=-1,''Name''=''''>>') DECLARE #SearchFor varchar(10) = 'refnr''='''; DECLARE #EndsWith varchar(10) = ''''; SELECT * ,SUBSTRING(data, (CHARINDEX(#SearchFor, data) + LEN(#SearchFor)), (CHARINDEX(#EndsWith, data, (CHARINDEX(#SearchFor, data) + LEN(#SearchFor)))) - (CHARINDEX(#SearchFor, data) + LEN(#SearchFor))) as refnr FROM #test
entity framework 4 POCO's stored procedure error - "The FunctionImport could not be found in the container"
Entity Framework with POCO Entities generated by T4 template. Added Function Import named it "procFindNumber" specified complex collection named it "NumberResult". Here's what got generated in Context.cs file: public ObjectResult<NumberResult> procFindNumber(string lookupvalue) { ObjectParameter lookupvalueParameter; if (lookupvalue != null) { lookupvalueParameter = new ObjectParameter("lookupvalue", lookupvalue); } else { lookupvalueParameter = new ObjectParameter("lookupvalue", typeof(string)); } return base.ExecuteFunction<NumberResult>("procFindNumber", lookupvalueParameter); } Here's the stored procedure: ALTER PROCEDURE [dbo].[procFindNumber] #lookupvalue varchar(255) AS BEGIN SET NOCOUNT ON; DECLARE #sql nvarchar(MAX); IF #lookupvalue IS NOT NULL AND #lookupvalue <> '' BEGIN SELECT #sql = 'SELECT dbo.HBM_CLIENT.CLIENT_CODE, dbo.HBM_MATTER.MATTER_NAME, dbo.HBM_MATTER.CLIENT_MAT_NAME FROM dbo.HBM_MATTER INNER JOIN dbo.HBM_CLIENT ON dbo.HBM_MATTER.CLIENT_CODE = dbo.HBM_CLIENT.CLIENT_CODE LEFT OUTER JOIN dbo.HBL_CLNT_CAT ON dbo.HBM_CLIENT.CLNT_CAT_CODE = dbo.HBL_CLNT_CAT.CLNT_CAT_CODE LEFT OUTER JOIN dbo.HBL_CLNT_TYPE ON dbo.HBM_CLIENT.CLNT_TYPE_CODE = dbo.HBL_CLNT_TYPE.CLNT_TYPE_CODE WHERE (LTRIM(RTRIM(dbo.HBM_MATTER.CLIENT_CODE)) <> '''')' SELECT #sql = #sql + ' AND (dbo.HBM_MATTER.MATTER_NAME like ''%' + #lookupvalue + '%'')' SELECT #sql = #sql + ' OR (dbo.HBM_MATTER.CLIENT_MAT_NAME like ''%' + #lookupvalue + '%'')' SELECT #sql = #sql + ' ORDER BY dbo.HBM_MATTER.MATTER_NAME' -- Execute the SQL query EXEC sp_executesql #sql END END In my WCF service I try to execute the stored procedure: [WebGet(UriTemplate = "number/{value}/?format={format}")] public IEnumerable<NumberResult> GetNumber(string value, string format) { if (string.Equals("json", format, StringComparison.OrdinalIgnoreCase)) { WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json; } using (var ctx = new MyEntities()) { ctx.ContextOptions.ProxyCreationEnabled = false; var results = ctx.procFindNumber(value); return results.ToList(); } } Error message says "The FunctionImport ... could not be found in the container ..." What am I doing wrong?
You need to qualify the function import with the container name. E.g change this: return base.ExecuteFunction<NumberResult>("procFindNumber", lookupvalueParameter); to this: return base.ExecuteFunction<NumberResult>("EntityContainerName.procFindNumber", lookupvalueParameter); The entity container name is found on your EDMX - right click anywhere and do "Properties".
I had a similar problem with EF 4.1... and found that I would get this issue when the code was running in Release mode. In Debug mode, it works fine.