I get a form filled from the user through browser..
I enter the values in a database
I want to call a script on the entry made in the database
(Do not want to call it from browser as browser has timeout issues. This script can take 10 hrs if it needs to)
Looks like you need a trigger:
A trigger is a special kind of stored procedure that automatically executes when an event occurs in the database server.
Example:
USE AdventureWorks2012;
GO
IF OBJECT_ID ('Sales.reminder2','TR') IS NOT NULL
DROP TRIGGER Sales.reminder2;
GO
CREATE TRIGGER reminder2
ON Sales.Customer
AFTER INSERT, UPDATE, DELETE
AS
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'AdventureWorks2012 Administrator',
#recipients = 'danw#Adventure-Works.com',
#body = 'Don''t forget to print a report for the sales force.',
#subject = 'Reminder';
GO
CREATE TRIGGER (Transact-SQL)
Related
I'm looking for a way to send emails (or reminders/confirmations) to users who create a new record in a web application, which is then inserted into a table. Basically a dynamic way of sending emails.
I've been reading online about Triggers and DB mail and there seems to be alot of disadvantages going with this approach.
Does anyone have any recommendations on the best way to achieve this?
Flow: New Record Inserted into DB Table ->>> At this point the email address of the user who created the record in the application should receive a mail (basically a confirmation mail).
What I've tried already:
DB mail is already configured and working.
I've made the below Trigger (very basic) but from reading online using a trigger in this way will lead to load/performance issues of my DB.
But I'm on unsure on how to generate the emails and last record inserted.
CREATE TRIGGER [dbo].[INSERT_Trigger] ON [dbo].[Entity]
FOR INSERT
AS
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'DBMail',
#recipients = 'admni#domain.com', ####Here I would need dyncamic emails based on what user enters the new record which are stored in the same table
#query = 'Select Description From Entity where 'Last inserted record'####Here I would need the last record for that user entered
#subject = 'Subject' ,
#Body = 'Message Body',
#importance = 'High',
IMHO this approach is a design flaw: the DB tier is something that should be one of the leaves of the tier tree. The fact that the MS SQL Server is actually an application server and has support for such things is a legacy, but I don't think that it should be used.
At first look:
you might need to switch to another RDBMS
your production environment might not support SMTP for any reason
your attempt to send the mail could fail for various reasons - resulting in user not being notified and never trying it again
Yes, indeed, you can use SQL Server even as a message bus but would not be an efficient one. This concept is actually dispatching events of "notification needed" kind. The event is implemented as insert and the trigger is the consumer. But the event is produced inside your application, in a higher tier. Why not reacting to it there? Or, use the database only as a queue: store the details there, but process them in a way where you have more control.
You have not told us about the application you are creating, but I would create a separate background task (the implementation could vary depending on the application design - can be an OS-level scheduled task, windows service, or a background worker in your application) that checks periodically for mails not yet sent, and tries to send them, storing the result in the record. Things might get of course more complicated depending on load. But this way you can retry, and you are certainly taking load of the DB server, but at least you have the possibility to do so.
I have a trigger that works as u asked
First insert records in temporary table from inserted table
Second, declare parameters that you need
Third, add a cursor in your trigger and get parameters that u need in cursor from temporary table
Inside trigger you can declare recipients as u needed to be, query, and other stuffs
CREATE TRIGGER Trigger ON [Entity]
FOR INSERT
not for replication
AS
select ins.* into #temp from inserted ins
declare #Param1 integer, #Param2 integer
declare cursor forward only for
select Col1, Col2 from #temp order by Col1
open cursor
fetch next from cursor into #Param1, #Param2
while ##fetch status = 0
begin
declare #recipients varchar (max), #query varchar(max)
select #recipient = Col1 -- or whatever col contains the recipient address
from #temp
where Col1 = #Param1
select #query = description
from #temp
where Col2 = #Param2 -- or whatever condition give u the description for parameter
exec sp_send_dbmail
#profile_name = 'profile',
#recipients = #recipient
#subject = 'Subject' ,
#Body = #query,
#importance = 'High'
fetch next from cursor into #Param1, #Param2
end
close cursor
deallocate cursor
drop table #temp
--- note that the body can be formatted in html format, like bellow
declare #body varchar(max)
select #body = '<html><body>'
select #body = #body+'Hello'+'<b><b>'
select #body = #body+'Here is the description:'+#query+'<b><b>'
select #body = #body+'Regards'
select #body = #body+'</body></html>'
i have a table in SQl server which occasionally gets data from a linked server, and than i have to do activities on it .
but the problem is there is no way to check if the data is inserted in table (table is always truncated after performing the activity so next time when data is pushed table is already empty) i manually check daily for data if it is inserted or not .
what i want is to get auto alert on my email (i already have db_mail configured and working) whenever the data is pushed in a table .
i have sa admin and complete privileges on Database and also on Windows server 2012 R2
You can do this with a trigger but you will have to do some preparations with privileges so the executor (the login that's inserting the records on your tracking table) can send email correctly:
CREATE TRIGGER dbo.TrackingTableNameAfterInsert ON TrackingTable
AFTER INSERT
AS
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'YourConfiguredProfile',
#recipients = 'youremail#mail.com',
#subject = 'Records were inserted on TrackingTable',
#body = ''
END
You might want to encapsulate the email sending on an SP and configure it's permissions there.
In regards to the following:
...table is always truncated after performing the activity so next time
when data is pushed table is already empty...
You can create a historical table and use a trigger to also insert inserted records on this table, so the TRUNCATE or DROP of the original one won't affect the copied records.
CREATE TABLE TrackingTableMirror (
/*Same columns and types*/
InsertedDate DATETIME DEFAULT GETDATE())
GO
CREATE TRIGGER dbo.TrackingTableInsertMirror ON TrackingTable
AFTER INSERT
AS
BEGIN
INSERT INTO TrackingTableMirror (
/*Column list*/)
SELECT
/*Column list*/
FROM
inserted AS I
END
This way you can check all records on this mirrored table and not the volatile one (and avoid all the email sending).
1) Create Profile and Account
You need to create a profile and account using the Configure Database Mail Wizard which can be accessed from the Configure Database Mail context menu of the Database Mail node in Management Node. This wizard is used to manage accounts, profiles, and Database Mail global settings.
2) Run Query
sp_CONFIGURE 'show advanced', 1
GO
RECONFIGURE
GO
sp_CONFIGURE 'Database Mail XPs', 1
GO
RECONFIGURE
GO
3)
USE msdb
GO
EXEC sp_send_dbmail #profile_name='yourprofilename',
#recipients='test#Example.com',
#subject='Test message',
#body='This is the body of the test message.
Congrates Database Mail Received By you Successfully.'
through the table
DECLARE #email_id NVARCHAR(450), #id BIGINT, #max_id BIGINT, #query NVARCHAR(1000)
SELECT #id=MIN(id), #max_id=MAX(id) FROM [email_adresses]
WHILE #id<=#max_id
BEGIN
SELECT #email_id=email_id
FROM [email_adresses]
set #query='sp_send_dbmail #profile_name=''yourprofilename'',
#recipients='''+#email_id+''',
#subject=''Test message'',
#body=''This is the body of the test message.
Congrates Database Mail Received By you Successfully.'''
EXEC #query
SELECT #id=MIN(id) FROM [email_adresses] where id>#id
END
4) Trigger Code
CREATE TRIGGER [dbo].[Customer_INSERT_Notification]
ON [dbo].[Customers]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CustomerId INT
SELECT #CustomerId = INSERTED.CustomerId
FROM INSERTED
declare #body varchar(500) = 'Customer with ID: ' + CAST(#CustomerId AS VARCHAR(5)) + ' inserted.'
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Email_Profile'
,#recipients = 'recipient#gmail.com'
,#subject = 'New Customer Record'
,#body = #body
,#importance ='HIGH'
END
I refer this link.
If I make a change to MyTable on MyDatabase, I want it to delete the contents of TestTable on TestDatabase on MYLINKEDSERVER and reinsert everything from scratch. To do this, I use a trigger on MyTable. I know this is inefficient but the number of records is less than 10.
In case the linked server is down, I want it to still commit the changes locally to MyTable and just send an email alert stating that the linkedserver could not be updated. I am failing to get the local transaction to commit when the linkedserver is unavailable... I've tried messing with XACT_ABORT but I just get different errors.
What am I doing wrong?
CREATE TRIGGER trig_updatelinkedserver ON MyDatabase.dbo.MyTable
FOR INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE #linked_server SYSNAME = 'MYLINKEDSERVER'
, #tablename SYSNAME --name of the table calling the trigger so we can send error details in alert eamil
SELECT #tablename = OBJECT_NAME(parent_object_id)
FROM sys.objects
WHERE sys.objects.name = OBJECT_NAME(##PROCID)
BEGIN TRY
--If linkedserver fails to connect, we do not want the remaining code in the block to run
--but we do want the original transaction that triggered this to complete.
EXEC sp_testlinkedserver #servername = #linked_server
DELETE FROM MYLINKEDSERVER.TestDatabase.dbo.TestTable
INSERT INTO MYLINKEDSERVER.TestDatabase.dbo.TestTable
SELECT *
FROM MyDatabase.dbo.MyTable
END TRY
BEGIN CATCH
DECLARE #subj VARCHAR(1000) = 'TRIGGER FAILURE: ' + #tablename + ': Could not locate linkedserver ' + #linked_server
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'foo#bar.com'
, #subject = #subj
, #body = ''
, #body_format = 'HTML'
, #profile_name = 'MyEmailProfile'
END CATCH
END
Error handling in tsql is complicated and inconsistent. What are you doing wrong? Making assumptions. Here is what Erland says in his lengthy discussion about the topic:
What is important to understand about triggers is that they are part of the command that fired the trigger, and in a trigger you are always in a transaction, even if you did not use BEGIN TRANSACTION. Sometimes I see people in SQL Server forums ask if they can write a trigger that does not roll back the command that fired the trigger if the trigger fails. The answer is that there is no way that you can do this reliably, so you better not even try. If you have this type of requirement, you should probably not use a trigger at all, but use some other solution.
So take that last sentence to heart. Note that the link takes you into the middle of the discussion. And there are links at the end of the page that continue to related topics - one of which is about linked servers.
Good day,
I am new to SQL and creating triggers and I have read up on it and just created a test trigger so as to gain a understanding of how to create one.
I wrote the following Test Trigger and when debugging it, it went through successfully.
USE [Martin_test]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[Test_to_write_a_trigger]
ON [dbo].[_rtblAgents]
AFTER DELETE, INSERT, UPDATE
AS
BEGIN
EXEC msdb.dbo.sp_send_dbmail
exec msdb.dbo.sp_send_dbmail
#profile_name = 'ChamgeContactMail',
#recipients = 'martin#aboutit.co.za',
#body = 'Someone changed the customer',
#subject = 'Change Customer';
END
However, when I make a change in the specified table, I get the following error.
The SQL Server is residing on my local machine.
Please could someone assist me.
I have an INSERT trigger on a table that simply executes a job.
Example:
CREATE TABLE test
(
RunDate smalldatetime
)
CREATE TRIGGER StartJob ON test
AFTER INSERT
AS
EXEC msdb.dbo.sp_start_job 'TestJob'
When I insert a record to this table, the job is fired of without any issue. There are a few people, however, that have lower permissions than I do (db_datareader/db_datawriter on the database only); they are able to insert a record to the table, but the trigger does not fire.
I am a SQL Server novice and I was under the impression that users did not need elevated permissions to fire off a trigger (I thought that was one of the big benefits!). Is this a permission issue at the trigger level, or at the job level? What can I do to get around this limitation?
The trigger will execute in the context of the caller, which may or may not have the permissions to access msdb. That seems to be your problem. There are a few ways to extend these permissions using Execute As; they are greatly detailed in this link
Use impersonation within trigger:
CREATE TRIGGER StartJob ON test
with execute as owner
AFTER INSERT
AS
EXEC msdb.dbo.sp_start_job 'TestJob'
And set database to trustworthy (or read about signing in above link):
alter database TestDB set trustworthy on
Another way to go (depending on what operations the agent job performs) would be to leverage a Service Broker queue to handle the stored procedure activation. Your users' context would simply call to Send On the queue while, in an asynchronous process SvcBroker would activate a stored procedure which executed in context of higher elevated user. I would opt for this solution rather than relying on a trigger calling an agent job.
I wanted to test the call to Service Broker, so I wrote this simple test example. Instead of calling an SSIS package I simply send an email, but it is very similar to your situation. Notice I use SET TRUSTWORTHY ON at the top of the script. Please read about the implications of this setting.
To run this sample you will need to substitute your email profile info below, <your_email_address_here>, etc.
use Master;
go
if exists(select * from sys.databases where name = 'TestDB')
drop database TestDB;
create database TestDB;
go
alter database TestDB set ENABLE_BROKER;
go
alter database TestDB set TRUSTWORTHY ON;
use TestDB;
go
------------------------------------------------------------------------------------
-- create procedure that will be called by svc broker
------------------------------------------------------------------------------------
create procedure dbo.usp_SSISCaller
as
set nocount on;
declare #dlgid uniqueidentifier;
begin try
-- * figure out how to start SSIS package from here
-- for now, just send an email to illustrate the async callback
;receive top(1)
#dlgid = conversation_handle
from SSISCallerQueue;
if ##rowcount = 0
begin
return;
end
end conversation #dlgid;
exec msdb.dbo.sp_send_dbmail
#profile_name = '<your_profile_here>',
#importance = 'NORMAL',
#sensitivity = 'NORMAL',
#recipients = '<your_email_address_here>',
#copy_recipients = '',
#blind_copy_recipients = '',
#subject = 'test from ssis caller',
#body = 'testing',
#body_format = 'TEXT';
return 0;
end try
begin catch
declare #msg varchar(max);
select #msg = error_message();
raiserror(#msg, 16, 1);
return -1;
end catch;
go
------------------------------------------------------------------------------------
-- setup svcbroker objects
------------------------------------------------------------------------------------
create contract [//SSISCallerContract]
([http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer] sent by initiator)
create queue SSISCallerQueue
with status = on,
activation (
procedure_name = usp_SSISCaller,
max_queue_readers = 1,
execute as 'dbo' );
create service [//SSISCallerService]
authorization dbo
on queue SSISCallerQueue ([//SSISCallerContract]);
go
return;
-- usage
/*
-- put a row into the queue to trigger the call to usp_SSISCaller
begin transaction;
declare #dlgId uniqueidentifier;
begin dialog conversation #dlgId
from service [//SSISCallerService]
to service '//SSISCallerService',
'CURRENT DATABASE'
on contract [//SSISCallerContract]
with encryption = off;
begin conversation timer (#dlgId)
TIMEOUT = 5; -- seconds
commit transaction;
*/
It would be permissions at the job level. You can possibly assign those users the SQLAgentReaderRole in MSDB to be able to start a job, considering that they would be added to a group that owned the job. If they are not in a group which owns the job, it gets more difficult.