I am looking for a way for a trigger to insert into a second table only where the value in table 1 changes - sql

I am looking for a way for a trigger to insert into a second table only where the value in table 1 changes. It is essentially an audit tool to trap any changes made. The field in table 1 is price and we want to write additional fields.
This is what I have so far.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
insert into zmerps_Item_costprice_history
select NEWID(), -- unique id
GETDATE(), -- CURRENT_date
'PRICE_CHANGE', -- reason code
a.ima_itemid, -- item id
a.ima_price-- item price
FROM Inserted b inner join item a
on b.ima_recordid = a.IMA_RecordID
The table only contains a unique identifier, date, reference(item) and the field changed (price). It writes any change not just a price change

Is it as simple as this? I moved some of the code around because comments after the comma between columns is just painful to maintain. You also should ALWAYS specify the columns in an insert statement. If your table changes this code will still work.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
insert into zmerps_Item_costprice_history
(
UniqueID
, CURRENT_date
, ReasonCode
, ItemID
, ItemPrice
)
select NEWID()
, GETDATE()
, 'PRICE_CHANGE'
, d.ima_itemid
, d.ima_price
FROM Inserted i
inner join deleted d on d.ima_recordid = i.IMA_RecordID
AND d.ima_price <> i.ima_price

Since you haven't provided any other column names I Have used Column2 and Column3 and the "Other" column names in the below example.
You can expand adding more columns in the below code.
overview about the query below:
Joined the deleted and inserted table (only targeting the rows that has changed) joining with the table itself will result in unnessacary processing of the rows which hasnt changed at all.
I have used NULLIF function to yeild a null value if the value of the column hasnt changed.
converted all the columns to same data type (required for unpivot) .
used unpivot to eliminate all the nulls from the result set.
unpivot will also give you the column name its has unpivoted it.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr
ON [ITEM]
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON ;
WITH CTE AS (
SELECT CAST(NULLIF(i.Price , d.Price) AS NVARCHAR(100)) AS Price
,CAST(NULLIF(i.Column2 , d.Column2) AS NVARCHAR(100)) AS Column2
,CAST(NULLIF(i.Column3 , d.Column3) AS NVARCHAR(100)) AS Column3
FROM dbo.inserted i
INNER JOIN dbo.deleted d ON i.IMA_RecordID = d.IMA_RecordID
WHERE i.Price <> d.Price
OR i.Column2 <> d.Column2
OR i.Column3 <> d.Column3
)
INSERT INTO zmerps_Item_costprice_history
(unique_id, [CURRENT_date], [reason code], Item_Value)
SELECT NEWID()
,GETDATE()
,Value
,ColumnName + '_Change'
FROM CTE UNPIVOT (Value FOR ColumnName IN (Price , Column2, Column3) )up
END

As I understand your question correctly, You want to record change If and only if The column Price value is changes, you dont need any other column changes to be recorded
here is your code
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
if update(ima_price)
insert into zmerps_Item_costprice_history
select NEWID(), -- unique id
GETDATE(), -- CURRENT_date
'PRICE_CHANGE', -- reason code
a.ima_itemid, -- item id
a.ima_price-- item price
FROM Inserted b inner join item a
on b.ima_recordid = a.IMA_RecordID

Related

How to insert bulk data without changing order of item into table using merge statement

I wrote a stored procedure that can insert bulk data into table using the merge statement.
Problem is that when I insert itemid 1024,1000,1012,1025 in this order, then SQL Server automatically changes order of itemid 1000,1012,1024,1025.
I want to insert data that I actually pass.
Here is sample code. This will parse XML string into table object:
DECLARE #tblPurchase TABLE
(
Purchase_Detail_ID INT ,
Purchase_ID INT ,
Head_ID INT ,
Item_ID INT
);
INSERT INTO #tblPurchase (Purchase_Detail_ID, Purchase_ID, Head_ID, Item_ID)
SELECT
Tbl.Col.value('Purchase_Detail_ID[1]', 'INT') AS Purchase_Detail_ID,
Tbl.Col.value('Purchase_ID[1]', 'INT') AS Purchase_ID,
Tbl.Col.value('Head_ID[1]', 'INT') AS Head_ID,
Tbl.Col.value('Item_ID[1]', 'INT') AS Item_ID
FROM
#PurchaseDetailsXML.nodes('/documentelement/TRN_Purchase_Details') Tbl(Col)
This will insert bulk data into the TRN_Purchase_Details table:
MERGE TRN_Purchase_Details MTD
USING (SELECT
Purchase_Detail_ID,
Id AS Purchase_ID,
Head_ID, Item_ID
FROM
#tblPurchase
LEFT JOIN
#ChangeResult ON 1 = 1) AS TMTD ON MTD.Purchase_Detail_ID = TMTD.Purchase_Detail_ID
AND MTD.Purchase_ID = TMTD.Purchase_ID
WHEN MATCHED THEN
UPDATE SET MTD.Head_ID = TMTD.Head_ID,
MTD.Item_ID = TMTD.Item_ID
WHEN NOT MATCHED BY TARGET THEN
INSERT (Purchase_ID, Head_ID, Item_ID)
VALUES (Purchase_ID, Head_ID, Item_ID)
WHEN NOT MATCHED BY SOURCE AND
MTD.Purchase_ID = (SELECT TOP 1 Id
FROM #ChangeResult
WHERE Id > 0) THEN
DELETE;
Rows in a SQL table don't have any order. They come back in indeterminate order unless you specify an order by.
Try adding an identity column to your temporary table?
DECLARE #tblPurchase TABLE
(
ID int identity,
Purchase_Detail_ID INT ,
The identity column might capture the order of the XML elements.
If that doesn't work, you can calculate the position of the elements in the XML and store that position in the temporary table.
As mentioned elsewhere, data in a table is stored as an unordered set. If you need to be able to go back to your table after data is inserted and determine the order that it was inserted, you'll have to add a column to the table schema to record that information.
It could be something as simple as adding an IDENTITY column, which will increment on each row addition, or perhaps a column with a DATETIME data type and a GETDATE() default value so you not only know the order rows were added, but exactly when that happened.

How to copy the column id from another table?

I'm stuck with this since last week. I have two tables, where the id column of CustomerTbl correlates with CustomerID column of PurchaseTbl:
What I'm trying to achieve is I want to duplicate the data of the table from itself, but copy the newly generated id of CustomerTbl to PurchaseTbl's CustomerID
Just like from the screenshots above. Glad for any help :)
You may use OUTPUT clause to access to the new ID. But to access to both OLD ID and NEW ID, you will need to use MERGE statement. INSERT statement does not allow you to access to the source old id.
first you need somewhere to store the old and new id, a mapping table. You may use table variable or temp table
declare #out table
(
old_id int,
new_id int
)
then the merge statement with output clause
merge
#CustomerTbl as t
using
(
select id, name
from CustomerTbl
) as s
on 1 = 2 -- force it to `false`, not matched
when not matched then
insert (name)
values (name)
output -- the output clause
s.id, -- old_id
inserted.id -- new_id
into #out (old_id, new_id);
after that you just use the #out to join back using old_id to obtain the new_id for the PurchaseTbl
insert into PurchaseTbl (CustomerID, Item, Price)
select o.new_id, p.Item, p.Price
from #out o
inner join PurchaseTbl p on o.old_id = p.CustomerID
Not sure what your end game is, but one way you could solve this is this:
INSERT INTO purchaseTbl ( customerid ,
item ,
price )
SELECT customerid + 3 ,
item ,
price
FROM purchaseTbl;

Needing system defined function to select updated or unmatched new records from two tables

I am having a live data table in which the old values are placed,in a new table i am moving data from that live table to this one how to find updated or new records that are inserted or updated in new table with out using except,checksum(binary_checksum) and join ,i am looking for a solution using System Defined Function.
The requirement is interesting as the best solutions are to use EXCEPT or a FULL JOIN. What you are trying to do is what is referred to as an left anti semi join. Here's a good article about the topic.
Note this sample data and the solutions (note that my solution that does not use EXCEPT or a join is the last solution):
-- sample data
if object_id('tempdb.dbo.orig') is not null drop table dbo.orig;
if object_id('tempdb.dbo.new') is not null drop table dbo.new;
create table dbo.orig (someid int, col1 int, constraint uq_cl_orig unique (someid, col1));
create table dbo.new (someid int, col1 int, constraint uq_cl_new unique (someid, col1));
insert dbo.orig values (1,100),(2,110),(3,120),(4,2000)
insert dbo.new values (1,100),(2,110),(3,122),(5,999);
Here's the EXCEPT version
select someid
from
(
select * from dbo.new except
select * from dbo.orig
) n
union -- union "distict"
select someid
from
(
select * from dbo.orig except
select * from dbo.new
) o;
Here's a FULL JOIN Solution which will also tell you if the record was removed, changed or added:
select
someid = isnull(n.someid, o.someid),
[status] =
case
when count(isnull(n.someid, o.someid)) > 1 then 'changed'
when max(n.col1) is null then 'removed' else 'added'
end
from dbo.new n
full join dbo.orig o
on n.col1=o.col1 and n.someid = o.someid
where n.col1 is null or o.col1 is null
group by isnull(n.someid, o.someid);
But, because those efficient solutions are not an option - you will need to go with a NOT IN or NOT EXISTS subquery.... And because it has to be a function, I am encapsulating the logic into a function.
create function dbo.newOrChangedOrRemoved()
returns table as return
-- get the new records
select someid, [status] = 'new'
from dbo.new n
where n.someid not in (select someid from dbo.orig)
union all
-- get the removed records
select someid, 'removed'
from dbo.orig o
where o.someid not in (select someid from dbo.new)
union all
-- get the changed records
select someid, 'changed'
from dbo.orig o
where exists
(
select *
from dbo.new n
where o.someid = n.someid and o.col1 <> n.col1
);
Results:
someid status
----------- -------
5 new
4 removed
3 changed

Inserting data into table variable

I want to know if it's possible to insert data into a table variable from a select query. When using a variable table before I have inserted the values and typed them myself.
I have come up with the following select query that displays the latest comment for a repair order. It returns 37 records and I wanted to know if it's possible to insert those into a variable table.
SELECT
a.CH_REPREF, a.CH_DATE, a.CH_CRTIME,
b.[Latest Customer Comment], a.CH_CRUSER, a.CH_CCOMMNT
FROM
(SELECT
CH_REPREF,
MAX(CH_REPREF1) As [Latest Customer Comment]
FROM
dbo.V_CSRPCH
WHERE
CH_CCOMMNT IS NOT NULL
AND CH_CCOMMNT NOT LIKE 'X'
GROUP BY
CH_REPREF) b,
(SELECT
CH_REPREF, CH_DATE, CH_CRTIME, CH_REPREF1,
CH_CRUSER, CH_CCOMMNT, CH_ACCOUNT
FROM
dbo.V_CSRPCH) a
WHERE
CH_REPREF1 = [Latest Customer Comment]
AND CH_ACCOUNT = 'DDCHC'
I have started the table variable with the following, but am stuck whether it's possible to fill it with the data returned by the select query:
Declare #tbl_last_customer_comment TABLE
(CH_REPREF nvarchar(10),
CH_DATE smalldatetime,
CH_CRTIME nvarchar(8),
Latest int,
CH_CRUSER nvarchar(8),
CH_CCOMMNT text)
just do it as 'constant' table-
INSERT INTO #tbl_last_customer_comment
SELECT a.CH_REPREF, a.CH_DATE, a.CH_CRTIME,b.[Latest Customer Comment],a.CH_CRUSER,a.CH_CCOMMNT
FROM (SELECT CH_REPREF, MAX(CH_REPREF1) As [Latest Customer Comment]
FROM dbo.V_CSRPCH
WHERE CH_CCOMMNT IS Not Null AND CH_CCOMMNT Not Like 'X'
GROUP BY CH_REPREF) b,
(SELECT CH_REPREF, CH_DATE, CH_CRTIME,CH_REPREF1, CH_CRUSER, CH_CCOMMNT, CH_ACCOUNT
FROM dbo.V_CSRPCH) a
WHERE CH_REPREF1 = [Latest Customer Comment] AND CH_ACCOUNT = 'DDCHC'

sql server: How to detect changed rows

I want to create a trigger to detect whether a row has been changed in SQL Server. My current approach is to loop through each field, apply COLUMNS_UPDATED() to detect whether UPDATE has been called, then finally compare the values of this field for the same row (identified by PK) in inserted vs deleted.
I want to eliminate the looping from the procedure. Probably I can dump the content of inserted and deleted into one table, group on all columns, and pick up the rows with count=2. Those rows will count as unchanged.
The end goal is to create an audit trail:
1) Track user and timestamp
2) Track insert, delete and REAL changes
Any suggestion is appreciated.
Instead of looping you can use BINARY_CHECKSUM to compare entire rows between the inserted and deleted tables, and then act accordingly.
Example
Create table SomeTable(id int, value varchar(100))
Create table SomeAudit(id int, Oldvalue varchar(100), NewValue varchar(100))
Create trigger tr_SomTrigger on SomeTable for Update
as
begin
insert into SomeAudit
(Id, OldValue, NewValue)
select i.Id, d.Value, i.Value
from
(
Select Id, Value, Binary_CheckSum(*) Version from Inserted
) i
inner join
(
Select Id, Value, Binary_CheckSum(*) Version from Deleted
) d
on i.Id = d.Id and i.Version <> d.Version
End
Insert into sometable values (1, 'this')
Update SomeTable set Value = 'That'
Select * from SomeAudit