Email job scheduler query for generating daily report - sql-server

i have a job scheduler that is suppose to send a daily collection report to 20 different id's based on 20 different divisions. Each division will receive 1 report of that particular division only. This is the query i have come up with .
DECLARE #xml NVARCHAR(MAX)
DECLARE #body NVARCHAR(MAX)
SET #xml = CAST((select tm.name as 'td','',h.name as 'td','',h.account_number AS 'td','',
SUM(bc.total_amount) AS 'td'
FROM MJP.dbo.tbl_bank_collection bc,
MJP.dbo.tbl_div_type_master tm,
MJP.dbo.tbl_div_header h
where bc.type_id = tm.id
and bc.header_id = h.id
and bc.transaction_date = '06-12-2012'
and bc.div_id in ( select d.id
from tbl_div d, tbl_bank_collection bc
where bc.div_id = d.id
group by d.id)
group by tm.name, h.name,h.account_number with rollup
having grouping(h.name) = 1 or
GROUPING(h.account_number) = 0
FOR XML PATH('tr'), ELEMENTS ) AS NVARCHAR(MAX))
SET #body ='<html><i>Collection Report</i>
<body bgcolor=red><table border = 1><tr><th>Type Name
<th>Header Name</th><th>Account Number</th>
<th>Total Amount</th></tr>'
SET #body = #body + #xml +'</table></body></html>'
EXEC msdb.dbo.sp_send_dbmail
#profile_name='alkesh mail',
#body_format ='HTML',
#recipients='id.no1#yahoo.com;id.no2#yahoo.com',
#subject='Daily Report',
#body=#body
----------------------------------------------------------------------------------------
Now i want to split the report after a particular divison's final sum amount is calculated and the next report should be generated for the next division' id.
Any suggestions or clarifications !!

I am assuming business politics has you constrained within SQL Server, because concatenating XML email reports is not what SQL Server is designed for.
Personally I would have a service executable (Windows Service, or just a Console Application called by task scheduler, whatever.) with code (e.g. C#) that:
Gets just the data form SQL Server, passing a divisionId paramter to get the report for just that division.
Open an XML template file and insert the data.
Request a list of users for that division from SQL Server, and email each person in the list.
Repeat for each division.
Having said all that you could do this with just stored procedures, using loops. You could use a cursor (yuck) but I always prefer these constructs: (NB. Looping in RDBMS's is something to be avoided really, it very much depends on your particular circumstances)
declare #divisionId int = (select min(divisionId) from tbDivisions)
declare #userId int = ( min(userId) from users where divisionId = #divisionId )
declare #xml nvarchar(max)
while divisionId is not null
begin
exec your_report_stored_procedure #division = #divisionId, output #xml = #xmlOut
while #userId is not null
begin
exec your_email_tored_procedure #userId = #userId, #xmlIn = #xml
select #userId = min(userId) from users where divisionId = #divisionId and userId > #userId
end
select #divisionId = min(divisionId) from tbDivisions where divisionId > #divisionId
end

Related

I am searching for a loop query over multiple databases and insert result into existing table in one database to collect al data

I am searching for a loop query over multiple databases and insert result into existing table in one database to collect al data.
There are 28 existing databases at the moment but when i start the query below it says table already exists at the second database.
when this works i want to loop a much larger query then this.
I also tried executing and union all but if a new database is added it must be collected autmatically.
See example i've tried below:
--drop table if exists [hulptabellen].dbo.HIdatabases
declare #dbList table (dbName varchar(128), indx int)
insert into #dbList
select dbName = dbname, row_number() over (order by dbname)
from [hulptabellen].dbo.HIdatabases
--declare variables for use in the while loop
declare #index int = 1
declare #totalDBs int = (select count(*) from #dbList)
declare #currentDB varchar(128)
declare #cmd varchar(300)
--define the command which will be used on each database.
declare #cmdTemplate varchar(300) =
'
use {dbName};
select * insert into [hulptabellen].dbo.cladrloc from {dbname}.dbo.cladrloc
'
--loop through each database and execute the command
while #index <= #totalDBs
begin
set #currentDB = (select dbName from #dbList where indx = #index)
set #cmd = replace(#cmdTemplate, '{dbName}', #currentDB)
execute(#cmd)
set #index += 1
end
Create the table outside your loop and insert into the table this way:
INSERT INTO [hulptabellen].dbo.cladrloc (col1,col2)
SELECT col1,col2
FROM {dbname}.dbo.cladrloc
FYI: When you use the following syntax, a new table is created, so it can be executed only once.
SELECT *
INTO [hulptabellen].dbo.cladrloc
FROM {dbname}.dbo.cladrloc

Email data from several tables which is joined with inserted table

I have four tables:
Activity:
ActivityID(PK) ActivityName CustomerID(FK) UserId(FK)
1 Lead Gen 1st 50 U1
2 Lead Gen 2nd 60 U2
Customer:
CustomerID(PK) CustomerNumber CustomerName
50 C0150 cust50 ltd
60 C0160 cust60 ltd
User:
UserID(PK) UserName Email
U1 Mr. X X#cat.com
U2 Mr. Y Y#cat.com
UserActivity:
UserActivityID(PK) UserID(FK) ActivityID(FK)
888 U1 1
889 U2 2
I want to send an email (i.e. Email:X#cat.com) to the users related to the activity (i.e. ActivityId:1) if any insert happens in Activity Table (SQL Server 2008-R2).
The email body should contain the ActivityId, ActivityName, CustomerNumber and CustomerName.
The trigger has to do the above mentioned and the result should be like this in the email:
ActivityID:1, ActivityName:Lead Gen 1st created for CustomerNumber: C0150 & CustomerName: cust50 ltd
Here is my code:
CREATE TRIGGER [dbo].[Activity_Insert_Mail_Notification]
ON [dbo].[Activity]
AFTER INSERT
AS
BEGIN
DECLARE #ActivityID varchar(2000)
DECLARE #ActivityName varchar (2000)
Select #ActivityID=inserted.ActivityID,#ActivityName=inserted.ActivityName
From inserted
DECLARE #CustomerNo varchar(2000)
DECLARE #CustomerName varchar(2000)
Select #CustomerNo = B.[CustomerNumber]
,#CustomerName= B.[CustomerName]
from [dbo].[Activity] A
inner join [dbo].[Customer] B
on A.[CustomerID]=B.[CustomerID]
DECLARE #email VARCHAR(2000)
SELECT #email = RTRIM(U.[Email]) + ';'
FROM [dbo].[Activity] A
left join [dbo].[UserActivity] UA
inner join [dbo].[User] U
on UA.[UserID]=U.[UserID]
on A.[ActivityID]=UA.[ActivityID]
WHERE U.[Email]<> ''
DECLARE #content varchar (2000)
= 'ActivityID:' + #ActivityId + ' '
+ ',ActivityName:' + #ActivityName + ' '
+ 'has been created for' + 'CustomerNumber: ' + #CustomerNo
+ ' ' + '&CustomerName: ' + #CustomerName
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'LEADNOTIFY'
,#recipients = #email
,#subject = 'New Lead Found'
,#body = #content
,#importance ='HIGH'
END
The problem is in my code that I can't fetch the customer data and email from the respective tables properly.
I have written some code below which will loop through all the effected rows and send an email for each.
However before you read that, I would highly recommend (as #HABO commented) on using a different approach. Triggers are fine for some tasks, but 2 key things you want to keep in mind when using triggers:
Ensure its obvious to anyone developing the system that there are triggers - there is nothing worse as a developer than finding stuff magically happening on what seems like a simple CRUD operation.
Do whatever you do in your trigger as fast as possible because you are holding locks which not only affect the current session, but could easily affect other users as well. Ideally therefore you want to be performing set-based operations, not RBAR (Row By Agonising Row) operations.
Sending emails is a terrible thing to do inside a trigger because its not uncommon to be forced to wait for an SMTP server to respond. If you wish to trigger emails, a better way is to use the trigger to insert the email data into a queuing table and then have a service elsewhere which de-queues these emails and sends them.
All that aside the following code shows one way to handle the Inserted pseudo-table when you want to perform RBAR operations. Because in SQL Server the Inserted pseudo-table (and the Deleted pseudo-table) will contain the number of rows effected by the operation i.e. 0-N. Also I've hopefully joined your tables to correctly obtain the required information.
CREATE TRIGGER [dbo].[Activity_Insert_Mail_Notification]
ON [dbo].[Activity]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ActivityID VARCHAR(2000), #ActivityName VARCHAR(2000), #CustomerNo VARCHAR(2000), #CustomerName VARCHAR(2000), #Email VARCHAR(2000), #Content VARCHAR(2000);
-- Get all the relevant information into a temp table
SELECT ActivityID, ActivityName, C.CustomerNumber, C.CustomerName, RTRIM(U.[Email]) + ';' Email, CONVERT(BIT, 0) Handled
INTO #ActivityTriggerTemp
FROM Inserted I
INNER JOIN Customer C on C.CustomerID = I.CustomerID
INNER JOIN UserActivity UA on UA.ActivityID = I.ActivityID
INNER JOIN [USER] U on U.UserID = UA.UserID;
-- Loop through the temp table sending an email for each row, then setting the row as 'handled' to avoid sending it again.
WHILE EXISTS (SELECT 1 FROM #ActivityTriggerTemp WHERE Handled = 0) BEGIN
SELECT TOP 1 #ActivityID = ActivityID, #ActivityName = ActivityName, #CustomerNumber = CustomerNumber, #CustomerName = CustomerName, #Email = Email
FROM #ActivityTriggerTemp
WHERE Handled = 0;
-- Build the body of the email
set #Content = 'ActivityID:' + #ActivityId + ' '
+ ',ActivityName:' + #ActivityName + ' '
+ 'has been created for' + 'CustomerNumber: ' + #CustomerNo
+ ' ' + '&CustomerName: ' + #CustomerName;
-- Send the email
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'LEADNOTIFY'
, #recipients = #Email
, #subject = 'New Lead Found'
, #body = #Content
, #importance ='HIGH';
UPDATE #ActivityTriggerTemp SET
Handled = 1
WHERE ActivityID = #ActivityID AND ActivityName = #ActivityName AND CustomerNumber = #CustomerNumber AND CustomerName = #CustomerName AND Email = #Email;
END;
END

EXEC msdb.dbo.sp_send_dbmail ignored Query

I have a code to send Birthday Emails to Customers, the query works fine, but the SQL Mail server always send a Birthday Email to all Customers, even he has no Birthday
use Insurance
go
select
Customer.CustomerID
,Customer.FirstName
,Customer.LastName
,Customer.Birthday
,Customer.Email
from Customer
where Customer.CustomerID = Customer.CustomerID and
DAY([Birthday]) = DAY(GETDATE())
AND MONTH([Birthday]) = MONTH(GETDATE())
declare #Receipientlist nvarchar(4000)
set #Receipientlist =
STUFF((select ';' + Email FROM dbo.Customer FOR XML PATH('')),1,1,'')
EXEC msdb.dbo.sp_send_dbmail #profile_name='test',
#recipients=#Receipientlist,
#subject='Insurance',
#body='Happy Birthday.
Today is your Birthday.'
Your query at the top of your batch has nothing to do with your statement that executes msdb.dbo.sp_send_dbmail. If you only want to email customer's who's birthday it is, you need to filter in the statement that creates the recipients (and you don't need the previous statement):
DECLARE #RecipientList nvarchar(4000);
--I removed the CustomerID = CustomerID clause, as it'll always equal itself,
--apart from when it's value is NULL (and I doubt it'll ever be NULL)
SET #RecipientList = STUFF((SELECT N';' + Email
FROM dbo.Customer
WHERE DAY([Birthday]) = DAY(GETDATE())
AND MONTH([Birthday]) = MONTH(GETDATE())
FOR XML PATH(N''),TYPE).value('.','nvarchar(4000)'), 1, 1,N'');
EXEC msdb.dbo.sp_send_dbmail #profile_name = 'test',
#recipients = #RecipientList,
#subject = 'Insurance',
#body = 'Happy Birthday.
Today is your Birthday.';
I've also changed the way that the value from the subquery is returned, used TYPE and the value clauses. Email addresses can contain some special characters, and these would have been escaped without the usage of TYPE (for example & would become &). (I've also corrected the spelling of #RecipientList.)

Automates sending email using SQL Server 2008 T-SQL, with custom message title and body

This question is the follow-up of this thread
Given that I've created two simple tables User and Item:
User (Id, Name, Email)
Item (Id, CreatedDate, EmailSent, UserId)
I am able to create an SQL Server Agent job that periodically runs the following script:
USE test
DECLARE #emails varchar(1000)
SET #emails = STUFF((SELECT ';' + u.Email FROM [user] u JOIN [item] i
ON u.ID = i.UserId
WHERE i.EmailSent = 0 AND DATEDIFF(day, i.CreatedDate, GETDATE()) >= 30
FOR XML PATH('')),1,1,'')
/** send emails **/
EXEC msdb.dbo.sp_send_dbmail
#profile_name='test',
#recipients=#emails,
#subject='Test message',
#body='This is the body of the test message.'
The purpose is to get any item that has been created for >= 30 days and then send a reminder email to its user. The EmailSent is checked first to exclude items already reminded.
I want to improve this script as follows:
There's a separate query that does the SELECT part and stores the result into a variable so that it can be reused in the email sending query, and in an UPDATE query that sets selected items' EmailSent to True (done after emails sent).
Customise the title and body of the message with user's Name and item's Id. Something like: Dear Name, the item Id has been created for 30 days or more.
Anybody has any idea of how these improvement could be done?
I've managed to have this so far
USE test
-- 1) Update selected items to state 1
UPDATE [PickingList]
SET ReminderState = 1
WHERE ReminderState = 0 AND DATEDIFF(day, CreatedDate, GETDATE()) >= 30
DECLARE #userId INT
DECLARE #email NVARCHAR(100)
DECLARE #plId INT
DECLARE #getId CURSOR
-- 2) Process those having state 1
SET #getId = CURSOR FOR
SELECT u.ID, u.Email, pl.ID
FROM [User] u JOIN [PickingList] pl
ON u.ID = pl.UserId
WHERE pl.ReminderState = 1
OPEN #getId
FETCH NEXT
FROM #getId INTO #userId, #email, #plId
WHILE ##FETCH_STATUS = 0
BEGIN
/** send emails **/
DECLARE #pId VARCHAR(1000)
DECLARE #title VARCHAR(1000)
DECLARE #body VARCHAR(8000)
SET #pId = CAST(#plId AS VARCHAR(16))
SET #title = #pId + ' was created more than 30 days ago'
SET #body = 'The following picking list ' + #pId + ' blah blah'
DECLARE #code INT
SET #code = -1
EXEC #code = msdb.dbo.sp_send_dbmail
#profile_name='test',
#recipients=#email,
#subject=#title,
#body=#body
-- 3) Log the email sent and update state to 2
-- Below is what I want to do, but ONLY when the it can be sure that
-- the email has been delivered
INSERT INTO [PickingListEmail] (UserId, PickingListId, SentOn)
VALUES (#userId, #plId, GETDATE())
UPDATE [PickingList] SET ReminderState = 2 WHERE ReminderState = 1
FETCH NEXT
FROM #getId INTO #userId, #email, #plId
END
CLOSE #getId
DEALLOCATE #getId
In step (3), before saving the email sent and update the item to state 2 (processed), I would want to make sure that the email has been sent, based on the data fetched from sysmail_log, as sp_send_dbmail would only care about whether it can send the mail to the SMTP server, so will return the success code 0 even when the sending fails.
Something like this:
meaning of the values of sent_status on msdb.dbo.sysmail_mailitems
or
Check If sp_send_dbmail Was Successful

SQL Server: stored procedure and send dbmail

The reason of creating a stored procedure is to schedule a job to send a biweekly report to our staff (coordinators) using SQL Server db mail.
I'm having problems with getting it to work the right way. I don't usually work with cursors but couldn't find other choices.
Here's the issue. I tested the query by set criteria to send only to one Coordinator with one record "if #Coord_Email = 'lamez.sw1#gmail.com', where n.id = '43422546'". However the query been running over 5 minutes so i had to cancel it.
ALTER PROCEDURE [dbo].[sp_MZ_Coord_rpt_s9]
AS
BEGIN
DECLARE #Member_ID VARCHAR(20)
DECLARE Report_S9 CURSOR FOR
SELECT id
FROM name
WHERE status = 'a'
OPEN Report_S9
FETCH NEXT FROM Report_S9 INTO #member_ID
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Coord_ID Varchar(20)
DECLARE #CO_ID Varchar(20)
DECLARE #Coord_Name Varchar(50)
DECLARE #Coord_Email Varchar(50)
SELECT #CO_ID = ID
FROM Relationship
WHERE id = #Member_ID
SELECT #Coord_ID = target_id
FROM Relationship
WHERE RELATION_TYPE = 'CO'
SELECT #Coord_Name = FULL_NAME
FROM Name
WHERE ID = #Coord_ID
SELECT #Coord_Email = email
FROM Name
WHERE id = #Coord_ID
IF #Coord_Email <> ''
BEGIN
SELECT
n.id, n.CO_ID, n.FULL_NAME, a.TRANSACTION_DATE, a.UF_1, r.TARGET_ID
FROM name n
INNER JOIN activity a ON n.id = a.id
INNER JOIN Tops_Profile tp ON a.id = tp.ID
INNER JOIN Relationship r ON n.CO_ID = r.ID
WHERE
n.id = #member
AND UF_1 <> ''
AND (DATEDIFF(dd, TRANSACTION_DATE, GETDATE()) < 2)
AND r.RELATION_TYPE = 'co'
ORDER BY
TRANSACTION_DATE
EXEC msdb..sp_send_dbmail
#profile_name = 'TOPS.ADMIN',
#recipients = #Coord_Email,
--#blind_copy_recipients = ,
#subject = 'S9 Report'
End
FETCH NEXT FROM Report_S9 INTO #member_ID
END
CLOSE Report_S9
DEALLOCAT Report_S9
End
Any help is greatly appreciated.
The FETCH NEXT should be outside of your check for null. You need to continue the loop, even when there is nothing to do.

Resources