The table from which a data range is to be queried :
Id Variance
1 2
2 17
3 7
4 4
5 20
6 1
7 111
8 8
9 18
10 67
Another table that has
Freq StartRange EndRange
H 10 7
H 8 8
H 6 20
The data in the first table is loaded every 30mins through an SSIS package.
Now I want alert as in emails to be triggered when the data in the Variance column falls in the StartRange and EndRange.
CREATE PROCEDURE [dbo].[SP_Email] #Start varchar(50),#End varchar(50),#Date datetime
AS
BEGIN
DECLARE #xml NVARCHAR(MAX)
DECLARE #body NVARCHAR(MAX)
SET #xml = CAST(( SELECT [SlNo] AS 'td','',[date] AS 'td','',
[lag] AS 'td','', Percent AS 'td'
FROM TestTbl
WHERE(percent >= #Start AND percent < #End)
AND CAST(CONVERT(VARCHAR,curDate,106) AS DATETIME) = CAST(#Date AS DATETIME)
ORDER BY SlNo
FOR XML PATH('tr'), ELEMENTS ) AS NVARCHAR(MAX))
SET #body ='<html><body><H3>Report</H3>
<table border = 1>
<tr>
<th> SlNo </th> <th> date </th> <th> lag </th> <th> Percent </th></tr>'
SET #body = #body + #xml +'</table></body></html>'
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Alert',
#body = #body,
#body_format ='HTML',
#recipients = 'abc#gmail.com'
#subject = 'Report';
END
Please suggest a process that will help me setup such in SQL Server 2005.
Thanks in Advance
This can be done via SQL Server Agent jobs:
EXEC dbo.sp_add_job
#job_name = N'Variance Check',
#notify_email_operator_name='you#your.domain',
#notify_level_email=2; -- Only notify you when YourStoredProceedure fails
GO
EXEC sp_add_jobstep
#job_name = N'Variance Check',
#step_name = N'Check the variance',
#subsystem = N'TSQL',
#command = N'EXEC YourStoredProceedure',
#retry_attempts = 1,
#retry_interval = 5 ;
GO
EXEC dbo.sp_add_schedule
#schedule_name = N'EveryThirtyMinutes',
#freq_type = 4, -- Daily
#freq_subday_type = 0x4, -- Minutes
#freq_subday_interval = 30,
#active_start_time = NULL; -- Start right away
USE msdb ;
GO
EXEC sp_attach_schedule
#job_name = N'Variance Check',
#schedule_name = N'EveryThirtyMinutes';
GO
EXEC dbo.sp_add_jobserver
#job_name = N'Variance Check';
GO
One way to approach this is through triggers. However, I don't recomment sending emails through triggers.
Instead, add a step to the process that loads the data in T1. After this step, call a stored procedure that finds the rows that meet the conditions and send the appropriate email.
It is rather hard to tell exactly what the conditions are, however. Your second table has three ranges. It is not clear which to apply to which row.
Related
TASK: Create an AFTER TRIGGER to accomplish a condition from a JOIN. The Trigger would be in table_1 when some record is created. Meanwhile, table_2 has a common column with some parameters that the condition needs to have.
Every time that the Result <> 1 AND Status <> 3 in table_2 and ALERT should be sent
-- QUERY WITH JOIN TABLE_1 ON TABLE_2
-- MOCK TABLE
-- Table_1 as A | Table_2 as B
A.LotCode | A.LineNumber | B.Result | B.Status
00000 | xxxx | 1 | 3
00001 | xxxx | 2 | 4
-- The LotCode 00001 should send it through email because satisfy the condition
CREATE TRIGGER FullfillOrderQCResult
ON Table_1
AFTER INSERT
AS
BEGIN
-----DECLARE VARIABLES-----
DECLARE #LOTNUMBER VARCHAR(50)
DECLARE #ACTIONPEFORMED VARCHAR(MAX)
DECLARE #ITEM INT
DECLARE #RESULT TINYINT
DECLARE #STATUS TINYINT
SELECT #LOTNUMBER = A.LotCode, #ITEM = A.LineNumber, #RESULT = B.Result, #STATUS = B.Status
FROM inserted AS A
JOIN Table_2 AS B
ON A.LotCode = B.DocumentID2
-----CONDITION WHEN I INSERT A VALUE-----
IF (#RESULT <> 1 AND #STATUS <> 3)
BEGIN
SET #ACTIONPEFORMED =
N'Hello, ' + '<br>' + '<br>'
+ N' The following LOT NUMBER: ' + #LOTNUMBER + ' has not been approved for this Item: '
EXEC MSDB.DBO.SP_SEND_DBMAIL
#PROFILE_NAME = 'SQLMail',
#RECIPIENTS = 'TEST#gmail.com',
#SUBJECT = 'LOT NON-Approved',
#BODY = #ACTIONPEFORMED,
#IMPORTANCE = 'HIGH',
#BODY_FORMAT = 'HTML'
END
ELSE
PRINT 'ALL GOOD MY FRIEND'
END
TESTING THE TRIGGER
--------INSERT VALUES------------------
INSERT INTO Table_1 (LotCode,LineNumber)
values ('00000','xxxx')
-----EXISTING VALUES-----
INSERT INTO Table_2 (CreationUser,DocumentID1,DocumentID2,DocumentID3,Result,Status)
values ('JL','00000','00000','00000',2,3)
The following shows you how to handle the fact that Inserted might have multiple rows. This is really not ideal behaviour for a trigger, because you have to process the results RBAR (row by agonising row), which is slow by itself, let alone the fact that you are sending an email.
CREATE TRIGGER FullfillOrderQCResult
ON Table_1
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
-----DECLARE VARIABLES-----
DECLARE #ACTIONPEFORMED varchar(max), #Id int;
SELECT A.LotCode, A.LineNumber, CONVERT(bit, 0) Done, IDENTITY(int) id -- Use your own id if you have one, just need to uniquely identify each row.
INTO #FullfillOrderQCResult_temp
FROM Inserted AS A
INNER JOIN Table_2 AS B ON A.LotCode = B.DocumentID2
WHERE B.Result <> 1 and B.[Status] <> 3;
WHILE EXISTS (SELECT 1 FROM #FullfillOrderQCResult_temp WHERE Done = 0) BEGIN
SELECT TOP 1 #Id = id, #ACTIONPEFORMED =
N'Hello, ' + '<br>' + '<br>'
+ N'The following LOT NUMBER: ' + LotCode + ' has not been approved for this Item: ' + LineNumber
FROM #FullfillOrderQCResult_temp
WHERE Done = 0;
EXEC MSDB.DBO.SP_SEND_DBMAIL
#PROFILE_NAME = 'SQLMail',
#RECIPIENTS = 'TEST#gmail.com',
#SUBJECT = 'LOT NON-Approved',
#BODY = #ACTIONPEFORMED,
#IMPORTANCE = 'HIGH',
#BODY_FORMAT = 'HTML';
UPDATE #FullfillOrderQCResult_temp SET Done = 1 WHERE id = #Id;
END;
END;
I don't know whether you would still want the concept of 'ALL GOOD MY FRIEND' because you could have none, some or all rows with issues. Anyway I assume print is only for debugging.
That said you would be much better off pushing an event into a queue and having a service process said event because triggers really should be as fast as possible. And adding an event to a queue could be handled in a set based manner e.g.
CREATE TRIGGER FullfillOrderQCResult
ON Table_1
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO MyEventQueue (A.LotCode, A.LineNumber) -- Any other information required to identify the records etc
SELECT A.LotCode, A.LineNumber
FROM Inserted AS A
INNER JOIN Table_2 AS B ON A.LotCode = B.DocumentID2
WHERE B.Result <> 1 and B.[Status] <> 3;
END;
I have a SQL query that runs fine outside of the stored procedure. When I run it through the stored procedure, it just runs and never completes and seems to cause locks. Am I doing something simply wrong like missing a NO LOCK in the sql cmd query or if there something obvious I cant see that explains why this stored procedure runs for so long and causes blocking?
CREATE PROCEDURE [USP_GET_INACTIVE_REPORTS_EMAIL]
-- Author: XXXXXXXXX
-- Version 1.4 - Ready for Production ( LOL ! )
AS
BEGIN
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
if #trancount = 0
begin transaction usp_get_inactive_reports_email;
else
save transaction usp_get_inactive_reports_email;
-- Step 1 - Create Temp Tables to hold Reporting DataSet for Active Reports that have not been executed in the last 30 days
IF OBJECT_ID('tempdb..##temp01') IS NOT NULL
BEGIN
DROP TABLE ##temp01
END
IF OBJECT_ID('tempdb..##temp02') IS NOT NULL
BEGIN
DROP TABLE ##temp02
END
-- Step 2 - Query the Report Server Catalog and Execution Log for answers . Active reports defined as not being hidden and having a description populated.
;WITH RankedReports AS
(
SELECT
ReportID,
TimeStart,
UserName,
RANK() OVER (PARTITION BY ReportID ORDER BY TimeStart DESC) AS iRank
FROM
ReportServer.dbo.ExecutionLog t1
JOIN
ReportServer.dbo.Catalog t2 ON t1.ReportID = t2.ItemID
AND t2.Type <> 1
)
SELECT DISTINCT
t1.UserName, t2.Name AS ReportName,
TimeStart,
SUBSTRING(t2.Path, 2, LEN(t2.Path) - LEN(t2.Name) - 1) AS Folder,
t2.Type
INTO ##temp01
FROM RankedReports t1
INNER JOIN ReportServer.dbo.Catalog t2 ON t1.ReportID = t2.ItemID
WHERE t1.iRank = 1
AND t2.Type <> 1
AND CAST(TimeStart AS DATE) > GETDATE() - 30
ORDER BY t1.UserName, t2.Name;
--- Select * from ##temp01 order by timestart
SELECT
cast ( SUBSTRING(Path,2,LEN(Path)-LEN(Name)-1) as varchar(100)) AS ReportFolder
,Name AS ReportName
,CreationDate
,ModifiedDate
,Type,replace([Description],'~','/') as Description,Hidden
INTO ##temp02
FROM ReportServer.dbo.Catalog
WHERE Name NOT IN (SELECT ReportName FROM ##temp01)
AND Path <> ''
AND SUBSTRING(Path,2,LEN(Path)-LEN(Name)-1)<>''
AND Description is not NULL
AND Type = 2 -- Means Report Type according to Mictosoft Docs for Report Server Schema
AND cast (ModifiedDate as date )<Getdate() - 30
OR (
Hidden='0' --- Reports that we have shown as hidden ad soft deletes done in SSRS
AND Name NOT IN (SELECT ReportName FROM ##temp01)
AND Path <> ''
AND SUBSTRING(Path,2,LEN(Path)-LEN(Name)-1)<>''
AND Type = 2 -- Means Report Type according to Mictosoft Docs for Report Server Schema)
AND cast (ModifiedDate as date )<Getdate() - 30
)
ORDER BY Path
-- Step 3 - If the Query return no results then dont email. If it does email it to requestforchange#creditfix.co.uk
declare #counter int
;with query as (
SELECT ReportFolder
,ReportName
,CreationDate
,ModifiedDate
,Type,[Description],Hidden
FROM ##temp02 where Hidden=0 AND Description is not null
)
select #counter = count(*) from query
--- Step 4 - If the count of rows from Inactive Reports query >0 then Send the email about which reports to xxxxx
IF #counter > 0
BEGIN
--execute xp_cmdshell 'bcp "SELECT '' ReportFolder,ReportName,CreationDate,ModifiedDate,Type,Description FROM ##temp02 WHERE ReportFolder <>''''AND Description is not NULL AND Type = 2 AND Hidden=0 ORDER BY 4" queryout C:\xxxxx\BI_Active_Reports_not_used_in_Last_30_days.csv -c -t, -S xxxxxx -U xxxxx -P xxxxxxx'
declare #fileNameTXT varchar(200) = '\\xxxxxx\Active_Reports_not_used_in_Last_30_days.csv'
declare #sql_bcp varchar(1000) = 'sqlcmd -S xxxxx -d xxxxxx Rep -E -Q "set nocount on; PRINT ''Reportname'' + '','' + ''ReportFolder'' ;SELECT ReportName,ReportFolder FROM ##temp02 where Hidden=0 AND Description is not null " -o ' + #filenameTXT + ' -W -h -1 -s"," -w 1500'
--print #sql_bcp
execute xp_cmdshell #sql_bcp
--- Send Email Section to export query contents to a CSV Output File
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'SMTP Server Mail',
#recipients= 'xxxxxx',
#subject = 'IT - Active Reports not used in the Last 30 days',
#execute_query_database = 'ReportServer',
#body = 'Greetings, <br><br>
Please find attached Reports that have not been run in the last 30 days.
<br><br>
Kind Regards
<br><br>
xxxxxx Team',
#body_format = 'HTML',
#file_attachments= '\\xxxxx\Active_Reports_not_used_in_Last_30_days.csv'
commit transaction usp_get_inactive_reports_email
END
ELSE
BEGIN
--RAISERROR('No rows exist, quit job', 16, 1);
commit transaction usp_get_inactive_reports_email;
--print 'commit'
Return;
END
commit transaction usp_get_inactive_reports_email;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(), #message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback ;
raiserror ('usp_get_inactive_reports_email: %d: %s', 16, 1, #error, #message) ;
return;
end catch
end
Global temporary tables are visible from multiple sessions, but they have locking just like regular tables. So sessions can't read each other's uncommitted changes. This includes uncommitted DDL, like CREATE TABLE or SELECT ... INTO.
What you have is a un-detectable deadlock between your sessions, equivalent to:
begin transaction
create table ##t(id int)
exec xp_cmdshell 'bcp "select * from ##t" queryout "c:\temp\foo.csv" -S "localhost" -T'
Your BCP session can't read from the table, and is waiting for its creation to be committed, and the stored procedure is waiting on BCP.
Good day I have a table called Ticket which has several tickets registered, each one has a status:
1 = Accepted,2 = Assigned,3 = At attention,4 = Attended,5 = Agree.
I want to perform a stored procedure in which I only send mail to the tickets that are in state 4, that is, my ticket has status 4, it is activated exec sp_sendmail .
Then I will use it as a Job every 30 minutes, check to see if it is still in that state and if it is in state 4 it sends again mail, once it changes state 4 to 5 it will not send anything and it will be closed.
Something like this, that loops through the list of tickets and sends an email. Couple notes, though: first, if you try to send too many at once, your email provider may start dropping them, so maybe put in a {pre}WAITFOR DELAY '00:00:02'{pre} delay between messages. Also, instead of sending one email per ticket, you can look into the query options in sp_send_dbmail: you can email a single list of all currently-4 tickets. It just depends on your needs.
CREATE PROCEDURE dbo.SendTicketAttendedEmails
AS
BEGIN
DECLARE #MailList TABLE(TicketID INT, SendTo VARCHAR(255))
DECLARE #ThisTicketID INT
, #MailMessage NVARCHAR(2000)
, #MailSubject NVARCHAR(255)
, #SendTo VARCHAR(255)
INSERT INTO #MailList
([TicketID], [SendTo])
SELECT t.[ID], u.[UserEmail]
FROM dbo.YourTicketTable t
JOIN dbo.YourUserTable u
ON t.UserCreated = u.ID
WHERE [StatusID] = 4
WHILE EXISTS(SELECT 1 FROM #MailList)
BEGIN
SELECT TOP(1) #ThisTicketID = [TicketID]
, #MailSubject = 'Ticket ' + CAST([TicketID] AS VARCHAR(10)) + ' is in status 4.'
, #MailMessage = 'Please review, or whatever, ticket ' + CAST([TicketID] AS VARCHAR(10)) + '.'
, #SendTo = COALESCE([SendTo], 'yourEmailAddress#InCase.Missing')
FROM #MailList
ORDER BY [TicketID];
DECLARE #mailitem_id INT ;
EXEC [msdb].dbo.[sp_send_dbmail]
#profile_name = 'SomeDBMailProfileName' -- sysname
, #recipients = #SendTo -- varchar(max)
, #subject = #MailSubject -- nvarchar(255)
, #body = #MailMessage -- nvarchar(max)
, #mailitem_id = #mailitem_id OUTPUT -- int
, #from_address = 'you#you.com' -- varchar(max)
, #reply_to = 'you#you.com' -- varchar(max)
DELETE #MailList
WHERE [TicketID] = #ThisTicketID
END
END
Basically you will use something like this for your job.
exec sp_send_dbmail
#profile_name = 'your_mail_profile'
,#recipients = 'you#email.com'
,#subject = 'Attended'
,#query = 'select * from yourTable where [status] = 4'
--,#attach_query_result_as_file = 1
--,#query_attachment_filename = 'somefile.csv'
See other options in the docs... and adjust accordingly.
I have a SQL query that checks when a stored procedure was last run, from that date it gathers all the units that were purchased between that date and today's date and compares it to another number. If they match it sends an email saying the two number match and executes the rest of the stored procedure. However, when it runs I constantly get 0 for the number of accounts, when it should be a value greater than 0 daily:Here is my code:
DECLARE #UnitsPurchased int
DECLARE #WelcomeLetterGenerated int
DECLARE #LastSuccesfulRunTime datetime
SET #LastSuccesfulRunTime = (select cast(run_datetime as DATE) from apsllc_v2.dbo.LastSuccesfulRunDate)
SET #UnitsPurchased = (SELECT COUNT(*) FROM rsbi.dbo.loans as l
where rsbi.dbo.fn_Convert_Paradata_Birth_Date(l.Unit_PURDATE) between #LastSuccesfulRunTime
AND getDate() and l.LN_CLASS in (58,59)) -- Shows the number of units that were purchased yesterday
SET #WelcomeLetterGenerated = ( SELECT COUNT(*) FROM apsllc_v2.dbo.ApsUpload ap where ap.FormNumber IN(1100,1150) ) -- Shows how many welcome letters were generated today
if #UnitsPurchased <> #WelcomeLetterGenerated
begin
EXEC msdb.dbo.sp_send_dbmail
#recipients = N''
, #body = ' '
, #subject = 'Welcome Letter Counts do not Match'
, #profile_name = 'Email_Profile_01'
return
end
ELSE if #UnitsPurchased = #WelcomeLetterGenerated --or if
begin
EXEC msdb.dbo.sp_send_dbmail
#recipients = N''
, #body = ' '
, #subject = 'Welcome Letter Counts Match'
, #profile_name = 'Email_Profile_01'
end
I have a query below which tells me if something is due in the next 24 hours, however using SQL Server Agent, it sends an email whether the query returns a result or not, so:
My Question
based on the SQL Code below which I will add directly into an SQL Agent Job, can i set it up to not send an email of the query returns a 0 row count?
I have done some research however applying a boolean doesnt work to detect if there are results or not, or I may have done it wrong:
Code
DECLARE #xml NVARCHAR(MAX)
DECLARE #body NVARCHAR(MAX)
SET #xml = CAST((
SELECT
Jobs.JobID AS 'td', '' ,
Jobs.JobName AS 'td', '',
ToDoList.List AS 'td', '',
Staff.StaffName AS 'td', '',
CONVERT(DATE, ToDoItem.DueDate) AS 'td', '',
ToDoItem.Comment AS 'td', ''
FROM ToDoItem
INNER JOIN Staff ON ToDoList.StaffID= Staff.StaffID
INNER JOIN ToDoList on ToDoItem.ToDoListID = ToDoList.ToDoListID
INNER JOIN JobSummaryAndStatus ON ToDoList.JSASID = JobSummaryAndStatus.JSASID
INNER JOIN Jobs ON ProjectSummaryAndStatus.JobID= Jobs.JobID
WHERE DueDate BETWEEN GETDATE() AND GETDATE() + 1
AND Staff.StaffID = 20
FOR XML PATH('tr'), ELEMENTS ) AS NVARCHAR(MAX))
SET #body ='<html><body><H3>Jobs Due Today</H3>
<table border = 1>
<tr>
<th> Job Ref </th> <th> JobName </th> <th> List Name </th> <th> Staff Name </th> <th> Due Date </th> <th> Comments </th></tr>'
SET #body = #body + #xml +'</table></body></html>'
EXEC msdb.dbo.sp_send_dbmail
#body = #body,
#body_format ='HTML',
#recipients = 'myemail#domain.com;',
#subject = 'Jobs Due Today';
Research
I tried this with little success so hopefully its my SQL Knowledge which is the reason why i cant apply this simple boolean checker. Im sure it would check the #body tag at the bottom before it sends the email and I could and in a IF statement of sorts here
http://www.sqlservercentral.com/Forums/Topic1131198-391-1.aspx
thanks for the help
If there isn't any result from the query the #xml variable should be empty so you should be able to check that:
if (len(#xml) > 0)
begin
EXEC msdb.dbo.sp_send_dbmail
#body = #body,
#body_format ='HTML',
#recipients = 'myemail#domain.com;',
#subject = 'Jobs Due Today';
end
I haven't tried it though.