How to create procedure with to Keep Track of Last Changed Data - sql

So i have two tables: User and LogUser. When i update table User the old data should be keep for history in LogUser table.
I have created procedure for update, but don't know how to keep history of changed data.
Update Procedure:
CREATE PROCEDURE dbo.UpdateUser
(
#UserID int,
#Name nvarchar(40),
#Address nvarchar(60),
#City nvarchar(15),
)
AS
SET NOCOUNT OFF;
UPDATE [dbo].[User] SET [UserID] = #UserID, [Name] = #Name, [Address] = #Address, [City] = #City,WHERE (([UserID] = #UserID));
PROCEDURE:
CREATE PROCEDURE dbo.UpdateUser
(
#UserID int,
#Name nvarchar(40),
#Address nvarchar(60),
#City nvarchar(15)
)
AS
BEGIN
INSERT INTO
dbo.LogUser (UserID, Name, Address, City)
SELECT
UserID, Name, Address, City
FROM
dbo.User
WHERE
UserID = #UserID;
UPDATE
dbo.User
SET
UserID = #UserID,
Name = #Name,
Address = #Address,
City = #City
WHERE
UserID = #UserID;
END

How about inserting the data into the LogUser table before you update it?
-- Step 1: Add user history into LogUser table
INSERT INTO
dbo.LogUser (UserID, Name, Address, City)
SELECT
UserID, Name, Address, City
FROM
dbo.User
WHERE
UserID = #UserID;
-- Step 2: Update the user in the User table
UPDATE
dbo.User
SET
Name = #Name,
Address = #Address,
City = #City
WHERE
UserID = #UserID;

Create dbo.LogUser
create table dbo.LogUser (
action varchar(3),
changedAt datetime,
userID nchar(5), -- why not int ???
Name nvarchar(40),
Address nvarchar(60),
City nvarchar(15) )
Use trigger on dbo.User
create trigger dbo.User_UPD on dbo.User
for update, delete
as begin
insert dbo.LogUser
select case when i.UserID is null then 'Del' else 'Upd' end,
GETDATE(),
d.UserID,
d.Name,
d.Address,
d.City
from deleted d
left join inserted i on i.UserID = d.UserID
end
Update dbo.Users as You want.
Update dbo.Users
Set Name = 'Joe'

Related

Create trigger in SQL using inserted table to concatenate data, but end up with two rows

I'm trying to create a trigger when inserting first name and last name (without email), it will automatically generate the email, but it gives me two rows. One is that the email is NULL, and the other is the correct result.
CREATE TRIGGER trg_assignEmail
ON StudentInformation
FOR INSERT
AS
BEGIN
DECLARE
#FirstName NVARCHAR (50),
#LastName NVARCHAR (50),
#Email NVARCHAR (100)
SELECT
#FirstName = inserted.FirstName,
#LastName = inserted.LastName,
#Email = inserted.Email
FROM
inserted
IF NOT EXISTS (SELECT Email FROM inserted)
SET #Email = #FirstName+'.'+#LastName+'#disney.com'
SELECT
#FirstName = inserted.FirstName,
#LastName = inserted.LastName,
#Email = TRIM(#FirstName)+'.'+TRIM(#LastName)+'#disney.com'
FROM
inserted
INSERT INTO StudentInformation
(
FirstName, LastName, Email
)
values (#FirstName, #LastName, #Email)
END
The results
I guess you are looking for something similar to this:
CREATE TRIGGER trg_assignEmail
ON StudentInformation
INSTEAD OF INSERT
AS
BEGIN
DECLARE
#FirstName NVARCHAR (50),
#LastName NVARCHAR (50),
#Email NVARCHAR (100)
SELECT
#FirstName = INSERTED.FirstName,
#LastName = INSERTED.LastName,
#Email = INSERTED.Email
FROM
INSERTED
IF (#Email IS NULL)
BEGIN
SET #Email = TRIM(#FirstName)+'.'+TRIM(#LastName)+'#disney.com'
END
INSERT INTO StudentInformation (FirstName, LastName, Email)
VALUES (#FirstName, #LastName, #Email)
END
When a new record is inserted to StudentInformation, it is required to check whether the email is provided and create a new email address when it's not provided. So INSTEAD OF INSERTis used in my attempt above to do the checking before the INSERT statement.

How to map table fields from source as the variables of stored procedure in destination in SSIS Package?

I want to transfer data from source Database table named Patient (which contains many rows) to destination database tables(2) named Person & Patient.
I already have stored procedure named AddPatient in destination database which will add person related fields to Person table and other fields to Patient table, so I would like to execute that procedure and to assign the fields from source database as variables to it. The following are the code of AddPatient sp in destination database.
ALTER PROCEDURE [dbo].[AddPatient]
(
#TenantId BIGINT,
#FirstName NVARCHAR(100),
#LastName NVARCHAR(100),
#PersonNumber NVARCHAR(20),
#MobileNumber NVARCHAR(20),
#EmailId NVARCHAR(100),
#Address NVARCHAR(255),
#City NVARCHAR(50),
#ZipCode NVARCHAR(20),
#ListComments NVARCHAR(1000),
#Comment NVARCHAR(500),
#AlternateEmailId NVARCHAR(100) ,
#HomePhone NVARCHAR(20) ,
#Relative NVARCHAR(255) ,
#HasDiabetes [bit],
#HasBlooPressure [bit],
#AddedBy BIGINT,
#AddedDateTime smalldatetime,
#PersonId BIGINT OUTPUT
)
AS
BEGIN
SET NOCOUNT ON
IF #TenantId IS NULL
RAISERROR('The value for #TenantID should not be null', 15, 1) -- with log
ELSE
BEGIN
DECLARE #new_person_id BIGINT
DECLARE #new_patient_id BIGINT
DECLARE #PatientIdentifier NVARCHAR(50)
EXEC dbo.GetNextPatientIdForTenant #TenantID, #PatientIdentifier OUTPUT
INSERT INTO dbo.Person
(
TenantId,
FirstName,
LastName,
PersonNumber,
MobileNumber,
EmailId,
Address,
City,
ZipCode,
AddedBy,
AddedDateTime
)
VALUES
(
#TenantId,
#FirstName,
#LastName,
#PersonNumber,
#MobileNumber,
#EmailId,
#Address,
#City,
#ZipCode,
#AddedBy,
#AddedDateTime
)
SELECT #new_person_id = SCOPE_IDENTITY()
INSERT INTO dbo.Patient
(
TenantId,
PatientIdentifier,
PersonId,
ListComments,
Comment,
AlternateEmailId,
HomePhone,
Relative,
HasDiabetes,
HasBlooPressure,
AddedBy,
AddedDateTime
)
VALUES
(
#TenantId,
#PatientIdentifier,
#new_person_id,
#ListComments ,
#Comment ,
#AlternateEmailId,
#HomePhone ,
#Relative ,
#HasDiabetes,
#HasBlooPressure,
#AddedBy ,
#AddedDateTime
)
SELECT #new_patient_id = SCOPE_IDENTITY()
SELECT #PersonId = #new_person_id
SELECT #new_patient_id
END
END
There is no TenantId & AddedBy field in source, so I want to assign both as 1 for all rows to be transfered.
I know Execute SQL Task will handles stored procedure and for each row data Foreach Loop Container will take care in SSIS. But I don't know how to assign the variables of sp in destination database to the fields of table from source database.
Anyone help me with this.
Thanks in advance !

Pass Output Value of one stored procedure to another stored procedure

I want to use output value of one stored procedure in another stored procedure .
Stored procedure 1:
Create PROCEDURE [dbo].[usp_AddUpdateUser]
#UserId INT,
#Email varchar(50),
#FirstName varchar(50)
AS
BEGIN
MERGE [User] AS target
USING (SELECT #UserId) AS source (Id)
ON target.Id = source.Id
WHEN MATCHED THEN
UPDATE
SET Email = #Email,
FirstName = #FirstName
WHEN NOT MATCHED THEN
INSERT (Email, FirstName)
VALUES (#Email, #FirstName)
OUTPUT inserted.Id;
END
Now I want to use the inserted Id of above stored procedure to below stored procedure:
ALTER PROCEDURE usp_AddUpdateDealer
(#Id INT,
#DealerName varchar(55),
#Email varchar(55),
#UserId INT)
AS
BEGIN
DECLARE #NewUserId INT
EXEC #NewUserId = usp_AddUpdateUser #UserId, #Email, #DealerName
MERGE Dealer AS target
USING (SELECT #Id) AS source (Id) ON target.Id = source.Id
WHEN MATCHED THEN
UPDATE
SET #DealerName = #DealerName,
Email = #Email,
UserId = #NewUserId
WHEN NOT MATCHED THEN
INSERT (DealerName, Email, UserId)
VALUES (#DealerName, #Email, #NewUserId)
OUTPUT inserted.Id;
END
#NewUserId not gives the output value.
How can I got the output option of the usp_AddUpdateUser stored procedure to use that in next statement?
ALTER PROCEDURE usp_AddUpdateDealer
(
#Id INT,
#DealerName varchar(55),
#Email varchar(55),
#UserId INT
)
AS
BEGIN
DECLARE #t table(NewUserId INT )
INSERT #t(NewUserId)
EXEC #NewUserId = usp_AddUpdateUser #UserId,#Email,#DealerName
DECLARE #NewUserId INT
SELECT #NewUserId = NewUserId FROM #t
MERGE Dealer AS target
USING (SELECT #Id) AS source (Id)
ON target.Id = source.Id
WHEN MATCHED THEN
UPDATE
SET #DealerName = #DealerName,
Email = #Email,
UserId=#NewUserId
WHEN NOT MATCHED THEN
INSERT (DealerName,Email,UserId)
VALUES (#DealerName,#Email,#NewUserId)
OUTPUT inserted.Id;
END

Creating a status update trigger

I have 2 tables like so:
JOBS table
Jobcode UserId Status
101 130 R
102 139 D
USERS table
UserId Email
130 test#example.com
I want to create a trigger on insert and update that sends an email to my stored procedure:
EXEC dbo.SendMyEmail #email, #jobcode;
when the jobcode is inserted as 'D' or updated to 'D'.
In my opinion, sending email in a trigger is not optimal.
Instead, you should just insert to a queue table, and have a process run frequently that checks the table and sends the email.
What happens if you get an error in your email procedure? It will force a rollback of your job completion status. Only you know whether that is minor or possibly catastrophic. But I can tell you for sure that DB best practice is to NOT do extended I/O during a DML operation.
CREATE TRIGGER TR_Jobs_EnqueueEmail_IU ON dbo.Jobs FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
INSERT dbo.EmailQueue (UserID, JobCode)
SELECT UserID, JobCode
FROM
Inserted I
LEFT JOIN Deleted D
ON I.JobCode = D.JobCode -- or proper PK columns
WHERE
IsNull(D.Status, 'R') <> 'D'
AND I.Status = 'D';
Tables needed:
CREATE TABLE dbo.EmailQueue (
QueuedDate datetime NOT NULL
CONSTRAINT DF_EmailQueue_QeueueDate DEFAULT (GetDate()),
UserID int NOT NULL,
JobCode int NOT NULL,
CONSTRAINT PK_EmailQueue PRIMARY KEY CLUSTERED (QueuedDate, UserID, JobCode)
);
CREATE TABLE dbo.EmailSent (
SentDate datetime NOT NULL
CONSTRAINT DF_EmailSent_SentDate DEFAULT (GetDate()),
QueuedDate datetime NOT NULL,
UserID int NOT NULL,
JobCode int NOT NULL,
CONSTRAINT PK_EmailSent PRIMARY KEY CLUSTERED (SentDate, QueuedDate, UserID, JobCode)
);
Then, run the following stored procedure once a minute from a SQL Job:
CREATE PROCEDURE dbo.EmailProcess
AS
DECLARE #Email TABLE (
QueuedDate datetime,
UserID int,
JobCode int
);
DECLARE
#EmailAddress nvarchar(255),
#JobCode int;
WHILE 1 = 1 BEGIN
DELETE TOP 1 Q.*
OUTPUT Inserted.QueuedDate, Inserted.UserID, Inserted.JobCode
INTO #Email (QueuedDate, UserID, JobCode)
FROM dbo.EmailQueue Q WITH (UPDLOCK, ROWLOCK, READPAST)
ORDER BY QueuedDate;
IF ##RowCount = 0 RETURN;
SELECT #EmailAddress = U.EmailAddress, #JobCode = E.JobCode
FROM
#Email E
INNER JOIN dbo.User U
ON E.UserID = U.UserID;
EXEC dbo.SendMyEmail #EmailAddress, #JobCode;
DELETE E
OUTPUT QueuedDate, UserID, JobCode
INTO dbo.EmailSent (QueuedDate, UserID, JobCode)
FROM #Email E;
END;
The delete pattern and locks I used are very specifically chosen. If you change them or change the delete pattern in any way it is almost certain you will break it. Handling locks and concurrency is hard. Don't change it.
Note: I typed all the above without checking anything on a SQL Server. It is likely there are typos. Please forgive any.
I'm not sure about data types etc but this should at least put you on the right track.
Hope it helps...
CREATE TRIGGER SendEmailOnStatusD
ON JOBS
-- trigger is fired when an update is made for the table
FOR UPDATE --You can add the same for INSERT
AS
-- holds the UserID so we know which Customer was updated
DECLARE #UserID int
DECLARE #JobCode int
SELECT #UserID = UserId, #JobCode = JobCode
FROM INSERTED WHERE [Status] = 'D' --If you want the old value before the update, use 'deleted' table instead of 'inserted' table
IF (#UserID IS NOT NULL)
BEGIN
-- holds the email
DECLARE #email varchar(250)
SELECT #email = Email FROM USERS WHERE UserId = #UserID
EXEC SendMyEmail (#email, #jobcode);
END
GO
EDIT:
Above code does not handle multiple updates, so for better practice see below option
CREATE TRIGGER SendEmailOnStatusD ON JOBS
-- trigger is fired when an update is made for the table
FOR UPDATE --You can add the same for INSERT
AS
DECLARE #Updates table(UserID int, JobCode int, Email varchar(250))
INSERT INTO #Updates (UserID, JobCode, Email)
SELECT i.UserID, i.JobCode, u.Email
FROM INSERTED i
JOIN USERS u ON i.UserID = u.UserID
WHERE [Status] = 'D'
DECLARE #UserID int
DECLARE #JobCode int
DECLARE #Email varchar(250)
WHILE EXISTS(SELECT * FROM #Updates)
BEGIN
SELECT TOP 1
#UserID = UserID,
#Email = Email,
#JobCode = JobCode
FROM #Updates WHERE UserID = #UserID
EXEC SendMyEmail (#email, #jobcode);
DELETE FROM #Updates
WHERE UserID = #UserID
END
GO
Additionally, as discussed in the comments, sending emails from a trigger is also not the best, but as this is what the question asks for it has been included. I would recommend alternative options for sending emails such as a queue which has been mentioned in other answers.

Upsert SQL query

I am looking for some advice how to optimize a couple of SQL stored procedures. With the 1st query I am doing insert, and with the 2nd one I am doing update, if the data exists.
What I want to do is merge both stored procedures in one, where the query will check if the data exists than update, else insert a new row.
Here is what I have at this time:
update SP:
ALTER PROCEDURE [dbo].[UpdateStep1](#UserId nvarchar(50), #First_Name nvarchar(50), #Last_Name nvarchar(50),
#TitlePosition nvarchar(30))
AS
BEGIN
UPDATE Company_Information
SET First_Name = #First_Name,
Last_Name = #Last_Name,
Title_Position=#TitlePosition,
WHERE UserId = #UserId
END
insert SP:
ALTER PROCEDURE [dbo].[InsertStep1](#UserId nvarchar(50), #First_Name nvarchar(50), #Last_Name nvarchar(50),
#TitlePosition nvarchar(30))
AS
BEGIN
INSERT INTO Company_Information(UserId,
First_Name,
Last_Name,
Title_Position)
VALUES
(#UserId,
#First_Name,
#Last_Name,
#TitlePosition)
END
So, I would like to merge both SP in one, and the SP to check if there is already data for that UserId than update, else insert a new row.
MERGE Statement?
CREATE PROCEDURE [dbo].[MERGEStep1](#UserId nvarchar(50), #First_Name nvarchar(50), #Last_Name nvarchar(50), #TitlePosition nvarchar(30))
AS
BEGIN
MERGE Company_Information WITH(HOLDLOCK) AS T
USING(SELECT 1 S) S
ON T.UserId = #UserId
WHEN MATCHED THEN UPDATE SET
First_Name = #First_Name,
Last_Name = #Last_Name,
Title_Position=#TitlePosition
WHEN NOT MATCHED THEN
INSERT (UserId, First_Name, Last_Name, Title_Position)
VALUES(#UserId, #First_Name,#Last_Name,#TitlePosition);
END
Follow these steps:
Create a variable to test it (ex: #id)
Select #id = UserId from Company_Information where UserId = #UserId
If #id = #userId update, otherwise insert
As #gbn specified, be aware of concurrence issues.