I have complaint table
1. tblProfile with columns
userId | name | age | address | mobileno |
2. tblUserId with columns userId | role | status
now when user fills the form I want to insert one row in tblProfile, before inserting a new row I want to create userId by combining starting letters of name and mobile no and then insert into tblprofile with userId after this I want to insert that UserId into tblUserId table.
for this I have to use two triggers one is before insert trigger and another is after insert trigger.but I dont know how to capture user information to create userId and how to pass that Id to second trigger.
Since you're acting upon INSERTs, try #inserted or inserted as the container of your inserting values.
CREATE TRIGGER...
insert into tblProfile (userId, name, age, address, mobileno) (
select N'do-your-concat-here...', name, age, address, mobileno
from inserted
)
I have not worked with triggers for a long time now, but this should help you get what you need.
Please have an eye out this link: Using the inserted and deleted Tables.
However, you already have all the tools on the application-side as you have the information and the capability to work with the in-memory information data.
EDIT #1
how to pass this Id to second trigger?
In this particular situation, I see more suitable to process with a stored procedure than two independant triggers. We have interdependent information data here (userId). I do think the simplest way would be a stored procedure. It is adviseable to wrap these operation within a transaction scope, as if either insert fails, both won't be applied, assuring data integrity.
CREATE PROCEDURE prcInsProfileUserId
-- Assuming data types. Replace with proper data types as needed.
#name nvarchar(50) NOT NULL
, #age int NOT NULL
, #address nvarchar(150) NOT NULL
, #mobileno bigint NOT NULL
, #role nvarchar(10) NOT NULL
, #status int NOT NULL
AS BEGIN TRANSACTION
DECLARE #UserId nvarchar(10)
SET #UserId = N'do-your-concat-here...';
-- We then have the userId value, so we may insert into both tables accordingly.
insert into tblProfile (userId, name, age, mobileno)
values (#userId, #name, #age, #address, #mobileno)
insert into tblUserId (userId, role, status)
values (#userId, #role, #status)
COMMIT
END
However, if you do prefer to go with triggers, then the alternative would be to insert the concatenated value for userId into a temporary table. You then should have a DDL looking as follows:
DECLARE #UserIdTempTable TABLE (
userId nvarchar(10) NOT NULL
)
Then, within the first trigger, you would have to set the value of a #userId variable to contain you concatenated userId, then use it to insert into tblProfile, then perform a second insert into #UserIdTempTable.
CREATE TRIGGER...
DECLARE #userId nvarchar(10)
SET #userId = N'do-your-concat-here...'
insert into tblProfile (userId, name, age, address, mobileno) (
select #userId, name, age, address, mobileno
from inserted
)
IF ##ROWCOUNT > 0
BEGIN
delete from #UserIdTempTable -- assuring there is no mistake possible while populating and retrieving the userId
insert into #UserIdTempTable (userId)
values (#userId)
END
END
Then, you may select it from your second trigger.
CREATE TRIGGER second...
insert into tblUserId (userId, role, status) (
select tmp.userId, i.role i.status
from #UserIdTempTable tmp
, inserted i
)
Be careful here though, because no data integrity is absolutely preserved as the first insert may have been processed successfully, but the second not. To preserve data integrity, you would have to verify whether ##ROWCOUNT is greater than 0, unless you would delete the record with this actual userId from tblProfile.
This is abosolute hard hand-work. Processing through triggers is not adviseable here, because within the insertion of tblProfile, you do not have information data required by tblUserId, so you have to have two DbCommand and launch two ExecuteNonQuery() in a row. That is a lot of overhead for such a tiny task. Then, it would be more aviseable to process with the stored procedure as suggested, plus it assures data integrity by the DBMS itself, instead of simulating it through a ##ROWCOUNT verification.
Disclaimer: This code is provided as-is and is not guaranteed to compile without you to adapt it to fit your situation. I wrote it off the top of my head with no verification.
I do hope this helps! =)
Related
I am working with an insert trigger and work fine. I am creating an insert trigger and take a backup in tblHist table.
I have two tables:
tblUser - creating this table for insert,update and delete purpose
tblHist - creating this table for store a record for history purpose
tblUser table design:
tblHist table design:
Then I create an insert and update trigger:
ALTER TRIGGER [dbo].[trgr_tblUser_AFTERINSERT]
ON [dbo].[tblUser]
AFTER INSERT, UPDATE
--,DELETE
AS
BEGIN
DECLARE #userid int, #username varchar(50),
#useraddress varchar(50), #countryname varchar(5),
#statename varchar(50), #cityname varchar(50);
BEGIN
SELECT
#userid = u.userid, #username = u.username,
#useraddress = u.useraddress,
#countryname = u.countryname,
#statename = u.statename, #cityname = u.cityname
FROM tblUser u;
INSERT INTO tblHist (userid, username, useraddress, countryname, statename, cityname)
VALUES (#userid, #username, #useraddress, #countryname, #statename, #cityname);
PRINT 'AFTER INSERT update trigger fired.'
END
END
When I insert a record into the tblUser table, then it inserts a record into the tblHist table - this is working fine.
See below
Then I update a record then insert a history in tblHist table working fine.
but issue is when I add a code for delete a record functionality in trgr_tblUser_AFTERINSERT then delete functionality not work
And when I create a delete trigger separately then work fine
See below
ALTER TRIGGER [dbo].[trgr_tblUser_AFTERDELETE]
ON [dbo].[tblUser]
FOR DELETE
AS
BEGIN
DECLARE #userid int, #username varchar(50),
#useraddress varchar(50), #countryname varchar(5),
#statename varchar(50), #cityname varchar(50);
SELECT
#userid = u.userid, #username = u.username,
#useraddress = u.useraddress, #countryname = u.countryname,
#statename = u.statename, #cityname = u.cityname
FROM deleted u;
INSERT INTO tblHist (userid, username, useraddress, countryname, statename, cityname)
VALUES (#userid, #username, #useraddress, #countryname, #statename, #cityname);
PRINT 'AFTER DELETE trigger fired.'
END
I want to add insert, update, delete trigger functionality In one trigger but not work.
What I am trying:
exist select 1 --- but not work
which place I am doing wrong need help
I highly doubt that your first trigger will work properly .... you're just selecting an arbitrary rows from your tblUser table - not even one that's necessarily just been inserted or updated ....
I would strongly recommend these changes:
creating a separate trigger for each operation - that makes the trigger simpler, since you don't need to first figure out what you're dealing with....
add a ModifiedOn DATETIME2(3) column to your tblHist to record the date & time stamp when the change occurred
also possibly add an Operation column to your tblHist - so that you can understand what operation (insert, update, delete) caused this entry in the history table
properly handle the Inserted and Deleted pseudo tables in your trigger code taking into account they can (and will!) contain multiple rows - handle them in a proper, set-based fashion
drop the PRINT - makes no sense inside a trigger....
Code would be something like:
CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterInsert
ON dbo.tblUser
AFTER INSERT
AS
BEGIN
-- do an "INSERT INTO" ...
INSERT INTO tblHist (ModifiedOn, userid, username, useraddress, countryname, statename, cityname)
-- based on the "Inserted" pseudo table, and use proper set-based approach
SELECT
SYSDATETIME(),
i.userid, i.username, i.useraddress, i.countryname, i.statename, i.cityname
FROM
Inserted i;
END
END
and
CREATE OR ALTER TRIGGER dbo.trgr_tblUser_AfterDelete
ON dbo.tblUser
AFTER DELETE
AS
BEGIN
INSERT INTO tblHist (ModifiedOn, userid, username, useraddress, countryname, statename, cityname)
SELECT
SYSDATETIME(),
d.userid, d.username, d.useraddress, d.countryname, d.statename, d.cityname
FROM
Deleted d;
END
END
If you want to check whether it was an insert, update or delete, you have to examine the row count of the inserted and deleted pseudotables - for an insert, records are only present in inserted, for delete they are only present in deleted. An update has both so you can tell the old values (deleted) and the new (inserted)
Make your life easy; put twice as many columns in your hist (should be called UserHist, no?) table as your user table, and make them e.g. old_username, new_username.. an insert the result of a full outer join between the inserted and deleted tables, this way you can tell if it was an insert, update or delete and particularly for an insert, what changed to what
Alternatively, use something like
IF EXISTS(SELECT null FROM inserted)
IF EXISTS(SELECT null FROM deleted)
--it was an update
ELSE
--it was an insert
END;
ELSE
--it was a delete
END;
Or, make 3 separate triggers
Final point of note, you're doing these queries wrong - you're declaring a bunch of variables (that can only hold a single value)and selecting the values from inserted/deleted into them but those pseudotables can have more than one row, if the query affected multiple rows (such as DELETE FROM user WHERE name = 'John')
You should be doing your operations in a bunch-of-rows way, not a "single row" way:
INSERT INTO tblHist
SELECT * FROM inserted
This can insert multiple rows into hist, and this is the way you should always think about doing things in SQLServer.. Even if you only ever insert one row and your inserted pseudotable has one row, you must get into the habit of treating it as "a collection of rows with one entry" so that any code you write won't fall apart when one day it becomes "a collection of rows with multiple entries"
Note in one of your attempts you did not select from inserted, you selected from the users table - this is wrong:
Of course, an AFTER delete trigger will insert nothing if you just deleted the only row from tblusers, but you shouldn't be using tblusers anyway
On a table, there's a delete trigger that performs some operations and then at the end, executes a select statement, so when you do something like...
delete from mytable where id=1
it returns a recordset.
Is there a way to save the results of that recordset into a temp table or something? I tried something like this:
declare #temptable table (returnvalue int);
insert into #temptable (returnvalue)
delete from mytable where id=1;
But apparently that syntax doesn't work.
Well,
I can not imagine a situation that you need to return the recordset of the line you will delete using a trigger returning a recordset. But I am not here to judge your requests.
Well, you can use the OUTPUT to show the row data that will be excluded and enter this data into a temporary table. Follow the example below.
However you should know that: SQL Server does not guarantee the order in Which rows are processed and returned by DML statements using the OUTPUT clause. It is up to the application to include an WHERE clause Appropriate que can guarantee the Desired semantics, or Understand que When multiple rows may qualify for the DML operation, there is guaranteed in order. The Following example uses the subquery and you assume uniqueness is a characteristic of the column in order to DatabaseLogID in Place the Desired ordering semantics. See the link.
Example:
CREATE TABLE Person
(
PersonID int,
LastName varchar(255),
FirstName varchar(255)
);
GO
--DECLARE #MyTablePerson TABLE
--(
-- PersonID int,
-- LastName varchar(255),
-- FirstName varchar(255)
--);
--GO
--CREATE TRIGGER TRG_DLT_Person
--ON Person
--INSTEAD OF DELETE
--AS
--BEGIN
-- Some code you want to do before delete
-- DELETE Person
-- FROM DELETED D
--END
--GO
insert into Person
(PersonID,
LastName,
FirstName)
values
(1,
'Kilmister',
'Lemmy');
GO
insert into Person
(PersonID,
LastName,
FirstName)
values
(2,
'Gilmour',
'David');
GO
insert into Person
(PersonID,
LastName,
FirstName)
values
(3,
'Rose',
'Axl');
GO
insert into Person
(PersonID,
LastName,
FirstName)
values
(4,
'Bullock',
'Sandra');
GO
--
select * from Person;
GO
delete from Person
--output deleted.* INTO #MyTablePerson
output deleted.*
WHERE PersonID = 4 OR PersonID = 2;
GO
select * from Person;
GO
select * from #MyTablePerson;
GO
I put the example I'm showing in a this environment, but in this environment believe that are not supported for temporary tables.
SQL Fiddle
Regardless of this being a bad practice due to it being difficult for anyone interacting with the table to know that it will happen and deal with it when it does, and regardless of it being possible to capture, one pretty solid reason to not return result sets from a trigger is that doing so will be disallowed as of one of the next versions of SQL Server, so you would have to re-code the functionality anyway. The MSDN page for the disallow results from triggers Server Configuration Option states:
Important
This feature will be removed in the next version of Microsoft SQL Server. Do not use this feature in new development work, and modify applications that currently use this feature as soon as possible. We recommend that you set this value to 1.
If you are merely returning something like SELECT IdField FROM deleted; from the trigger, then you should (well, really need to) use the OUTPUT clause instead.
That being said, doing the following will do what you want:
CREATE TABLE #TempResults
(
ReturnValue INT
);
INSERT INTO #TempResults (ReturnValue)
EXEC('DELETE FROM mytable WHERE id = 1;');
You can test with the following:
SET NOCOUNT ON;
IF (OBJECT_ID('dbo.DeleteTriggerWithResults') IS NOT NULL)
BEGIN
DROP TABLE dbo.DeleteTriggerWithResults;
END;
CREATE TABLE dbo.DeleteTriggerWithResults
(
Col1 INT NOT NULL IDENTITY(1, 1),
Col2 DATETIME DEFAULT (GETDATE())
);
GO
CREATE TRIGGER dbo.tr_DeleteTriggerWithResults_d
ON dbo.DeleteTriggerWithResults
AFTER DELETE
AS
BEGIN
SELECT Col1
FROM deleted;
END;
GO
INSERT INTO dbo.DeleteTriggerWithResults DEFAULT VALUES;
GO 30
SELECT * FROM dbo.DeleteTriggerWithResults;
And then run the test:
DECLARE #TempResults TABLE (Col1 INT);
INSERT INTO #TempResults (Col1)
EXEC('
DELETE TOP (10)
FROM dbo.DeleteTriggerWithResults;
');
SELECT * FROM #TempResults;
Returns:
Col1
-------
10
9
8
7
6
5
4
3
2
1
I have the following table:
CREATE TABLE [Test]
(
[Id] BIGINT IDENTITY(1, 1) NOT NULL,
[Name] CHARACTER VARYING(255) NOT NULL,
[DeletedOn] DATETIMEOFFSET NULL,
UNIQUE([Name], [DeletedOn]),
PRIMARY KEY([Id])
);
GO
I insert a new record like this:
INSERT INTO [Test] (Name, DeletedOn) VALUES ('A record', SYSDATETIMEOFFSET())
Subsequent inserts with this command will complete as expected. However, I want to require that before data with a DeletedOn value newer than an existing record with the same Name be rejected so long as a record with the same Name exists in the table with a NULL DeletedOn value.
A different way to explain this behavior would be to have you imagine a user password history. Users enter in passwords and my software hashes them and stores it in the database. A user's password expires and DeletedOn is set to the current date and time. I never want users to enter the same password over again, so that is the purpose for keeping the history. In order to maintain data consistency, I want to prevent a password from being added when there is already an active one that does not have a value in the DeletedOn column. So, if my software erroneously behaves and tries to add random passwords to a user's password history, it should fail because it would violate some constraint that prevents deletion of passwords that are not the single active password.
I originally imagined I would just wrap this logic in a stored procedure and throw an error up if such behavior was attempted, but I am curious if this could be done in a different way.
As per my knowledge There is no out of box feature to prevent new data from being entered until old data is soft deleted? We need to add some CONSTRAINT to prevent that. We can use check constraint and validate whether the combination of Name and Deleted On exists or not.
Try This
CREATE FUNCTION [dbo].[chk_RecordExists](#Name Varchar(255))
RETURNS INT
AS
BEGIN
DECLARE #Result int
SET #Result = 0
DECLARE #id AS INT
SET #id=0
SELECT #id=MAX(ID) FROM [Test] WHERE Name = #Name
IF (#id=0)
BEGIN
SET #Result = 1 -- Allow to insert as its New record
END
ELSE
BEGIN
-- Check the latest record for name if Deletedon is not null then its soft deleted can allow to insert new
IF EXISTS (SELECT ID FROM [Test] WHERE ID=#id AND Name = #Name AND DeletedOn IS NOT NULL)
BEGIN
SET #Result = 1 -- Allow to insert as its old data is soft deleted
END
END
RETURN #Result
END
GO
ALTER TABLE Test WITH NOCHECK ADD CONSTRAINT [chk_Constraint] CHECK(dbo.chk_RecordExists](Name)=0)
GO
I have never user INSTEAD OF keywords but I have read that it can be used to do exactly what you want. Below is sample code from MSDN to prevent insert of new record if there is old record. Full afticle on INSTEAD OF can be found here
CREATE TRIGGER IO_Trig_INS_Employee ON Employee
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
-- Check for duplicate Person. If there is no duplicate, do an insert.
IF (NOT EXISTS (SELECT P.SSN
FROM Person P, inserted I
WHERE P.SSN = I.SSN))
INSERT INTO Person
SELECT SSN,Name,Address,Birthdate
FROM inserted
ELSE
-- Log an attempt to insert duplicate Person row in PersonDuplicates table.
INSERT INTO PersonDuplicates
SELECT SSN,Name,Address,Birthdate,SUSER_SNAME(),GETDATE()
FROM inserted
-- Check for duplicate Employee. If no there is duplicate, do an INSERT.
IF (NOT EXISTS (SELECT E.SSN
FROM EmployeeTable E, inserted
WHERE E.SSN = inserted.SSN))
INSERT INTO EmployeeTable
SELECT EmployeeID,SSN, Department, Salary
FROM inserted
ELSE
--If there is a duplicate, change to UPDATE so that there will not
--be a duplicate key violation error.
UPDATE EmployeeTable
SET EmployeeID = I.EmployeeID,
Department = I.Department,
Salary = I.Salary
FROM EmployeeTable E, inserted I
WHERE E.SSN = I.SSN
END
Changes to trigger above to only insert unique rows
CREATE TRIGGER IO_Trig_INS_TEST ON MyTestTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
IF (
NOT EXISTS ( SELECT P.SSN
FROM MyTestTable P
,inserted I
WHERE P.SSN = I.SSN ) )
INSERT INTO MyTestTable
SELECT FirstName
,LastName
,SSN
FROM ( SELECT FirstName
,LastName
,SSN
,ROW_NUMBER() OVER ( PARTITION BY SSN ORDER BY LastName, Firstname ) AS ROWNUM
FROM INSERTED ) A
WHERE A.ROWNUM = 1
ELSE
RAISERROR(N'You are trying to insert duplicate records',16,1)
END
What is the purpose of the OUTPUT clause? I have gone through the MSDN documentation for the OUTPUT clause, which includes the following example:
DELETE FROM dbo.table1
OUTPUT DELETED.* INTO #MyTableVar
WHERE id = 4 OR id = 2;
From the above query, it seems that deleted records are saved in some magic table called deleted, and the query will load those records into table called MyTableVar from the magic deleted table. .
I still do not understand the purpose of the OUTPUT clause usage.
As another SQL example:
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM #MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
GO
What is this actually doing? Can anyone explain what this clause is doing with an easy example?
UPDATE with non-functioning example:
create proc test
as
CREATE TABLE dbo.table1
(
id INT,
employee VARCHAR(32)
)
go
INSERT INTO dbo.table1 VALUES
(1, 'Fred')
,(2, 'Tom')
,(3, 'Sally')
,(4, 'Alice')
delete from table1
select * from deleted
This gives me an error when I run it, because it can't see the deleted table.
The general purpose of this clause is to capture the changes made to your data without an additional query, which would introduce locking and blocking issues. Example:
DELETE FROM X WHERE Name = 'Foo'
You want to know which IDs were deleted. You can do this naively like this:
SELECT ID FROM X WHERE Name = 'Foo'
DELETE FROM X WHERE Name = 'Foo'
But these selected IDs are unreliable unless you are running in a transaction with isolation level SERIALIZABLE which is usually not the case. Someone else can add, delete or change "Foo"-Records between your two statements. So instead you can use the OUTPUT clause and get back exactly and reliably the deleted IDs without any performance or reliability issues.
Another frequent use is to get the value of inserted default values, especially when using identity columns. For a single insert you can do this:
CREATE TABLE X
(
ID INT IDENTITY,
Name VARCHAR(10)
);
INSERT X (Name) VALUES ('Foo')
SELECT SCOPE_IDENTITY()
But SCOPE_IDENTITY() can give you only the last inserted ID. If you do multiple inserts, like
INSERT X (Name) VALUES ('Foo'), ('Bar')
or
INSERT X (Name) SELECT OtherName FROM Y
and you want to know the inserted IDs, you are out of luck. You can try to find them with another SELECT, but you need another unique column to even formulate the query and then you run into the same issues as with the DELETE sample above. So, the OUTPUT clause lets you identify neatly which Names got which IDs.
You will need these IDs for example when creating dependent records with foreign keys. Think "Order" and "OrderDetails" which are linked by an OrderID column with an IDENTITY clause. Again, with a single INSERT you can get away with using SCOPE_IDENTITY() or ##IDENTITY, but when inserting multiple orders at once, you will need OUTPUT.
When you perform Insert/Update/Delete operation on particular table and want to know what rows are affected OR want to log them for audit trail OR you want to use multiple values of affected rows in subsequent sql statements, you can use OUTPUT clause.
For Insert statement, it will have INSERTED table.
For Delete statement, it will have DELETED table. In case of Update DELETED table will contain rows (with old values) before update operation performed.
For Update statement, it will have DELETED and INSERTED tables.
DELETED table will contain rows (with old values) before update operation performed.
INSERTED table will contain rows (with new values) after update operation performed.
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM #MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
Now your query inserts rows in Production.ScrapReason as well as table variable #MyTableVar. Later it selects inserted rows from Production.ScrapReason and #MyTableVar. Thus you can compare both the resultset and it must have identical rows (considering Production.ScrapReason is empty table.)
I hope it makes sense!
Edit:
Inserted/Deleted tables will be available with Insert/Update/Delete statement and not after that. You may want to store those magic table values in db table or temp table.
Without the OUTPUT clause, how would you know which rows were deleted? Your example seems so simple because you already know the Id values, but what if you did this:
DELETE FROM T WHERE SomeColumn LIKE 'SomePattern%'
And you want to find out what was deleted. That's the purpose of the OUTPUT clause.
I would like to have a stored procedure which inserts rows into a table (retrieved from a select query from another table) and for each newly inserted row gets its identity and updates the original table with the identity
Pseudo code-
records = select id,city,state,country from USER where name=#name
for each record in records // for each rows selected
insert into LOCATION(city,state,country) values(#record.city,#record.state,#record.country); //inserts a value into LOCATION table
#id = SCOPE_IDENTITY(); // gets the identity of the newly inserted row
update USER set LocationId=#id where Id=#record.id //updates the new id back to old table's column
end
This is a data migration task, where we want to segregate the LOCATION from USER table
Thanks in advance for your time and effort for this thread.
You could do something like this:
DECLARE #InsertedValues TABLE (ID INT, City VARCHAR(50), State VARCHAR(50), Country VARCHAR(50))
INSERT INTO dbo.Location(City, State, Country)
OUTPUT Inserted.ID, Inserted.City, Inserted.State, Inserted.Country INTO #InsertedValues(ID, City, State, Country)
SELECT City, State, Country
FROM dbo.YourSourceTable
With this, you now have the inserted values - including the newly defined identity values - in your #InsertedValues table variable and you can now update the source table as you see fit.
UPDATE dbo.YourSourceTable
SET
Col1 = iv.Col1,
Col2 = iv.Col2, -- etc. - do whatever you nee to do here!
FROM #InsertedValues iv
WHERE ......... -- here, you need some condition to link the inserted values to the original table
This doesn't require any cursor or any other messy RBAR (row-by-agonizing-row) processing at all - everything is nicely set-based and as fast as it can possibly be.
Learn more about the OUTPUT clause at MSDN SQL Server Books Online - you can use the OUTPUT clause on insert, update and even delete statements, too!