search in sql with 2 input parameter - sql

i have 2 input parameter and i want search with These
CREATE TABLE dbo.tbl_answer
(
an_del INT,
u_username NVARCHAR(50),
u_name NVARCHAR(50) null
)
INSERT dbo.tbl_answer
VALUES(1, 'mohammad', null), (1, 'A13A6C533AF77160FBF2862953FA4530', 'GCV'), (1, 'C', 'GG'), (0, 'AB', 'BB'), (1, 'AC', 'K')
GO
CREATE PROC dbo.SearchAnswers
#Username nvarchar(20),
#Name nvarchar(20)
AS
SELECT *
FROM dbo.tbl_answer
WHERE an_del = 1 AND u_username LIKE ISNULL('%' + #Username + '%', u_username)
and u_name LIKE ISNULL('%' + #Name + '%', u_name)
and i run this command EXEC dbo.SearchAnswers 'moha', null but return any data
look at this

Try this :
CREATE PROC dbo.Answers
#Username nvarchar(20),
#Name nvarchar(20)
AS
declare #Name2 nvarchar(20)
set #Name2 = ISNULL(#Name, '00')
SELECT *
FROM dbo.tbl_answer
WHERE an_del = 1 AND ( u_username LIKE ISNULL('%' + #Username + '%', u_username)
AND
ISNULL(u_name,'00') LIKE '%' + #Name2 + '%' )

Your problem is that you aren't allowing for u_name to be null in your table. Which it is, on the only record with a u_username containing "moha"
CREATE PROCEDURE dbo.SearchAnswers
#Username nvarchar(20),
#Name nvarchar(20)
AS
SELECT *
FROM dbo.tbl_answer
WHERE an_del = 1
AND u_username LIKE ISNULL('%' + #Username + '%', u_username)
AND ISNULL(u_name,'') LIKE ISNULL('%' + #Name + '%', u_name)
You could also have the third conditional explicity test for NULL.
AND ( (u_name is null) or (u_name LIKE ISNULL('%' + #Name + '%', u_name) )

Here you are searching for 2 fields username and name and input parameter is null. Like will not work for null so you have to check for null independently or put some supplementary for that...
CREATE PROC dbo.SearchAnswers
#Username nvarchar(20),
#Name nvarchar(20)
AS
declare #Name2 nvarchar(20)
set #Name2 = ISNULL(#Name, '00')
SELECT *
FROM dbo.tbl_answer
WHERE an_del = 1 AND ( u_username LIKE ISNULL('%' + #Username + '%', u_username)
AND
ISNULL(u_name,'00') LIKE '%' + #Name2 + '%' )
SQL Fiddle

Related

How to use dynamic filter in sql procedure?

I want use Dynamic filter in sql Procedure
,like this
Select * from Table Where #Filter
Can I Write Like That Or Was Diffrent Ways to Use
I must Use this Syntax because I Want Remove Select in Application and Use Procedure.
CREATE PROCEDURE SP_DynamicFilter
(
-- Optional Filters for Dynamic Search
#COLUMN1 INT = NULL,
#COLUMN2 NVARCHAR(50) = NULL,
#COLUMN3 NVARCHAR(50) = NULL,
#COLUMN4 NVARCHAR(50) = NULL
)
AS
BEGIN
SELECT *
FROM TableName
WHERE
(#COLUMN1 IS NULL OR Column1 = #COLUMN1)
AND (#COLUMN2 IS NULL OR Column2 LIKE '%' + #COLUMN2 + '%')
AND (#COLUMN3 IS NULL OR Column3 LIKE '%' + #COLUMN3 + '%')
AND (#COLUMN4 IS NULL OR Column4 LIKE '%' + #COLUMN4 + '%')
END
CREATE PROCEDURE spProcedurName
#Filter datatype
AS
BEGIN
SELECT *
FROM Table
WHERE columnName= #Filter
END
You can paramater like that.
#Query='
Select * from table' + #Filter
exec #Query

How can I search for a single inserted item without the other's?

create procedure SearchUsers(#User_name varchar(50),
#User_cellPhone varchar(10 ) null,
#Is_Active bit null)
declare #boolean varchar(50) = #Is_active
set #boolean = CAST(#boolean as varchar)
select [User_Name],User_CellNumber,User_FirstName
from [User]
where (
[User_Name] like '%'+ #User_name + '%'
and User_CellNumber like '%' + #User_cellPhone + '%'
and User_IsActive like '%' + #boolean + '%'
)
order by [User_Name]
Please assist me on how I can search for a user by only using #User_name or all the above mentioned.
As others mentioned, there is NO need to convert a bit to a varchar. Since I don't have all the facts, I left all your wildcards, but you should only use the wildcards you need so they don't slow down your query. Try this out:
CREATE PROCEDURE SearchUsers(#User_name VARCHAR(50),
#User_cellPhone VARCHAR(10) NULL,
#Is_Active BIT NULL)
AS
SELECT [User_Name],User_CellNumber,User_FirstName
FROM [User]
WHERE (
[User_Name] LIKE '%' + #User_name + '%'
AND #User_cellPhone IS NULL
AND #Is_Active IS NULL
OR (
[User_Name] LIKE '%' + #User_name + '%'
AND User_CellNumber LIKE '%' + #User_cellPhone + '%'
AND User_IsActive = #Is_Active
)
GO

Writing a Stored procedure to search for matching values in columns with optional Input parameters

Requirement: To write stored Procedure(s) such that the values passed in stored procedures are matched against the values in columns in the table and then arranged with highest to lowest matching number of attributes.Then are inserted in a dynamically created temporary table inside the stored procedure.
Problem:
I have say 15-20 attributes that are matched against to confirm the suggestions made in response to record search. Basically, There is a table that stores Patients information and multiple parameters may be passed into the stored procedure to search through so that a Temporary table is created that suggests records in decreasing order of matching attributes.
To frame the basic structure, I tried with 3 attributes and respective stored procedures to match them which in turn are collectively called from a calling procedure based on the input parameter list which in turn creates the required temporary table.
Here is the SQL code(Just to give the gist of what I have tried so far):
But as a matter of fact it is, I realize this is way too naive to be used in a real time application which may require 80-90% of accuracy.So, what exactly can replace this technique for better efficiency?
Create Procedure NameMatch
(
#Name nvarchar(20),
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name = #Name)
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name =' + #Name
set #temp = 0.1*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
Create Procedure AgeMatch
(
#Name nvarchar(20),
#Age int,
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name =#Name and Age = + #Age)
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name = ' + #Name + ' and Age = '+ #Age
set #temp = 0.2*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
Create Procedure Nationality
(
#Name nvarchar(20),
#Age int,
#Nation nvarchar(10),
#PercentContribution nvarchar(4) OUT, #PatientName nvarchar(20) out
)
As
declare #temp int
DECLARE #Query nvarchar(500)
if Exists(select Name from dbo.PatientDetails where Name = #Name and Age = #Age and Nationality = #Nation )
Begin
set #PatientName = #Name
set #query = 'select * from dbo.PatientDetails where Name = ' + #Name + ' and Age = '+ #Age + ' and Nationality = ' + #Nation
set #temp = 0.3*100
set #PercentContribution = #temp + '%'
Execute(#query)
Return
End
create procedure CallingProcedure
(
#Name nvarchar(20),
#Age int = null,
#Nation nvarchar(10)= null
)
As
declare #PercentMatch nvarchar(4)
Begin
create table #results(PatientName nvarchar(30), PercentMatch nvarchar(4))
if(#Nation IS NOT NULL)
Insert into #results exec Nationality #Nation, #Name output, #PercentMatch output
else if(#Age is not Null)
Insert into #results exec AgeMatch #Age, #Name output, #PercentMatch output
else
Insert into #results exec NameMatch #Name, #Name output, #PercentMatch output
End
Setting aside nuances of stored procedure syntax, given parameters 1-n that if not null should match columns 1-n and the results sorted by highest number of matches first, a dynamic query is not needed - plain SQL can do it.
select *
from patient
where #param1 is null or column1 = #param1
or #param2 is null or column2 = #param2
...
or #paramN is null or columnN = #paramN
order by if(column1 = #param1, 1, 0)
+ if(column2 = #param2, 1, 0)
...
+ if(columnN = #paramN, 1, 0) desc

change conditions when parameter is null

I have a stored procedure like this
CREATE PROCEDURE Table_Search
#param1 VARCHAR(100) = NULL,
#param2 VARCHAR(100) = NULL,
#param3 VARCHAR(100) = NULL
AS
SELECT * FROM Table1
WHERE
column1 LIKE '%' + #param1 + '%'
AND
column2
IN
(
SELECT * FROM Table2
WHERE
CONTAINS(column3,#param2)
AND
column4
IN
(
SELECT * FROM
fn_SplitWords(#param3,',')
)
)
what i want to happen is when #param1 IS NOT NULL #param2 IS NULL AND #param3 IS NULL, the stored procedure will be like:
CREATE PROCEDURE Table_Search
#param1 VARCHAR(100) = NULL,
#param2 VARCHAR(100) = NULL,
#param3 VARCHAR(100) = NULL
AS
SELECT * FROM Table1
WHERE
column1 LIKE '%' + #param1 + '%'
and when #param2 IS NOT NULL AND #param3 IS NULL, the stored procedure will be like this:
CREATE PROCEDURE Table_Search
#param1 VARCHAR(100) = NULL,
#param2 VARCHAR(100) = NULL,
#param3 VARCHAR(100) = NULL
AS
SELECT * FROM Table1
WHERE
column1 LIKE '%' + #param1 + '%'
AND
column2
IN
(
SELECT * FROM Table2
WHERE
CONTAINS(column3,#param2)
)
I already visited
WHERE IS NULL, IS NOT NULL or NO WHERE clause depending on SQL Server parameter value
and Stored procedure to handle null parameter
but I believe that this is a different case.
What might be the possible approach for this?
You can do something like this:
SELECT * FROM Table1
WHERE
column1 LIKE '%' + ISNULL(#param1, column1) + '%' AND
column2 LIKE '%' + ISNULL(#param2, column2) + '%' AND
...
This filters on every field if the parameter is set.
You can dynamically build your sql statement and then executesql proc to execute your query something like this...
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N'SELECT * FROM Table1
WHERE 1 = 1 '
IF (#param1 IS NOT NULL)
BEGIN
SET #Sql = #Sql + N' AND column1 LIKE ''%#param1%'''
END
IF (#param2 IS NOT NULL)
BEGIN
SET #Sql = #Sql + N' AND column2 IN (
SELECT * FROM Table2
WHERE
CONTAINS(column3,#param2)
)'
END
IF (#param3 IS NOT NULL)
BEGIN
SET #Sql = #Sql + N' AND column4
IN
(
SELECT * FROM
fn_SplitWords(#param3,'','')
)
'
END
EXECUTE sp_executesql #Sql
, N'#param1 VARCHAR(100),#param2 VARCHAR(100),#param3 VARCHAR(100)'
, #param1, #param2, #param3

SQL Server Update Trigger, Get Only modified fields

I am aware of COLUMNS_UPDATED, well I need some quick shortcut (if anyone has made, I am already making one, but if anyone can save my time, I will appriciate it)
I need basicaly an XML of only updated column values, I need this for replication purpose.
SELECT * FROM inserted gives me each column, but I need only updated ones.
something like following...
CREATE TRIGGER DBCustomers_Insert
ON DBCustomers
AFTER UPDATE
AS
BEGIN
DECLARE #sql as NVARCHAR(1024);
SET #sql = 'SELECT ';
I NEED HELP FOR FOLLOWING LINE ...., I can manually write every column, but I need
an automated routin which can work regardless of column specification
for each column, if its modified append $sql = ',' + columnname...
SET #sql = $sql + ' FROM inserted FOR XML RAW';
DECLARE #x as XML;
SET #x = CAST(EXEC(#sql) AS XML);
.. use #x
END
I've another completely different solution that doesn't use COLUMNS_UPDATED at all, nor does it rely on building dynamic SQL at runtime. (You might want to use dynamic SQL at design time but thats another story.)
Basically you start with the inserted and deleted tables, unpivot each of them so you are just left with the unique key, field value and field name columns for each. Then you join the two and filter for anything that's changed.
Here is a full working example, including some test calls to show what is logged.
-- -------------------- Setup tables and some initial data --------------------
CREATE TABLE dbo.Sample_Table (ContactID int, Forename varchar(100), Surname varchar(100), Extn varchar(16), Email varchar(100), Age int );
INSERT INTO Sample_Table VALUES (1,'Bob','Smith','2295','bs#example.com',24);
INSERT INTO Sample_Table VALUES (2,'Alice','Brown','2255','ab#example.com',32);
INSERT INTO Sample_Table VALUES (3,'Reg','Jones','2280','rj#example.com',19);
INSERT INTO Sample_Table VALUES (4,'Mary','Doe','2216','md#example.com',28);
INSERT INTO Sample_Table VALUES (5,'Peter','Nash','2214','pn#example.com',25);
CREATE TABLE dbo.Sample_Table_Changes (ContactID int, FieldName sysname, FieldValueWas sql_variant, FieldValueIs sql_variant, modified datetime default (GETDATE()));
GO
-- -------------------- Create trigger --------------------
CREATE TRIGGER TriggerName ON dbo.Sample_Table FOR DELETE, INSERT, UPDATE AS
BEGIN
SET NOCOUNT ON;
--Unpivot deleted
WITH deleted_unpvt AS (
SELECT ContactID, FieldName, FieldValue
FROM
(SELECT ContactID
, cast(Forename as sql_variant) Forename
, cast(Surname as sql_variant) Surname
, cast(Extn as sql_variant) Extn
, cast(Email as sql_variant) Email
, cast(Age as sql_variant) Age
FROM deleted) p
UNPIVOT
(FieldValue FOR FieldName IN
(Forename, Surname, Extn, Email, Age)
) AS deleted_unpvt
),
--Unpivot inserted
inserted_unpvt AS (
SELECT ContactID, FieldName, FieldValue
FROM
(SELECT ContactID
, cast(Forename as sql_variant) Forename
, cast(Surname as sql_variant) Surname
, cast(Extn as sql_variant) Extn
, cast(Email as sql_variant) Email
, cast(Age as sql_variant) Age
FROM inserted) p
UNPIVOT
(FieldValue FOR FieldName IN
(Forename, Surname, Extn, Email, Age)
) AS inserted_unpvt
)
--Join them together and show what's changed
INSERT INTO Sample_Table_Changes (ContactID, FieldName, FieldValueWas, FieldValueIs)
SELECT Coalesce (D.ContactID, I.ContactID) ContactID
, Coalesce (D.FieldName, I.FieldName) FieldName
, D.FieldValue as FieldValueWas
, I.FieldValue AS FieldValueIs
FROM
deleted_unpvt d
FULL OUTER JOIN
inserted_unpvt i
on D.ContactID = I.ContactID
AND D.FieldName = I.FieldName
WHERE
D.FieldValue <> I.FieldValue --Changes
OR (D.FieldValue IS NOT NULL AND I.FieldValue IS NULL) -- Deletions
OR (D.FieldValue IS NULL AND I.FieldValue IS NOT NULL) -- Insertions
END
GO
-- -------------------- Try some changes --------------------
UPDATE Sample_Table SET age = age+1;
UPDATE Sample_Table SET Extn = '5'+Extn where Extn Like '221_';
DELETE FROM Sample_Table WHERE ContactID = 3;
INSERT INTO Sample_Table VALUES (6,'Stephen','Turner','2299','st#example.com',25);
UPDATE Sample_Table SET ContactID = 7 where ContactID = 4; --this will be shown as a delete and an insert
-- -------------------- See the results --------------------
SELECT *, SQL_VARIANT_PROPERTY(FieldValueWas, 'BaseType') FieldBaseType, SQL_VARIANT_PROPERTY(FieldValueWas, 'MaxLength') FieldMaxLength from Sample_Table_Changes;
-- -------------------- Cleanup --------------------
DROP TABLE dbo.Sample_Table; DROP TABLE dbo.Sample_Table_Changes;
So no messing around with bigint bitfields and arth overflow problems. If you know the columns you want to compare at design time then you don't need any dynamic SQL.
On the downside the output is in a different format and all the field values are converted to sql_variant, the first could be fixed by pivoting the output again, and the second could be fixed by recasting back to the required types based on your knowledge of the design of the table, but both of these would require some complex dynamic sql. Both of these might not be an issue in your XML output. This question does something similar to getting the output back in the same format.
Edit: Reviewing the comments below, if you have a natural primary key that could change then you can still use this method. You just need to add a column that is populated by default with a GUID using the NEWID() function. You then use this column in place of the primary key.
You may want to add an index to this field, but as the deleted and inserted tables in a trigger are in memory it might not get used and may have a negative effect on performance.
Inside the trigger, you can use COLUMNS_UPDATED() like this in order to get updated value
-- Get the table id of the trigger
--
DECLARE #idTable INT
SELECT #idTable = T.id
FROM sysobjects P JOIN sysobjects T ON P.parent_obj = T.id
WHERE P.id = ##procid
-- Get COLUMNS_UPDATED if update
--
DECLARE #Columns_Updated VARCHAR(50)
SELECT #Columns_Updated = ISNULL(#Columns_Updated + ', ', '') + name
FROM syscolumns
WHERE id = #idTable
AND CONVERT(VARBINARY,REVERSE(COLUMNS_UPDATED())) & POWER(CONVERT(BIGINT, 2), colorder - 1) > 0
But this snipet of code fails when you have a table with more than 62 columns.. Arth.Overflow...
Here is the final version which handles more than 62 columns but give only the number of the updated columns. It's easy to link with 'syscolumns' to get the name
DECLARE #Columns_Updated VARCHAR(100)
SET #Columns_Updated = ''
DECLARE #maxByteCU INT
DECLARE #curByteCU INT
SELECT #maxByteCU = DATALENGTH(COLUMNS_UPDATED()),
#curByteCU = 1
WHILE #curByteCU <= #maxByteCU BEGIN
DECLARE #cByte INT
SET #cByte = SUBSTRING(COLUMNS_UPDATED(), #curByteCU, 1)
DECLARE #curBit INT
DECLARE #maxBit INT
SELECT #curBit = 1,
#maxBit = 8
WHILE #curBit <= #maxBit BEGIN
IF CONVERT(BIT, #cByte & POWER(2,#curBit - 1)) <> 0
SET #Columns_Updated = #Columns_Updated + '[' + CONVERT(VARCHAR, 8 * (#curByteCU - 1) + #curBit) + ']'
SET #curBit = #curBit + 1
END
SET #curByteCU = #curByteCU + 1
END
I've done it as simple "one-liner". Without using, pivot, loops, many variables etc. that makes it looking like procedural programming. SQL should be used to process data sets :-), the solution is:
DECLARE #sql as NVARCHAR(1024);
select #sql = coalesce(#sql + ',' + quotename(column_name), quotename(column_name))
from INFORMATION_SCHEMA.COLUMNS
where substring(columns_updated(), columnproperty(object_id(table_schema + '.' + table_name, 'U'), column_name, 'columnId') / 8 + 1, 1) & power(2, -1 + columnproperty(object_id(table_schema + '.' + table_name, 'U'), column_name, 'columnId') % 8 ) > 0
and table_name = 'DBCustomers'
-- and column_name in ('c1', 'c2') -- limit to specific columns
-- and column_name not in ('c3', 'c4') -- or exclude specific columns
SET #sql = 'SELECT ' + #sql + ' FROM inserted FOR XML RAW';
DECLARE #x as XML;
SET #x = CAST(EXEC(#sql) AS XML);
It uses COLUMNS_UPDATED, takes care of more than eight columns - it handles as many columns as you want.
It takes care on proper columns order which should be get using COLUMNPROPERTY.
It is based on view COLUMNS so it may include or exclude only specific columns.
The below code works for over 64 columns and logs only the updated columns. Follow the instruction in the comments and all should be well.
/*******************************************************************************************
* Add the below table to your database to track data changes using the trigger *
* below. Remember to change the variables in the trigger to match the table that *
* will be firing the trigger *
*******************************************************************************************/
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE TABLE [dbo].[AuditDataChanges]
(
[RecordId] [INT] IDENTITY(1, 1)
NOT NULL ,
[TableName] [VARCHAR](50) NOT NULL ,
[RecordPK] [VARCHAR](50) NOT NULL ,
[ColumnName] [VARCHAR](50) NOT NULL ,
[OldValue] [VARCHAR](50) NULL ,
[NewValue] [VARCHAR](50) NULL ,
[ChangeDate] [DATETIME2](7) NOT NULL ,
[UpdatedBy] [VARCHAR](50) NOT NULL ,
CONSTRAINT [PK_AuditDataChanges] PRIMARY KEY CLUSTERED
( [RecordId] ASC )
WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
)
ON [PRIMARY];
GO
ALTER TABLE [dbo].[AuditDataChanges] ADD CONSTRAINT [DF_AuditDataChanges_ChangeDate] DEFAULT (GETDATE()) FOR [ChangeDate];
GO
/************************************************************************************************
* Add the below trigger to any table you want to audit data changes on. Changes will be saved *
* in the AuditChangesTable. *
************************************************************************************************/
ALTER TRIGGER trg_Survey_Identify_Updated_Columns ON Survey --Change to match your table name
FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
DECLARE #sql VARCHAR(5000) ,
#sqlInserted NVARCHAR(500) ,
#sqlDeleted NVARCHAR(500) ,
#NewValue NVARCHAR(100) ,
#OldValue NVARCHAR(100) ,
#UpdatedBy VARCHAR(50) ,
#ParmDefinitionD NVARCHAR(500) ,
#ParmDefinitionI NVARCHAR(500) ,
#TABLE_NAME VARCHAR(100) ,
#COLUMN_NAME VARCHAR(100) ,
#modifiedColumnsList NVARCHAR(4000) ,
#ColumnListItem NVARCHAR(500) ,
#Pos INT ,
#RecordPk VARCHAR(50) ,
#RecordPkName VARCHAR(50);
SELECT *
INTO #deleted
FROM deleted;
SELECT *
INTO #Inserted
FROM inserted;
SET #TABLE_NAME = 'Survey'; ---Change to your table name
SELECT #UpdatedBy = UpdatedBy --Change to your column name for the user update field
FROM inserted;
SELECT #RecordPk = SurveyId --Change to the table primary key field
FROM inserted;
SET #RecordPkName = 'SurveyId';
SET #modifiedColumnsList = STUFF(( SELECT ',' + name
FROM sys.columns
WHERE object_id = OBJECT_ID(#TABLE_NAME)
AND SUBSTRING(COLUMNS_UPDATED(),
( ( column_id
- 1 ) / 8 + 1 ),
1) & ( POWER(2,
( ( column_id
- 1 ) % 8 + 1 )
- 1) ) = POWER(2,
( column_id - 1 )
% 8)
FOR
XML PATH('')
), 1, 1, '');
WHILE LEN(#modifiedColumnsList) > 0
BEGIN
SET #Pos = CHARINDEX(',', #modifiedColumnsList);
IF #Pos = 0
BEGIN
SET #ColumnListItem = #modifiedColumnsList;
END;
ELSE
BEGIN
SET #ColumnListItem = SUBSTRING(#modifiedColumnsList, 1,
#Pos - 1);
END;
SET #COLUMN_NAME = #ColumnListItem;
SET #ParmDefinitionD = N'#OldValueOut NVARCHAR(100) OUTPUT';
SET #ParmDefinitionI = N'#NewValueOut NVARCHAR(100) OUTPUT';
SET #sqlDeleted = N'SELECT #OldValueOut=' + #COLUMN_NAME
+ ' FROM #deleted where ' + #RecordPkName + '='
+ CONVERT(VARCHAR(50), #RecordPk);
SET #sqlInserted = N'SELECT #NewValueOut=' + #COLUMN_NAME
+ ' FROM #Inserted where ' + #RecordPkName + '='
+ CONVERT(VARCHAR(50), #RecordPk);
EXECUTE sp_executesql #sqlDeleted, #ParmDefinitionD,
#OldValueOut = #OldValue OUTPUT;
EXECUTE sp_executesql #sqlInserted, #ParmDefinitionI,
#NewValueOut = #NewValue OUTPUT;
IF ( LTRIM(RTRIM(#NewValue)) != LTRIM(RTRIM(#OldValue)) )
BEGIN
SET #sql = 'INSERT INTO [dbo].[AuditDataChanges]
([TableName]
,[RecordPK]
,[ColumnName]
,[OldValue]
,[NewValue]
,[UpdatedBy])
VALUES
(' + QUOTENAME(#TABLE_NAME, '''') + '
,' + QUOTENAME(#RecordPk, '''') + '
,' + QUOTENAME(#COLUMN_NAME, '''') + '
,' + QUOTENAME(#OldValue, '''') + '
,' + QUOTENAME(#NewValue, '''') + '
,' + QUOTENAME(#UpdatedBy, '''') + ')';
EXEC (#sql);
END;
SET #COLUMN_NAME = '';
SET #NewValue = '';
SET #OldValue = '';
IF #Pos = 0
BEGIN
SET #modifiedColumnsList = '';
END;
ELSE
BEGIN
-- start substring at the character after the first comma
SET #modifiedColumnsList = SUBSTRING(#modifiedColumnsList,
#Pos + 1,
LEN(#modifiedColumnsList)
- #Pos);
END;
END;
DROP TABLE #Inserted;
DROP TABLE #deleted;
GO
I transformed the accepted answer to get list of column names separated by comma (according to author's recommendation). Output - "Columns_Updated" as 'Column1,Column2,Column5'
-- get names of updated columns
DECLARE #idTable INT
declare #ColumnName nvarchar(300)
declare #ColId int
SELECT #idTable = T.id
FROM sysobjects P JOIN sysobjects T ON P.parent_obj = T.id
WHERE P.id = ##procid
DECLARE #changedProperties nvarchar(max) = ''
DECLARE #Columns_Updated VARCHAR(2000) = ''
DECLARE #maxByteCU INT
DECLARE #curByteCU INT
SELECT #maxByteCU = DATALENGTH(COLUMNS_UPDATED()),
#curByteCU = 1
WHILE #curByteCU <= #maxByteCU BEGIN
DECLARE #cByte INT
SET #cByte = SUBSTRING(COLUMNS_UPDATED(), #curByteCU, 1)
DECLARE #curBit INT
DECLARE #maxBit INT
SELECT #curBit = 1,
#maxBit = 8
WHILE #curBit <= #maxBit BEGIN
IF CONVERT(BIT, #cByte & POWER(2, #curBit - 1)) <> 0 BEGIN
SET #ColId = cast( CONVERT(VARCHAR, 8 * (#curByteCU - 1) + #curBit) as int)
select #ColumnName = [Name]
FROM syscolumns
WHERE id = #idTable and colid = #ColId
SET #Columns_Updated = #Columns_Updated + ',' + #ColumnName
END
SET #curBit = #curBit + 1
END
SET #curByteCU = #curByteCU + 1
END
The only way that occurs to me that you could accomplish this without hard coding column names would be to drop the contents of the deleted table to a temp table, then build a query based on the table definition to to compare the contents of your temp table and the actual table, and return a delimited column list based on whether they do or do not match. Admittedly, the below is elaborate.
Declare #sql nvarchar(4000)
DECLARE #ParmDefinition nvarchar(500)
Declare #OutString varchar(8000)
Declare #tbl sysname
Set #OutString = ''
Set #tbl = 'SomeTable' --The table we are interested in
--Store the contents of deleted in temp table
Select * into #tempDelete from deleted
--Build sql string based on definition
--of table
--to retrieve the column name
--or empty string
--based on comparison between
--target table and temp table
set #sql = ''
Select #sql = #sql + 'Case when IsNull(i.[' + Column_Name +
'],0) = IsNull(d.[' + Column_name + '],0) then ''''
else ' + quotename(Column_Name, char(39)) + ' + '',''' + ' end +'
from information_schema.columns
where table_name = #tbl
--Define output parameter
set #ParmDefinition = '#OutString varchar(8000) OUTPUT'
--Format sql
set #sql = 'Select #OutString = '
+ Substring(#sql,1 , len(#sql) -1) +
' From SomeTable i ' --Will need to be updated for target schema
+ ' inner join #tempDelete d on
i.PK = d.PK ' --Will need to be updated for target schema
--Execute sql and retrieve desired column list in output parameter
exec sp_executesql #sql, #ParmDefinition, #OutString OUT
drop table #tempDelete
--strip trailing column if a non-zero length string
--was returned
if Len(#Outstring) > 0
Set #OutString = Substring(#OutString, 1, Len(#Outstring) -1)
--return comma delimited list of changed columns.
Select #OutString
End
The sample code provided by Rick lack handling for multiple rows update.
Please let me enhance Rick's version as below:
USE [AFC]
GO
/****** Object: Trigger [dbo].[trg_Survey_Identify_Updated_Columns] Script Date: 27/7/2018 14:08:49 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[trg_Survey_Identify_Updated_Columns] ON [dbo].[Sample_Table] --Change to match your table name
FOR INSERT
,UPDATE
AS
SET NOCOUNT ON;
DECLARE #sql VARCHAR(5000)
,#sqlInserted NVARCHAR(500)
,#sqlDeleted NVARCHAR(500)
,#NewValue NVARCHAR(100)
,#OldValue NVARCHAR(100)
,#UpdatedBy VARCHAR(50)
,#ParmDefinitionD NVARCHAR(500)
,#ParmDefinitionI NVARCHAR(500)
,#TABLE_NAME VARCHAR(100)
,#COLUMN_NAME VARCHAR(100)
,#modifiedColumnsList NVARCHAR(4000)
,#ColumnListItem NVARCHAR(500)
,#Pos INT
,#RecordPk VARCHAR(50)
,#RecordPkName VARCHAR(50);
SELECT *
INTO #deleted
FROM deleted;
SELECT *
INTO #Inserted
FROM inserted;
SET #TABLE_NAME = 'Sample_Table';---Change to your table name
DECLARE t_cursor CURSOR
FOR
SELECT ContactID
FROM inserted
OPEN t_cursor
FETCH NEXT
FROM t_cursor
INTO #RecordPk
WHILE ##FETCH_STATUS = 0
BEGIN
--SELECT #UpdatedBy = Surname --Change to your column name for the user update field
--FROM inserted;
--SELECT #RecordPk = ContactID --Change to the table primary key field
--FROM inserted;
SET #RecordPkName = 'ContactID';
SET #modifiedColumnsList = STUFF((
SELECT ',' + name
FROM sys.columns
WHERE object_id = OBJECT_ID(#TABLE_NAME)
AND SUBSTRING(COLUMNS_UPDATED(), ((column_id - 1) / 8 + 1), 1) & (POWER(2, ((column_id - 1) % 8 + 1) - 1)) = POWER(2, (column_id - 1) % 8)
FOR XML PATH('')
), 1, 1, '');
WHILE LEN(#modifiedColumnsList) > 0
BEGIN
SET #Pos = CHARINDEX(',', #modifiedColumnsList);
IF #Pos = 0
BEGIN
SET #ColumnListItem = #modifiedColumnsList;
END;
ELSE
BEGIN
SET #ColumnListItem = SUBSTRING(#modifiedColumnsList, 1, #Pos - 1);
END;
SET #COLUMN_NAME = #ColumnListItem;
SET #ParmDefinitionD = N'#OldValueOut NVARCHAR(100) OUTPUT';
SET #ParmDefinitionI = N'#NewValueOut NVARCHAR(100) OUTPUT';
SET #sqlDeleted = N'SELECT #OldValueOut=' + #COLUMN_NAME + ' FROM #deleted where ' + #RecordPkName + '=' + CONVERT(VARCHAR(50), #RecordPk);
SET #sqlInserted = N'SELECT #NewValueOut=' + #COLUMN_NAME + ' FROM #Inserted where ' + #RecordPkName + '=' + CONVERT(VARCHAR(50), #RecordPk);
EXECUTE sp_executesql #sqlDeleted
,#ParmDefinitionD
,#OldValueOut = #OldValue OUTPUT;
EXECUTE sp_executesql #sqlInserted
,#ParmDefinitionI
,#NewValueOut = #NewValue OUTPUT;
--PRINT #newvalue
--PRINT #oldvalue
IF (LTRIM(RTRIM(#NewValue)) != LTRIM(RTRIM(#OldValue)))
BEGIN
SET #sql = 'INSERT INTO [dbo].[AuditDataChanges]
([TableName]
,[RecordPK]
,[ColumnName]
,[OldValue]
,[NewValue] )
VALUES
(' + QUOTENAME(#TABLE_NAME, '''') + '
,' + QUOTENAME(#RecordPk, '''') + '
,' + QUOTENAME(#COLUMN_NAME, '''') + '
,' + QUOTENAME(#OldValue, '''') + '
,' + QUOTENAME(#NewValue, '''') + '
' + ')';
EXEC (#sql);
END;
SET #COLUMN_NAME = '';
SET #NewValue = '';
SET #OldValue = '';
IF #Pos = 0
BEGIN
SET #modifiedColumnsList = '';
END;
ELSE
BEGIN
-- start substring at the character after the first comma
SET #modifiedColumnsList = SUBSTRING(#modifiedColumnsList, #Pos + 1, LEN(#modifiedColumnsList) - #Pos);
END;
END;
FETCH NEXT
FROM t_cursor
INTO #RecordPk
END
DROP TABLE #Inserted;
DROP TABLE #deleted;
CLOSE t_cursor;
DEALLOCATE t_cursor;
This is perfect example for track log of updated columnwise value with unique records and UpdatedBy user.
IF NOT EXISTS
(SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].[ColumnAuditLogs]')
AND OBJECTPROPERTY(id, N'IsUserTable') = 1)
CREATE TABLE ColumnAuditLogs
(Type CHAR(1),
TableName VARCHAR(128),
PK VARCHAR(1000),
FieldName VARCHAR(128),
OldValue VARCHAR(1000),
NewValue VARCHAR(1000),
UpdateDate datetime,
UserName VARCHAR(128),
UniqueId uniqueidentifier,
UpdatedBy int
)
GO
create TRIGGER TR_ABCTable_AUDIT ON ABCTable FOR UPDATE
AS
DECLARE #bit INT ,
#field INT ,
#maxfield INT ,
#char INT ,
#fieldname VARCHAR(128) ,
#TableName VARCHAR(128) ,
#PKCols VARCHAR(1000) ,
#sql VARCHAR(2000),
#UpdateDate VARCHAR(21) ,
#UserName VARCHAR(128) ,
#Type CHAR(1) ,
#PKSelect VARCHAR(1000),
#UniqueId varchar(100),
#UpdatedBy VARCHAR(50)
--You will need to change #TableName to match the table to be audited.
-- Here we made ABCTable for your example.
SELECT #TableName = 'ABCTable' -- change table name accoring your table name
-- use for table unique records for everytime updation.
set #UniqueId = CONVERT(varchar(100),newID())
-- date and user
SELECT #UserName = SYSTEM_USER ,
#UpdateDate = CONVERT (NVARCHAR(30),GETDATE(),126)
SELECT #UpdatedBy = ModifiedBy --Change to your column name for the user update field
FROM inserted;
-- Action
IF EXISTS (SELECT * FROM inserted)
IF EXISTS (SELECT * FROM deleted)
SELECT #Type = 'U'
ELSE
SELECT #Type = 'I'
ELSE
SELECT #Type = 'D'
-- get list of columns
SELECT * INTO #ins FROM inserted
SELECT * INTO #del FROM deleted
-- Get primary key columns for full outer join
SELECT #PKCols = COALESCE(#PKCols + ' and', ' on')
+ ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = #TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key select for insert
SELECT #PKSelect = COALESCE(#PKSelect+'+','')
+ 'convert(varchar(100),
coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))'
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = #TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
IF #PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, #TableName)
RETURN
END
SELECT #field = 0,
#maxfield = MAX(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #TableName
WHILE #field < #maxfield
BEGIN
SELECT #field = MIN(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND ORDINAL_POSITION > #field
SELECT #bit = (#field - 1 )% 8 + 1
SELECT #bit = POWER(2,#bit - 1)
SELECT #char = ((#field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(),#char, 1) & #bit > 0
OR #Type IN ('I','D')
BEGIN
SELECT #fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND ORDINAL_POSITION = #field
SELECT #sql = '
insert ColumnAuditLogs ( Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName,
UniqueId,
[UpdatedBy])
select ''' + #Type + ''','''
+ #TableName + ''',' + #PKSelect
+ ',''' + #fieldname + ''''
+ ',convert(varchar(1000),d.' + #fieldname + ')'
+ ',convert(varchar(1000),i.' + #fieldname + ')'
+ ',''' + #UpdateDate + ''''
+ ',''' + #UserName + ''''
+ ',''' + #UniqueId + ''''
+ ',' + QUOTENAME(#UpdatedBy, '''')
+ ' from #ins i full outer join #del d'
+ #PKCols
+ ' where i.' + #fieldname + ' <> d.' + #fieldname
+ ' or (i.' + #fieldname + ' is null and d.'
+ #fieldname
+ ' is not null)'
+ ' or (i.' + #fieldname + ' is not null and d.'
+ #fieldname
+ ' is null)'
EXEC (#sql)
END
END
GO