Insert Statement not working for Insert trigger - sql-server

I have a trigger that executes off the back of a stored procedure, to capture certain data changes and inserts, for audit purposes.
There is a stored procedure that adds rows to table DTA, the trigger is coded to fire from this as such;
CREATE TRIGGER [AUDIT_TRACE]
ON [DTA]
AFTER UPDATE, INSERT
AS BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'tmp_inserted')
DROP TABLE tmp_inserted
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'tmp_deleted')
DROP TABLE tmp_deleted
SELECT * INTO tmp_inserted from inserted
SELECT * INTO tmp_deleted from deleted
INSERT INTO [AUDIT_TRAIL]
SELECT
UpdatedDate
,UserName
,Name
,oldValue
,newValue
,DATATABLEID
,ISNULL(AuthInvNo,'')+ISNULL(invNO,'') as InvoiceNumber
,AuthAccount As Product
,AuthValue AS Value
,QTY
,InputScreen
FROM
(
SELECT
i.UpdatedDate as [UpdatedDate]
,psn.UserName as [Username]
,CONCAT(psn.Firstname,' ',psn.surname) as [Name]
,CONVERT(nvarchar(36),i.DataTableId) as [DataTableID]
,dtType.Description as [InputScreen]
,dtat.Description as [ColumnName]
,CONVERT(nvarchar(1000),dtText.Text) as [Entry]
,dtavB.Description as OldValue
,dtavA.Description as NewValue
FROM dt
INNER JOIN inserted i on i.DataTableId = dt.DataTableId
LEFT JOIN deleted d on d.DataTableId = i.DataTableId
INNER JOIN dtavA on dtavA.DataTableAttributeValueId = i.DataTableAttributeValueId
and dtavA.DataTableAttributeTypeId IN ('23087D97-B96B-4015-9E66-258EE7CAF499','2D5E9D64-A2B6-444D-938A-7D8DD66208E0')-- after
LEFT JOIN dtavB on dtavB.DataTableAttributeValueId = d.DataTableAttributeValueId
and dtavB.DataTableAttributeTypeId IN ('23087D97-B96B-4015-9E66-258EE7CAF499','2D5E9D64-A2B6-444D-938A-7D8DD66208E0')-- before
INNER JOIN dtText on dtText.DataTableId = i.DataTableId
INNER JOIN dtType on dtType.DataTableTypeId = dt.DataTableTypeID
INNER JOIN psn on psn.PersonId = i.UpdatedBy
INNER JOIN dtat on dtat.DataTableAttributeTypeId = dtText.DataTableAttributeTypeId
)E
PIVOT(MAX([ENTRY]) FOR [COLUMNNAME] IN(DEBITCREDIT,AuthValue,QTY,AuthAccount,AuthInvNo,InvNO))as p
COMMIT TRANSACTION
END
Now the problem is that when inserting data into the DTA table nothing is being inserted into our AUDIT_TRAIL table, but when a row is updated in the DTA table, the outcome is exactly what we expect, oldValue, NewValue and all. As far as my colleagues and I can tell there is nothing wrong with the query, we have used profiler traces and all the sections are executing as they should. When running the code manually to select from the tmp_Inserted and tmp_Deleted tables, so we can see what the values we are dealing with are, there is again no issue. To further complicate things, when the INSERT INTO statement is run on its own then the newly inserted line appears as we would expect.

In order to ensure that data is not filtered by the JOINs in insert statement within trigger, insert data from INSERTED by joining it with all tables in inline INSERT.
SELECT I.* INTO tmp_inserted
FROM dt
INNER JOIN inserted i on i.DataTableId = dt.DataTableId
LEFT JOIN deleted d etc..

Related

Ms Sql Insert Trigger To Affect Other Table

I am trying to put table b value when table a inserted but nothing effects on table b. Its like there is no trigger. Do you have any suggestion. I have tried below but no result.
alter trigger triggername on tablea after insert as
begin
update tablea set valuetablea_a = valuetablea_b where id = (select distince id from inserted)
end
begin
update tableb set valuetableb_a = (select valuetablea_a from tablea where id = (select distincd id from Inserted))
where date = (select distinct date from Inserted)
end
Try this:
ALTER TRIGGER triggername ON tablea AFTER INSERT AS
BEGIN
UPDATE a
SET
valuetablea_a = I.valuetablea_b
FROM tablea A
INNER JOIN inserted I
ON A.Id = I.Id
UPDATE B
SET
valuetableb_a = A.valuetablea_a
FROM tableb B
INNER JOIN tablea
ON 1=1 AND EXISTS
(
SELECT 1 FROM inserted WHERE ID = A.ID AND [Date] = B.DATE
)
end
In the first update, you are giving this
update tablea set valuetablea_a = valuetablea_b
Which means From TableA update the value from column valuetablea_b to valuetablea_a for each row that was updated. Instead of getting the values from the Updated valuetablea_b column
2nd Update updates the values in TableB.valuetableb_a by matching the Id and Date fileds in the updated records

Trying to insert null data into a view in SQL, any other way?

Part1 on View
1-1) Create a view named VDept_Headcount that reports headcount for each department.
The report includes 3 columns as follow:
Dept_Name, Dept_Number, No_Emp.
Include all the departments.
Show the content of the view through SQL (Select * from VDept_Headcount;)
USE COMPANY
GO
--1-1) Create a view named VDept_Headcount that reports headcount for each department.
CREATE VIEW VDept_Headcount(Dept_Name, Dept_Number, No_Emp)
AS
SELECT d.DNAME, d.DNUMBER, COUNT(e.DNO)
FROM DEPARTMENT d LEFT OUTER JOIN EMPLOYEE e ON e.DNO = d.DNUMBER
GROUP BY d.DNAME, d.DNUMBER;
GO
SELECT *
FROM VDept_Headcount;
USE COMPANY
GO
--Add yourself into Employee
INSERT INTO EMPLOYEE
VALUES('Dan', NULL, 'Chan', '122345687', '13-Nov-1948', '323 Williams Ct, Columbus, OH', 'M', 55000, '987654321', 5);
SELECT *
FROM VDept_Headcount;
My question is there a different way to replace null value if I want add things back in? Can I use a blank value?
You should detect if it's an INSERT OR DELETE , try this :
CREATE TRIGGER AuditEmp_Trigger
ON YOUR_TABLE
AFTER INSERT , DELETE
AS
if (SELECT COUNT(*) FROM INSERTED ) > 0 AND (SELECT COUNT(*) FROM DELETED) =0
BEGIN
INSERT INTO destination_table SELECT * FROM INSERTED;
END
if (SELECT COUNT(*) FROM INSERTED ) = 0 AND (SELECT COUNT(*) FROM DELETED) >0
BEGIN
INSERT INTO destination_table SELECT * FROM DELETED;
END
GO
If you need to check just the columns updated you can use COLUMNS_UPDATED ()
Your trigger needs to be able to handle multiple rows in the Inserted and Deleted pseudo table, since if your UPDATE statement affects 10 rows, the trigger is called ONCE, but these pseudo table contain 10 rows each. You cannot just try to "select" the values from those table - you'd get one arbitrary row, and all other would be ignored and not handled.
You need to write your triggers in a set-based fashion, and in the case of UPDATE, join the two pseudo tables on the primary key of the table they're attached to:
CREATE TRIGGER dbo.EMPLOYEE_FKDno_UPDATE
ON dbo.DEPARTMENT FOR UPDATE
AS
BEGIN
INSERT INTO dbo.Audit_Emp_Record (date_of_change, old_Lname, new_Lname, old_dno, new_dno, old_ssn, new_ssn)
SELECT
GETDATE(), d.Name, i.Name, d.Number, i.Number, d.Mgr_Ssn, i.Mgr_Ssn
FROM
Inserted i
INNER JOIN
Deleted d ON i.PrimaryKey = d.PrimaryKey
In case of the DELETE, you'll just have only the Deleted table with the "old" values - so you'll need something like this:
CREATE TRIGGER dbo.EMPLOYEE_FKDno_DELETE
ON dbo.DEPARTMENT FOR DELETE
AS
BEGIN
INSERT INTO dbo.Audit_Emp_Record (date_of_change, old_Lname, new_Lname, old_dno, new_dno, old_ssn, new_ssn)
SELECT
GETDATE(), d.Name, NULL, d.Number, NULL, d.Mgr_Ssn, NULL
FROM
Deleted d
And in case of the INSERT, you'll just have only the Inserted table with the new values - so you'll need something like this:
CREATE TRIGGER dbo.EMPLOYEE_FKDno_DELETE
ON dbo.DEPARTMENT FOR DELETE
AS
BEGIN
INSERT INTO dbo.Audit_Emp_Record (date_of_change, old_Lname, new_Lname, old_dno, new_dno, old_ssn, new_ssn)
SELECT
GETDATE(), NULL, i.Name, NULL, i.Number, NULL, i.Mgr_Ssn
FROM
Inserted i

Creating a single trigger for Insert/Update

I am using SQL Server 2008.
Assuming I have Table A which is a transaction table. And Table B which is a history table.
Whenever a row is inserted or updated in Table A, a new row should be inserted in Table B.
The Status column of Table B should change to INSERTED or UPDATED respectively.
How to handle this from a single trigger?
What you're asking for is quite simple:
CREATE TRIGGER TR_TableA_IU ON dbo.TableA FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
INSERT dbo.TableB (Column1, Column2, Status)
SELECT
I.Column1,
I.Column2,
CASE WHEN EXISTS (SELECT * FROM Deleted) THEN 'UPDATED' ELSE 'INSERTED' END
FROM Inserted I;
If you also wanted to handle deletions, that can be done in a single statement, too:
CREATE TRIGGER TR_TableA_IUD ON dbo.TableA FOR INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
INSERT dbo.TableB (Column1, Column2, Status)
SELECT
I.Column1,
I.Column2,
CASE WHEN EXISTS (SELECT * FROM Deleted) THEN 'UPDATED' ELSE 'INSERTED' END
FROM
Inserted I
UNION ALL
SELECT
D.Column1,
D.Column2,
'DELETED'
FROM Deleted D
WHERE NOT EXISTS (
SELECT * FROM Inserted
);
Wow, there are a lot of outright-wrong and semi-wrong (at least in being overcomplicated) answers given so far.
Assuming that both tables:
has an "Id" column as primary key.
has the same schema, except that history table has an extra "Status" column at the end.
You can create a trigger like this:
CREATE TRIGGER dbo.TableA_InsUpd
ON dbo.TableA
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
Insert Into TableB
Select i.*, 'INSERTED'
From inserted i
Where not exists ( Select * From deleted d Where d.Id = i.Id )
Update B
Set [Status] = 'UPDATED',
Field1 = i.Field1,
Field2 = i.Field2
From TableB B
Inner Join inserted i On i.Id = B.Id
Where exists ( Select * From deleted d Where d.Id = i.Id )
Update B
Set [Status] = 'DELETED'
From TableB B
Inner Join deleted d On d.Id = B.Id
Where not exists ( Select * From inserted i Where i.Id = d.Id )
END
Here is a SqlFiddle with the complete code
(Note this will fail if a record Id is deleted and then inserted again)
Try this code
CREATE TRIGGER YouTriggerName
ON TableA
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
DECLARE #type NVarChar(15)=
CASE when not exists(SELECT * FROM inserted)
THEN 'Deleted'
WHEN exists(SELECT * FROM deleted)
THEN 'Updated'
ELSE
'Inserted'
END
/*
TableB should contains all the columns of TableA
OR tweak it to suit your need
*/
IF #type = 'Deleted' BEGIN
INSERT INTO TableB
SELECT *, #type Stat FROM deleted
END
ELSE BEGIN
INSERT INTO TableB
SELECT *, #type Stat FROM inserted
END
END
NOTE
You will get this error if TableB has identity on since we use (select *)
An explicit value for the identity column in table 'TableB' can only be specified when a column list is used and IDENTITY_INSERT is ON.
this is tested.here employee and employee have same table structure.0 means updated and 1=inserted,2=deleted
Alter trigger trgTest on dbo.employee
AFTER INSERT, UPDATE,Delete
as
Begin
Set noCount on
if exists(select e.id from deleted e inner join inserted i on e.ID=i.id )
Begin
insert into Employee1
select id,name,0 from inserted
End
else if exists(select e.id from Employee1 e inner join deleted d on e.ID=d.id)
Begin
insert into Employee1
select id,name,2 from deleted
End
else
Begin
insert into Employee1
select id,name,1 from inserted
End
End

Is it possible to use a stored procedure to update three different tables?

I have an proc doing a select and update statements as follows. I need to incorporate the update statements and select statement which put the data into a temp table into a single sql select statement.
Is this possible?
'
Yes:
CREATE TABLE TableA
(
valueA int
)
INSERT INTO
TableA
VALUES
(1),
(2),
(3)
GO
CREATE PROCEDURE test_procedure (#in_value int)
AS
BEGIN
--insert into temp table
SELECT
#in_value [out_value]
INTO
#TestTable
-- update with join on temp table
UPDATE
T
SET
T.[out_value] = 2
FROM
#TestTable T
INNER JOIN TableA A on A.valueA = T.out_value
WHERE
A.valueA = 1
-- update with join on temp table again
UPDATE
T
SET
[out_value] = 3
FROM
#TestTable T
INNER JOIN TableA A on A.valueA = T.out_value
WHERE
A.valueA = 2
--select results including the original "in_value"
SELECT
*,
#in_value [in_value]
FROM
TableA A
LEFT JOIN #TestTable T on T.out_value = A.valueA
END;
GO
--execute stored procedure
EXEC test_procedure 1
I have found one way out by using union.
this is bit wierd but since i am not able to find anything else, settled with this method

SQL Server UPDATE with WHERE spanning over 2 tables

I have a SQL Server database and I need to manually do an update query. There for no solutions using any programming language can be used.(stored procedures can be used)
I have 4 tables affected (/used) in the query.
[Orders]
[StatusHistoryForOrder]
[StatusHistory]
[Statuses]
I need to update the field [Orders].[OrderStatusID] which is a foreign key to [Statuses]. (So actually changing the state of the order. The table [StatusHistoryForOrder] is a linking table to [StatusHistory] and only contains 2 colums.
[StatusHistoryForOrder].[OrderId]
[StatusHistoryForOrder].[OrderStatusHistoryid]
Don't say that this is not logically cause I already know that. The company who designed the database is a complete retarded company but the database is now too large to set things straight and there is neither the time or money to do it.
The [StatusHistory] table has multiple columns:
[StatusHistory].[OrderStatusHistoryId]
[StatusHistory].[OrderStatusId]
[StatusHistory].[Date]
[StatusHistory].[Message]
The [StatusHistory].[OrderStatusId] is also a foreign key to [Statuses].
In the update query I need to update the status of the order to status 16. But only on rows that now have status 1 and are older then 60 days. I know I can check the date by using the function
DATEDIFF(DD,[StatusHistory].[Date],GETDATE()) > 60
But how to implement this query if the date field is not in the orders. And to set the new [StatusHistory] a new row has to be made for that table and the [StatusHistoryForOrder] table also needs a new row and the ID of that row needs to be set in the [Orders] table row.
Does anyone know how to do this? I am fairly new to SQL Server (or SQL for that matter) and I have absolutly no clue where to begin.
Conclusion:
I need a stored procedure that first checks every row in [Orders] if the [StatusHistory].[Date] (which is linked to the order using foreign keys) of that order is older that 60. If it is older then a new StatusHistory row must be inserted with the current date and status 16. Then in [StatusHistoryForOrder] a new row must be inserted with the new ID of the statusHistory been set in [StatusHistoryForOrder].[OrderStatusHistoryid] and the order id set in [StatusHistoryForOrder].[OrderId]. And last but not least: The [Orders].[OrderStatusID] also needs to be set to 16.
A select query to select the date and status of the order:
SELECT TOP (100) PERCENT
dbo.Orders.OrderID,
dbo.Statuses.Description AS Status,
dbo.StatusHistory.Date
FROM
dbo.Orders
INNER JOIN
dbo.Statuses
ON
dbo.Orders.OrderStatusID = dbo.Statuses.StatusId
INNER JOIN
dbo.StatusHistoryForOrder
ON
dbo.Orders.OrderID = dbo.StatusHistoryForOrder.OrderId
INNER JOIN
dbo.StatusHistory
ON
dbo.StatusHistoryForOrder.OrderStatusHistoryid = dbo.StatusHistory.OrderStatusHistoryId
WHERE
(dbo.Statuses.StatusId = 1)
AND
(DATEDIFF(DD, dbo.StatusHistory.Date, GETDATE()) > 60)
UPDATE
For #marc_s:
Can anyone help me with that?
Try this CTE (Common Table Expression) to find all those orders - does it work, are the results plausible? (this doesn't update anything just yet - just SELECTing for now):
USE (your database name here)
GO
DECLARE #OrdersToUpdate TABLE (OrderID INT, StatusHistoryID INT, StatusDate DATETIME)
;WITH RelevantOrders AS
(
SELECT
o.OrderId, sh.Date
FROM dbo.Orders o
INNER JOIN dbo.StatusHistoryForOrder ho ON ho.OrderId = o.OrderId
INNER JOIN dbo.StatusHistory sh ON ho.OrderStatusHistoryid = sh.OrderStatusHistoryid
WHERE
sh.Date <= DATEADD(D, -60, GETDATE()) -- older than 60 days back from today
AND o.OrderStatusID = 1 -- status = 1
)
INSERT INTO #OrdersToUpdate(OrderID, StatusDate)
SELECT OrderID, [Date]
FROM RelevantOrders
BEGIN TRANSACTION
BEGIN TRY
DECLARE #OrderIDToInsert INT, -- OrderID to process
#InsertedStatusHistoryID INT -- new ID of the inserted row in StatusHistory
-- grab the first OrderID that needs to be processed
SELECT TOP 1 #OrderIDToInsert = OrderID
FROM #OrdersToUpdate
WHERE StatusHistoryID IS NULL
ORDER BY OrderID
-- as long as there are still more OrderID to be processed ....
WHILE #OrderIDToInsert IS NOT NULL
BEGIN
PRINT 'Now inserting new StatusHistory entry for OrderID = ' + CAST(#OrderIDToInsert AS VARCHAR(10))
INSERT INTO dbo.StatusHistory(OrderStatusID, [Date], [Message])
VALUES(16, GETDATE(), 'Bulk Insert/Update operation') -- enter here whatever you want to store
SELECT #InsertedStatusHistoryID = SCOPE_IDENTITY(); -- grab newly inserted ID
PRINT 'New StatusHistory entry inserted with ID = ' + CAST(#InsertedStatusHistoryID AS VARCHAR(10))
UPDATE #OrdersToUpdate
SET StatusHistoryID = #InsertedStatusHistoryID
WHERE OrderID = #OrderIDToInsert
-- safety - reset #OrderIDToInsert to NULL so that we'll know when we're done
SET #OrderIDToInsert = NULL
-- read next OrderID to be processed
SELECT TOP 1 #OrderIDToInsert = OrderID
FROM #OrdersToUpdate
WHERE StatusHistoryID IS NULL
ORDER BY OrderID
END
-- insert into the StatusHistoryForOrder table
INSERT INTO dbo.StatusHistoryForOrder(OrderID, OrderStatusHistoryID)
SELECT OrderID, StatusHistoryID
FROM #OrdersToUpdate
-- update your Orders to status ID = 16
UPDATE dbo.Orders
SET OrderStatusID = 16
FROM #OrdersToUpdate upd
WHERE dbo.Orders.OrderID = upd.OrderID
COMMIT TRANSACTION
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
ROLLBACK TRANSACTION
END CATCH
This CTE basically joins your Orders table to the StatusHistory table (via the intermediate link table) and selects the values you're interested in (hopefully!).
This particular problem seems solvable with set operations only.
DECLARE #Orders TABLE (ID int, rownum int IDENTITY);
DECLARE #StatusHistory TABLE (ID int, rownum int IDENTITY);
/* get the list of orders with expired statuses */
INSERT INTO #Orders (ID)
SELECT o.OrderID
FROM Orders o
INNER JOIN StatusHistoryForOrder shfo ON o.OrderID = shfo.OrderId
INNER JOIN StatusHistory sh ON shfo.OrderStatusHistoryid = sh.OrderStatusHistoryId
GROUP BY o.OrderID
HAVING DATEDIFF(DD, MAX(sh.Date), GETDATE()) > 60
/* add so many new rows to StatusHistory and remember the new IDs */
INSERT INTO StatusHistory (OrderStatusId, Date, Message)
OUTPUT inserted.OrderStatusHistoryId INTO #StatusHistory (ID)
SELECT
16,
GETDATE(),
'Auto-inserted as the previous status has expired'
FROM #Orders
/* join the two temp lists together and add rows to StatusHistoryForOrder */
INSERT INTO StatusHistoryForOrder (OrderId, OrderStatusHistoryid)
SELECT o.ID, sh.ID
FROM #Orders o
INNER JOIN #StatusHistory sh ON o.rownum = sh.rownum
/* finally update the statuses in Orders */
UPDATE Orders
SET OrderStatusID = 16
FROM #Orders o
WHERE Orders.OrderID = o.ID
This should be the body of a single transaction, of course.

Resources