Multiple table SQL trigger an email to update - sql

Ok, much closer here for sure, but now it sends mass amounts of emails on one record update? I know I'm missing something here. First shot at cursors, so I'm guessing I've done something wrong in there? As always, Thanks for the help!!
create trigger eMailScheduleChange on dbo.BOOKINGS after update as
set nocount on;
Declare EmailCursor Cursor read_only for
select r.Name as RName
, c.Name as CName
, i.BookingTypeId
, i.Start
, i.Finish
, r.Email
from Wallchart.dbo.BOOKINGS b
inner join Wallchart.dbo.CUSTOMERS c on b.CustomerId = c.CustomerId
inner join Wallchart.dbo.RESOURCES r on b.ResourceId = r.ResourceId
inner join Inserted i on i.CustomerId = b.CustomerId
where i.BookingTypeId <> b.BookingTypeId
Declare #Email as varchar(50)
Declare #CName as varchar(100)
Declare #Start as datetime
Declare #RName as varchar (100)
Declare #Finish as datetime
Declare #body as varchar (255)
Declare #BookingTypeId as varchar (50)
open EmailCursor
Fetch next from EmailCursor
INTO #Email, #CName, #Start, #RName, #Finish, #BookingTypeId
While ##FETCH_STATUS=0
BEGIN
Set #body = '<Account cancelled>' + #CName + #Start + #Finish
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'SQLMail',
#recipients = #Email,
#subject = 'Account Update',
#body = #body
FETCH NEXT FROM EmailCursor INTO #Email, #CName, #Start, #RName, #Finish, #BookingTypeId
SET NOCOUNT OFF
END
Close EmailCursor
Deallocate EmailCursor

I think perhaps you have the impression that a query in a trigger is not allowed to read data from another table. This is not the case or triggers would be fairly useless. Here is a stub of how your trigger might look. I demonstrated how to pull the information you want. From there it is simply calling sp_send_dbmail.
create trigger MyTrigger on dbo.BOOKINGS for update as
set nocount on;
select r.Name
, c.Name
, i.BookingTypeId
, i.Start
, i.Finish
, r.Email
from Schedule.dbo.BOOKINGS b
inner join Wallchart.dbo.CUSTOMERS c on b.CustomerId = c.CustomerId
inner join Schedule.dbo.RESOURCES r on b.ResourceId = r.ResourceId
inner join Inserted i on i.SomeKeyValue = b.SomeKeyValue
where i.BookingTypeID <> b.BookingTypeID
--Now you can see how to capture all the information. All that is left is to call sp_send_dbmail
--https://msdn.microsoft.com/en-us/library/ms190307.aspx

SQL Server does not support direct mailing from the scripts. But you have other options.
First Create a table to capture the records that are being changed and also set a EmailFlag so that you can identify whether or not you have notified that user about the change.
The create a store procedure that will give you the list of all users that has undergone some changes but not notified by email (EmailFlag ="N" or something like that)
Now use the SSIS to create a send mail task to send the details email for all these users.
Once you have sent the email for each user, then you can update the EmailFlag as "Y" for that record. You can make the SSIS package a scheduled job in the SQL Server so that it will automatically run between certain interval.
You can use a for each loop container in SSIS so that you can send mail individually to each recipient
Please refer the following Links for more details
SSIS - How to configure a send mail task

Related

SQL Server : how to declare a variable in stored procedure

I need help for developing a stored procedure which has a select query joining tables from two different server.
Example:
CREATE PROCEDURE [dbo].[test proc]
DECLARE #customerid INT
SELECT OL.CUSTOMER_ID
FROM CUSTOMERS C
JOIN SERVER2.ORDER.ORDERLIST OL ON C.ID = OL.CUSTOMER_ID
WHERE OL.CUSTOMER_ID = #customerid
CUSTOMERS table is on Server1, CUSTOMER database
ORDERLIST table is on Server 2, ORDER database
They are linked servers.
This stored procedure will be in the Customer database on Server1.
Can I make server2 a variable? As I need the user to specify the server name and customerid when running the stored procedure. I need the stored procedure to be able to execute in production and test environment. Or how should I do it?
You need to use dynamic SQL:
create PROCEDURE [dbo].[test proc] (
#customerid int,
#server2 sysname -- or you can use nvarchar(255)
)
begin
declare #sql nvarchar(max);
set #sql = '
SELECT OL.CUSTOMER_ID
FROM CUSTOMERS C JOIN
#SERVER2.ORDER.ORDERLIST OL
ON C.ID = OL.CUSTOMER_ID
WHERE OL.CUSTOMER_ID = #customerid';
set #sql = replace(#sql '#SERVER2', #server2);
exec sp_executesql #sql,
N'#customerid int',
#customerid=#customerid;
end;
sp_executesql allows you to replace constant values in the dynamic SQL. However, you are not permitted to change identifiers, such as server names, which is why this uses replace().

T-SQL query result into variable

I am trying to assign the result of a SQL query into a variable but running into issues.
Here is my query:
use db
SELECT name
,ID
,pID
,pName
,group
FROM mName as m
INNER JOIN pID_Check AS p ON m.ID= p.pID
WHERE p.pName NOT LIKE m.name
Query works fine however I'm trying to schedule it to run hourly via SQL Server Agent and email on results.
Most of the time the query will not return any data but in the event there are rows I need it to email out.
Essentially need values of the row added to #results variable and email triggered only if #results not null
SET #sub = 'Subject'
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Profile',
#recipients = 'email#email.com;',
#body = #results,
#subject = #sub
You seem to want to send the result set to a mail recipient - while that is possible, I'm not aware of being possible to stick into the body in the method which you have attempted.
It can be included as an attachment via another property of SP_SEND_DBMAIL. Please see your example below, modified to include the result set as an attachment:
DECLARE #myquery varchar(max) = '
SELECT name
,ID
,pID
,pName
,group
FROM mName as m
INNER JOIN pID_Check AS p ON m.ID= p.pID
WHERE p.pName NOT LIKE m.name
'
SET #sub = 'Subject'
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Profile',
#recipients = 'email#email.com;',
#body = #results,
#subject = #sub,
#query = #myquery,
#attach_query_result_as_file = 1,
#query_attachment_filename= 'MyFileName.csv';
All SP_SEND_DBMAIL properties can be seen here: https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-send-dbmail-transact-sql

Stored procedure sends email even when condition is false. Any ideas?

When a user uses our site to process certain requests, upon exiting the site, an email with a link goes out to that user asking him/her to click the link to take a survey and share his/her user experiences with us.
Below is the stored procedure that I have written that does as described above.
ALTER proc [dbo].[SendSurvey]
AS
BEGIN
Declare #sender nvarchar(200)
declare #dept nvarchar(200) = ''
declare #loc nvarchar(200) = ''
declare #dteCreated nvarchar
declare #RequestID nvarchar(50) = ''
declare #authorizedname nvarchar(200) = ''
declare #email nvarchar(200) = ''
declare #message nvarchar(1000) = ''
declare #mailid int = 0
SET QUOTED_IDENTIFIER OFF;
SELECT
#email = email, #mailid=ID, #message = #message,
#RequestID = RequestID,
#authorizedname = SUBSTRING(submittedBy, CHARINDEX(',', submittedBy) + 1, LEN(submittedBy) - CHARINDEX(',', submittedBy) + 1)
+ ' ' + SUBSTRING(submittedBy, 1, CHARINDEX(',', submittedBy) - 1),
#loc = Bldg, #dtecreated = DateCreated, #dept = Department
FROM
Survey
WHERE
email = #email
AND Email IS NOT NULL OR Email != ''
AND (orderStatus != 1)
SELECT #message = 'This is a computer generated email message.
Please DO NOT use the REPLY button above to respond to this email.
Dear '+ #authorizedname +':
Thank you for using the order processing system.
Please click the link below to complete a survey
http://feedbacksurvey.php?rID=' +#RequestID+'&loc='+Replace(#loc,' ', '%20')+'&dept='+Replace(#dept,' ', '%20')+'
Regards,
web admin.'
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Customer Feedback Survey',
#recipients = #Email, -- your email
#subject = 'Customer Feedback Survey',
#body = #message;
UPDATE Survey
SET orderStatus = 1
WHERE orderStatus != 1 AND ID = #mailid
END
There are two problems with the stored procedure.
There is a column orderStatus which is a BIT data type with True (1) of false(0) value.
If the orderstatus is false, then send emails with records associated with it.
After sending the email, update orderstatus to true so the email doesn't get sent a second time.
This is not working. When I execute the stored procedure where all records on the table have orderstatus set to True, email still goes out.
the second problem that I am having is that the code is not sending out all records where orderStatus is True. It just sends email one at a time.
We would like emails to be send out for ALL records where orderstatus = 1 (True).
Any ideas what I am doing wrong?
You mixed AND with OR in your WHERE clause. The results will include all rows where Email != '', regardless of the other conditions.
Use parens to make this work:
WHERE email=#email
AND (Email IS NOT NULL or Email != '')
AND (orderStatus != 1)
As for why it's sending one email at a time, you are using your query to populate scalar variables.
SELECT #email = email...
Will result in #email being populated with one value, no matter how many rows the query returns.
the second problem that I am having is that the code is not sending out all records where orderStatus is True. It just sends email one at a time.
Yeah - that's how sp_send_dbmail works. You'll need a cursor to send > 1 email.
DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR
SELECT ....
FETCH NEXT FROM c INTO ....
WHILE ##FETCHSTATUS = 0 BEGIN
EXEC sp_send_dbmail ...
FETCH NEXT FROM c INTO ....
END
CLOSE c
DELLOCATE c

Get number of rows for tables across all databases

Searched a bit and didn't see anything that really fit my needs (although I don't really understand SQL beyond the basic select statement so maybe I just missed it). Took a SQL class in school many moons ago.
I have:
Virtual Windows Server 2008 R2 running SQL 2008 R2. I connect via MS SQL Server Management Studio
90-100 databases
All DB's have the same table structure (each one is a different client)
I want to:
Search all databases and return a list of all databases which have a table (TableName) that is larger than say, 5000 records.
Some sort of script that I can schedule that will use that list and if it finds a DB that the TableName table is more than 5000 records, will delete anything older than x amount of days (say 30). Any kind of logging to know what happened over the last few days would be a bonus.
Any help would be appreciated. Thank you.
EDIT/UPDATE (2/24/15): Hiren Dhaduk provided a nice stored procedure that works. Thanks!
You can use following store procedure in any database. Then run this store procedure by passing table name and NoOfRows(Minimum number of rows for table , In your case it will be 5000):
CREATE PROCEDURE usp_FindLargeTables
#TableName VARCHAR(256) , #NoOfRows int
AS
BEGIN
DECLARE #DBName VARCHAR(256)
DECLARE #varSQL VARCHAR(512)
DECLARE #getDBName CURSOR
SET #getDBName = CURSOR FOR
SELECT name
FROM sys.databases
WHERE state != 6
CREATE TABLE #TmpTable (TableName VARCHAR(256),
SchemaName VARCHAR(256),
DBName VARCHAR(256))
OPEN #getDBName
FETCH NEXT
FROM #getDBName INTO #DBName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #varSQL = 'USE ' + #DBName + ';
INSERT INTO #TmpTable
SELECT
sysobjects.Name as TableName
, sysindexes.Rows as NoOfRows , '''+ #DBName + ''' AS DBName
FROM
sysobjects
INNER JOIN sysindexes
ON sysobjects.id = sysindexes.id
WHERE
type = ''U''
AND sysindexes.IndId < 2
and sysobjects.Name = '''+ #TableName +''' and sysindexes.Rows > ' + CONVERT(VARCHAR, #NoOfRows) + ''
EXEC (#varSQL)
FETCH NEXT
FROM #getDBName INTO #DBName
END
CLOSE #getDBName
DEALLOCATE #getDBName
SELECT *
FROM #TmpTable
WHERE DBName != 'master'
-- STEP 2
DECLARE #DYNAMICQUERY VARCHAR(MAX)
SET #DYNAMICQUERY =
REPLACE((
SELECT 'DELETE FROM ['+ DBName +'].[dbo].['+ TableName +'] where Createdate < DATEADD(day, -30, GETDATE());'
FROM #TmpTable
WHERE DBName != 'master'
FOR XML PATH('')
), '<' , '<');
EXEC(#DYNAMICQUERY);
DROP TABLE #TmpTable
END
Example : usp_FindLargeTables 'DeltaStuds',5000
Clarification on your second point :
Unless you run a trace when the changes happen it is not possible.
So to do this i would suggest you to put one column called createdate in this table and then you will be able to delete record created before 30 days.
What you are asking is very custom to your setup...
You could use the following query to identify tables with row count greater than 5000
SELECT sc.name +'.'+ ta.name TableName
FROM sys.tables ta
INNER JOIN sys.partitions pa
ON pa.OBJECT_ID = ta.OBJECT_ID
INNER JOIN sys.schemas sc
ON ta.schema_id = sc.schema_id
WHERE ta.is_ms_shipped = 0 AND pa.index_id IN (1,0)
and ta.name = 'DeltaStuds'
GROUP BY sc.name,ta.name
having SUM(pa.rows)>5000
But you would need something like this link to run it for all DBs. How to find column names for all tables in all databases in SQL Server. I dont know of a easier way...
For the second part, you will have to setup a delete process that takes the resultset from the above query, creates a dynamic query and deletes rows based on your date field. In that process, you could inject an audit mechanism that logs that delete action, row count, datetime, etc...

SQL Server 2005 Database Mail Failures

I am running database mail on a SQL 2005 box. Occasionally mails fail to send, by quering the msdb.dbo.sysmail_mailitems table i can see there are items with a sent_status of "2", which is failed. I can query the sysmail_faileditems table to list all failed mails.
Is there anyway i can process/re-send these failed mail's?
Would it be reasonable to create a daily job to query this table looping through using a CURSOR to re-send the mails one by one, and then delete them from the table one by one.
If you have a better suggestion / ideas then please let me know.
Many thanks Karl
First up, i suggest you query faileditems to determine your main cause of failure:
SELECT items.subject ,
items.last_mod_date ,
l.description
FROM dbo.sysmail_faileditems AS items
INNER JOIN dbo.sysmail_event_log AS l ON items.mailitem_id = l.mailitem_id
If it's nothing that can be easily fixed, you can re-send them by looping through the sysmail_mailitems table and re-sending them based on the failure type (timeouts etc) in the faileditems log - some good examples in the suggestions of this blog: http://justgeeks.blogspot.co.uk/2007/05/resending-sysmail-emails.html
My personal favourite:
CREATE PROCEDURE sysmail_resend_timeout
AS
BEGIN
SET NOCOUNT ON
DECLARE SYSMAIL_LOG_RESEND_CURSOR CURSOR READ_ONLY
FOR
SELECT DISTINCT
l.mailitem_id ,
p.name ,
m.recipients ,
m.subject ,
m.body_format ,
m.body
FROM msdb.dbo.sysmail_log l WITH ( NOLOCK )
JOIN msdb.dbo.sysmail_mailitems m WITH ( NOLOCK ) ON m.mailitem_id = l.mailitem_id
JOIN msdb.dbo.sysmail_profile p WITH ( NOLOCK ) ON p.profile_id = m.profile_id
WHERE l.event_type = 3
AND m.sent_status = 2
AND l.description LIKE '%The operation has timed out%'
ORDER BY l.mailitem_id
OPEN SYSMAIL_LOG_RESEND_CURSOR
WHILE ( 1 = 1 )
BEGIN
DECLARE #mailitem_id INT ,
#profile_name NVARCHAR(128) ,
#recipients VARCHAR(MAX) ,
#subject NVARCHAR(255) ,
#body_format VARCHAR(20) ,
#body NVARCHAR(MAX)
FETCH NEXT FROM SYSMAIL_LOG_RESEND_CURSOR INTO #mailitem_id, #profile_name, #recipients, #subject, #body_format, #body
IF NOT ##FETCH_STATUS = 0
BEGIN
BREAK
END
PRINT CONVERT(VARCHAR, GETDATE(), 121) + CHAR(9) + CONVERT(VARCHAR, #mailitem_id) + CHAR(9) + #recipients
EXEC msdb.dbo.sp_send_dbmail
#profile_name = #profile_name ,
#recipients = #recipients ,
#subject = #subject ,
#body_format = #body_format ,
#body = #body
UPDATE msdb.dbo.sysmail_mailitems
SET sent_status = 3
WHERE mailitem_id = #mailitem_id
END
CLOSE SYSMAIL_LOG_RESEND_CURSOR
DEALLOCATE SYSMAIL_LOG_RESEND_CURSOR
END
GO
I know it's not really the answer you want to hear, but I always try and decouple the mail feature. I might use a trigger to spawn an external process if the mail sending needs to be timely, but I let the external script do the actual job of sending the mail. That way transient connection errors are taken care of by the MTA, and I don't have to worry about special book-keeping algorithms.