I'm trying to create a trigger that sends an email ONLY when there's an insert. I currently get an email whether there's an insert or not and I WANT to get an email ONLY when there's an insert in the table. Here's how the trigger currently looks like
ALTER TRIGGER [dbo].[myTriggerName]
ON [dbo].[myTableName]
AFTER INSERT
AS
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'MyRecipients',
#profile_name = 'DBAdmins',
#subject = 'MySubject',
#body = 'Body';
END
I would be extremely careful to put extensive processing or things like sending e-mails directly into a trigger.
The trigger executes in the context of the calling transaction, and thus delays the completion of that transaction until it is done.
If you have external dependencies (like a SMTP server), you can quickly get into situations where you have timeouts etc.
A trigger should be extremely nimble, small and fast.
My recommendation would be:
make a note into a separate table EmailToSend with all the necessary information you need (recipient, subject, body, date stored)
CREATE TRIGGER trgYourTableInsert
ON dbo.YourTable
AFTER INSERT
AS
INSERT INTO dbo.EmailToSend(Recipient, Subject, Body)
VALUES('john.doe#acme.org', 'Hello there', '.......')
have a separate process (e.g. a scheduled stored procedure that runs once every hour) checking that table and doing the actual sending of the e-mails (without blocking any other processes / transactions) - something along the lines of:
SELECT (list of columns)
FROM dbo.EmailToSend
WHERE DateSent IS NULL
or something like that - it really heavily depends on what exactly you're putting into that table and how you want to handle this ....
CREATE TRIGGER dbo.trg_I_tbl
ON dbo.tbl
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #text NVARCHAR(MAX)
SELECT #text = STUFF((
SELECT ', ' + col
FROM INSERTED
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'your_account#gmail.com',
#subject = 'caption',
#body = #text
END
GO
Related
Can someone please give an example of when we need to use a cursor in SQL that cannot be solved with a set based approaches.
Thanks
A cursor is often used when you need to do an action on a per row basis. Which is something we'd often relegate to other tools outside of the DBMS. In general the strength of the DBMS lies in set based approaches for data. However.. to give an example.
Say you have a table where some other process writes mail messages to be stored and sent at a later date. Perhaps multiple SQL jobs running and each writes their own status, then when next morning comes or the server load is very low, the DBMS is expected to send these on its own.
Setting up an example table with some data:
CREATE TABLE outgoingMessages
(
recipient VARCHAR(MAX),
subject NVARCHAR(255),
message NVARCHAR(MAX)
)
INSERT INTO dbo.outgoingMessages (recipient,subject,message)
VALUES
('foo#bar.com', N'An email', N'Procedure dbo.Foo ran with statuscode X'),
('foo#bar.com', N'An email', N'Procedure dbo.Bar ran with statuscode Y'),
('manager#bar.com', N'An email', N'Data synchronisation had problems, ask foo')
Then as a theoretical end step/end job, we have a process that goes over the table and handles all built up messages.
/* Scheduled job */
DECLARE mailCursor CURSOR FOR
SELECT * FROM dbo.outgoingMessages;
DECLARE #mailRecipient VARCHAR(MAX);
DECLARE #mailSubject NVARCHAR(255);
DECLARE #mailMessage NVARCHAR(MAX);
OPEN mailCursor;
FETCH NEXT FROM mailCursor INTO
#mailRecipient, #mailSubject, #mailMessage
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #mailRecipient + ' ' + #mailSubject + ' ' + #mailMessage
EXEC msdb.dbo.sp_send_dbmail
#profile_name = N'defaultMailprofile',
#recipients = #mailRecipient,
#subject = #mailSubject,
#body = #mailMessage
FETCH NEXT FROM mailCursor INTO
#mailRecipient, #mailSubject, #mailMessage
END
CLOSE mailCursor;
DEALLOCATE mailCursor;
This makes the database print out all lines individually and send a mail to the specified variables (calls another stored procedure for it) for each line in the table. I would say this line of operation, taking data for each row and manipulating it further or using it as variables for another procedure is a more common usecase.
/* Print results */
foo#bar.com An email Procedure dbo.Foo ran with statuscode X
foo#bar.com An email Procedure dbo.Bar ran with statuscode Y
manager#bar.com An email Data synchronisation had problems, ask foo
You can think of a table with perhaps built up API calls by other automated processes, then to be executed at a later date.
Are cursors common? No. You should always consider their usecases and ideally use a different approach. But if you need to do something for each line, and potentially jump backwards based on conditions. Cursors allow you to do so inside the DBMS and they're a powerful tool.
When I run an interface with name 'personimport' it will generate a new record in the table (run history).
There are 5 columns in the table:
interface-id, interface name, date, personid, error msg
Inserting "1, qwerty, 2019-09-11, a1" is successful, but inserting "2, person import, 2019-09-12, a2" throws an error .
Whenever a new record is added into this table with the name 'person import' I want to send an email to set of employees.
How to write a trigger for this in SQL Server?
You need to have database mail configured:
https://learn.microsoft.com/en-us/sql/relational-databases/database-mail/configure-database-mail?view=sql-server-2017
You need to create a trigger:
From SSMS goto the table and expand
Right click triggers, and "New Trigger"
Name your trigger, on table name, and after insert
CREATE TRIGGER SendTheEmails
ON runhistory
AFTER insert
AS
BEGIN
-- Insert statements for trigger here
END
GO
https://learn.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-2017
Then for the SQL to email:
Declare #message varchar(max)
Declare #subjectline varchar(60)
set #subjectline = 'Your Subject'
set #message = 'Your Message body'
EXEC msdb.dbo.sp_send_dbmail #profile_name = 'dbmailProfileName'
, #recipients = 'youremails#yourdomain'
, #subject = #subjectline
, #body_format = 'html'
, #body = #message
Obviously you can declare other variables, and use the insert table for further information.
Yeah, and as Fillburt said, this seems like a duplicate of Send e-mail from a trigger
One of my customer's website had been getting injection attack (inserting ads in emails) and at that time I didn't know how to handle the attack so I set trigger on update and delete to send me an email so I would know ads were added to table. However, I did not receive any email when values got changed.
ALTER TRIGGER [dbo].[TRIG_DEL]
ON [dbo].[A]
INSTEAD OF DELETE
AS
BEGIN
DECLARE #str VARCHAR(100)
SET #str = CONCAT('Someone is trying to delete A table at ', CURRENT_TIMESTAMP)
EXEC [msdb].[dbo].sp_send_dbmail
#profile_name = 'A_Email',
#recipients = 'abc#def.com;',
#subject = 'UPDATE ALERT',
#body = #str
END
This is my trigger and it works fine when I test it. Is there any way to avoid trigger? I just want to know how the attacker avoided the trigger, out of curiosity.
I'm trying to learn SQL triggers to automatically handle events in my database but I'm having some problems with execution.
If I run the following code:
declare #userid numeric(18,0);
declare #username nvarchar(max);
set #userid = 400
execute GetUserNameFromID #userid,#username output
select #username
which calls the following stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE GetUserNameFromID
-- Add the parameters for the stored procedure here
#UserID numeric(18,0),
#UserName nvarchar(MAX) OUT
AS
BEGIN
SET NOCOUNT ON;
SELECT #UserName = u.name from Users u where ID=#UserID
END
GO
I get a nice result 'sometestuser'
But when calling it from my trigger it fails to return a value from the stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER Trigger [dbo].[CheckIfUserHasNoItemsLeft] on [dbo].[Items] for update
As
Begin
set nocount on
declare #inactive_user nvarchar(50);
declare #userid numeric(18,0);
declare #username nvarchar(MAX);
if ( select Count(*) from inserted ) > 1 RaIsError( 'CheckIfIserHasNoItemsLeft: No more than one row may be processed.', 25, 42 ) with log
if update(InactiveUser)
set #inactive_user = (select InactiveUser from inserted)
if #inactive_user is not null
set #userid = (select CID from inserted)
execute GetuserNameFromID #userid,#username output
if #username is not null
insert into tasks (Task) values ('The last item for ' + #username + ' has been marked inactive, check if this user should now be also marked inactive.')
End
InactiveUser is the name of the app user who has marked this item inactive, it is what I am using as a check to see if the item has been set inactive rather than create an additional boolean column just for this purpose.
I'm sure it's something simple but information on If...Then statements for SQL seems to be limited and a lot of answers suggest using Case but the query editor gives me errors about incorrect syntax no matter which way I try to do it that way.
As I'm learning I'm more than happy for someone to show me a completely new way of handling this if what I've done is wrong or bad design. I'm hoping to create a set of triggers that will add items to the tasks table for administrators to check when user accounts appear to be stale and other maintenance checks etc.
I am using SQL server 2005.
Thank you.
Edit: Changed 'value <> null' to 'value is not null'
Edit2: Added HABO's suggestion to throw an error if more than one row is detected.
How about we take a whole new approach to this. Processes like this are exactly why the inline table valued functions were created.
Let's start by converting your stored procedure to an inline table valued function.
CREATE FUNCTION GetUserNameFromID
(
#UserID numeric(18,0)
) RETURNS TABLE
AS RETURN
SELECT u.name
from Users u
where ID = #UserID
GO
That is a LOT simpler and cleaner than that stored procedure with an output variable.
Here is where it really starts to make a difference. Here is what you could do with that trigger using the newly created iTVF.
ALTER Trigger [dbo].[CheckIfUserHasNoItemsLeft] on [dbo].[Items] for update
As Begin
set nocount on
if update(InactiveUser)
insert into tasks (Task)
select 'The last item for ' + u.name + ' has been marked inactive, check if this user should now be also marked inactive.'
from inserted i
cross apply dbo.GetUserNameFromID(i.CID) u
end
This is super simple AND it is fully set based so if you update 1 or 1,000 rows it will work correctly.
I am using one class file for updating my tables. In that I am either inserting or updating tables and after each update or insert, I am calling one stored procedure to save the last updated ID of the table. But once this stored procedure runs it never releases the resource. It is executing always in background. Why is this happening and how can I stop it?
Here is the stored procedure:-
Create procedure [dbo].[Updlastusedkey]
(
#tablename varchar(50)
)
as
Begin
DECLARE #sql varchar(300)
SET #SQL='UPDATE primarykeyTab SET lastKeyUsed = ISNULL(( SELECT Max(ID) from '+#tablename +'),1) WHERE Tablename='''+#tablename +''''
print #SQL
EXEC(#SQL)
END
Do you have Auto-Commit turned on? I think implicit_transactions = OFF means Auto Commit = ON in SQL Server. If not your Update operation may not be executing a COMMIT for the transaction it opened so leaving a write lock on the table. Alternatively just explicitly COMMIT your update perhaps.
Why don't you just create a view?
CREATE VIEW dbo.vPrimaryKeyTab
AS
SELECT tablename = 'table1', MAX(id_column) FROM table1
UNION
SELECT tablename = 'table2', MAX(id_column) FROM table2
/* ... */
;
Now you don't need to update anything or run anything in the background, and the view is always going to be up to date (it won't be the fastest query in the world, but at least you only pay that cost when you need that information, rather than constantly keeping it up to date).
Try this -
UPDATE primarykeyTab SET lastKeyUsed = ISNULL(( SELECT Max(ID) from '+#tablename
+' WITH (NOLOCK)),1) WHERE Tablename='''+#tablename +'''' WITH (NOLOCK)