SQL Email Formatting disappearing CR LF - sql-server-2008

I have a TSQL script that selects data and generates an email.
My script is quite complex but I have cut it down to a simple example to demonstrate my problem.
In simple terms when one of my fields go over a particular length (21 characters) the CR and LF I have inserted into the VAR_Body variable go missing. The text from the field is not truncated.
Expected Result
Renewal Date= 23 Jun 2017
Existing Insurer= 1234567890123456789012
Existing Insurer ID= 6007
Actual Result
Renewal Date= 23 Jun 2017
Existing Insurer= 1234567890123456789012 Existing Insurer ID= 6007
If I take one character of the Existing Insurer string it works.
Here is my cut down code to demo the problem.
Does anyone have any ideas what is going on here?
I have run out of things to try.
Thanks
David
-- Email Variables
Declare #VAR_Dest_EmailAddress varchar(150)
set #VAR_Dest_EmailAddress = 'myemail#email.com.au'
Declare #VAR_Subject varchar(100)
Declare #VAR_Body varchar(1000)
-- Format Variables
Declare #DateDisplayFormat int
Set #DateDisplayFormat = 106 -- DD MMM YYYY eg 25 MAY 2017
Declare #MoneyDisplayFormat int
set #MoneyDisplayFormat = 1 -- Commas every three digits and two digits to the right of the decimal point.
-- Data variables
Declare #sPlanNumber varchar(10)
Declare #dRenewal datetime
Declare #sExistingInsurer varchar(50)
Declare #lExistingInsurerID int
-- Set the Variables
Set #sPlanNumber = '12345'
Set #dRenewal = '2017-06-23 00:00:00.000'
set #sExistingInsurer = '1234567890123456789012'
set #lExistingInsurerID = '6007'
-- Set the body of the email
set #VAR_Body = 'Renewal Date= ' + Convert(varchar(11),#dRenewal,#DateDisplayFormat) + CHAR(13)+ CHAR(10)
set #VAR_Body = #VAR_Body + 'Existing Insurer= ' + #sExistingInsurer + CHAR(13) + CHAR(10)
set #VAR_Body = #VAR_Body + 'Existing Insurer ID= ' + Convert(varchar(10),#lExistingInsurerID) + CHAR(13) + CHAR(10)
-- Send the email
EXEC msdb.dbo.sp_send_dbmail
#recipients = #VAR_Dest_EmailAddress,
#subject = #sPlanNumber,
#body = #VAR_BODY,
#profile_name = 'SQL Email Profile' ;

If you have no requirement around email format (TEXT vs HTML) you could use HTML formatting for your email. You can then use <br/> and other html tags to format the email.
EXEC msdb.dbo.sp_send_dbmail
#recipients = #VAR_Dest_EmailAddress,
#subject = #sPlanNumber,
#body = #VAR_BODY,
#body_format = 'HTML' --declare the body formatting of the email to be HTML
#profile_name = 'SQL Email Profile' ;
You can then format your email :
set #VAR_Body = 'Renewal Date= ' + Convert(varchar(11),#dRenewal,#DateDisplayFormat) + '<br/>'
--we can now use the html break character which will be rendered by all email clients
set #VAR_Body = #VAR_Body + 'Existing Insurer= ' + #sExistingInsurer + '<br/>'
set #VAR_Body = #VAR_Body + 'Existing Insurer ID= ' + Convert(varchar(10),#lExistingInsurerID) + '<br/>'
Reference can be found in the MSDN docs here

Related

Conversion failed when converting the varchar value 'LAST1, FIRST1' to data type int

I have a TSQL query that is doing 3-4 different things (my 1st attempt at this type of query). I encounter this error on the first record:
Error: "Conversion failed when converting the varchar value 'LAST1, FIRST1' to data type int."
The value referenced in the error is a concatenation of first and last name from a view.
From reading other posts I have deduced that the comma is the issue but how do I get around it? Do I use substring to strip the comma out from the view before I use it or do I re-write the view or is there a CAST or parse function I can use?
Here's overview of what the query should do:
Select and loop through records in a view
If column, "daysUntil" (int) = 30, INSERT new record into another database via stored procedure
Return scope_identity to output parameter #PK_ID to use in email body
Send email using new #PK_ID in body of email
BTW I already have a C# console app that can handle all of the above but I want to move this functionality to SQL Server.
Here's the SQL:
DECLARE #MailTo varchar (1024)
DECLARE #MailBody varchar (1024)
DECLARE #MailSubject varchar (1024)
DECLARE #MailFlag int
DECLARE #Bcc varchar (256)
SET #Bcc = 'user1#companydomain.org;user2#companydomain.org
DECLARE #MailFrom varchar (256)
SET #MailFrom = 'recordsystem#companydomain.org'
DECLARE #Notification30Body varchar (512)
DECLARE #Notification30Subject varchar (512)
DECLARE #NotificationAllBody varchar (512)
SET #NotificationAllBody = 'REQUIREMENTS: requirements here'
--Declare variables to hold the data which we get after looping each record
DECLARE
#iEmpName VARCHAR(102),
#iEmpID INT,
#iEmail1 VARCHAR(24),
#iRvwDate smalldatetime,
#idaysUntil INT
DECLARE Empid_cur CURSOR FOR
select EmpName, EmpID, RvwDate, Email1, daysUntil from ABC.dbo.vwEmpDataTEST
--Flag the start of loop/curser
SET #MailFlag = 0
OPEN Empid_cur
FETCH NEXT FROM Empid_cur
INTO #iEmpID, #iEmpID, #RvwDate, #iEmail1, #idaysUntil
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#idaysUntil = 30)
BEGIN
DECLARE #PK_ID INT;
EXEC XYZ.dbo.InsertEmpDataTEST #iEmpID, #PK_ID OUTPUT
SELECT #PK_ID
--send 30 day email here
SET #MailTo = #iEmail1
SET #MailBody = #Notification30Body
SET #MailSubject = #Notification30Subject
SET #Notification30Body = 'This Record is for ' + #iEmpName + ' is due in 30 days. Please open the fake URL to the new Record for ' + #iEmpName + ':' + CHAR(10) + ' http://server/Record/template.aspx?ID=' + #PK_ID
SET #Notification30Subject = 'Record for ' + #iEmpName
SET #MailFlag = 1 --email ready
END
IF (#MailFlag = 1)
BEGIN
--send email
EXEC msdb..sp_send_dbmail
#profile_name ='MailUser',
#recipients = #MailTo,
#blind_copy_recipients = #Bcc,
#subject = #MailSubject,
#body = #MailBody
END
FETCH NEXT FROM Empid_cur
INTO #iEmpID, #iEmpID, #RvwDate, #iEmail1, #idaysUntil
END
CLOSE Empid_cur
DEALLOCATE Empid_cur
You have INTO #iEmpID, #iEmpID, ... with the int variable repeated twice, change to INTO #iEmpName, #iEmpID,... so the columns and variables (and their types) are all aligned.

How to pass column name along with other parameters

I have below SPROC in which i am passing column name(value) along with other parameters(Place,Scenario).
ALTER PROCEDURE [dbo].[up_GetValue]
#Value varchar(20), #Place varchar(10),#Scenario varchar(20), #Number varchar(10)
AS BEGIN
SET NOCOUNT ON;
DECLARE #SQLquery AS NVARCHAR(MAX)
set #SQLquery = 'SELECT ' + #Value + ' from PDetail where Place = ' + #Place + ' and Scenario = ' + #Scenario + ' and Number = ' + #Number
exec sp_executesql #SQLquery
END
GO
when executing : exec [dbo].[up_GetValue] 'Service', 'HOME', 'Agent', '123697'
i am getting the below error msg
Invalid column name 'HOME'.
Invalid column name 'Agent'.
Do i need to add any thing in the sproc??
First: You tagged your question as mysql but I think your code is MSSQL.
Anyway, your problem is that you need to add quotes around each string valued parameter.
Like this:
alter PROCEDURE [dbo].[up_GetValue]
#Value varchar(20), #Place varchar(10),#Scenario varchar(20), #Number varchar(10)
AS BEGIN
SET NOCOUNT ON;
DECLARE #SQLquery AS NVARCHAR(MAX)
set #SQLquery = 'SELECT ' + QUOTENAME(#Value) + ' from PDetail where Place = ''' + #Place + ''' and Scenario = ''' + #Scenario + ''' and Number = ''' + #Number +''''
print #SQLquery
exec sp_executesql #SQLquery
END
GO
Update:
Use QUOTENAME to make sure it works.
QUOTENAME:
Returns a Unicode string with the delimiters added to make the input string a valid SQL Server delimited identifier.
You need to quote column names with ` (backtick) and string values with ".
set #SQLquery = 'SELECT `' + #Value + '` from PDetail where Place = "' + #Place + '" and Scenario = "' + #Scenario + '" and Number = ' + #Number
Try using a prepared statement instead of concatinating the string.
Example:
PREPARE stmt1 FROM 'SELECT ? from PDetail where Place = ? and Scenario = ? and Number = ?;
EXECUTE stmt1 USING #Value, #Place, #Scenario, #Number;

SQL Concatenation syntax error

I am using an SQL script to build the body of an email with values from a database to pass to a stored procedure, however I am getting an 'incorrect syntax near '+' error on the following line:
#body = 'Item number ' + #ref + 'is due calibration on ' + convert(nvarchar(20), #next) + '. <br><br>Please take the necessary action to ensure the item is calibrated before this time. <br><br>You can view the item here > http://server/app/ViewItem.aspx?calibration_no=' + #ref
ref is an nvarchar(50)
next is a date
body is an nvarchar(max)
Can anyone explain where i'm going wrong? Thanks!
EDIT - Full query and procedure
Sorry for the pastebin links, couldn't get the indentation right in the editor on here.
Query is HERE
Procedure is HERE
I would suggest you declare a variable, put the contents in the variable and then set the #Body param with that variable:
declare #MailBody nvarchar(max)
declare
#out_desc nvarchar(max),
#out_mesg nvarchar(50)
declare #ref nvarchar(50),
#next date
declare c1 cursor read_only
for
select [calibration_no], [description], [next_calibration]
from calibration.dbo.item
open c1
fetch next from c1 into
#ref, #next
while ##fetch_status = 0
begin
if datepart(month,#next) <= dateadd(month, 1, (datepart(month, getdate())))
begin
SET #MailBody = 'Item number ' + STR(#ref) + 'is due calibration on ' + STR(convert(nvarchar(20), #next))
+ '. <br><br>Please take the necessary action to ensure the item is calibrated before this time.
<br><br>You can view the item here > http://server/app/ViewItem.aspx?calibration_no=' + STR(#ref)
exec calibration.dbo.spSendMail #recipients = 'xxxxxxxx',
#subject = 'Test Email',
#from = 'xxxxxxxx',
#body = #MailBody
#output_mesg = #out_mesg output,
#output_desc = #out_desc output
print #out_mesg
print #out_desc
end
fetch next from c1 into
#ref, #next
end
close c1
deallocate c1
For what it is worth, the following worked fine for me in SQL Server:
declare #body nvarchar(max)
declare #ref nvarchar(50)
declare #next date
select #ref = 'test'
select #next = getdate()
select #body = 'Item number ' + #ref + 'is due calibration on ' + convert(nvarchar(20), #next) + '. <br><br>Please take the necessary action to ensure the item is calibrated before this time. <br><br>You can view the item here > http://server/app/ViewItem.aspx?calibration_no=' + #ref
select #body
To be on the safe side, I always wrap the variable with a STR()
This manages to avoid the incorrect syntax error
So your code would read...
#body = 'Item number ' + STR(#ref) + 'is due calibration on ' + STR(convert(nvarchar(20), #next)) + '. <br><br>Please take the necessary action to ensure the item is calibrated before this time. <br><br>You can view the item here > http://server/app/ViewItem.aspx?calibration_no=' + STR(#ref)

Convert dynamically SQL Server 2008 Table to HTML table

Is there a way I can convert a SQL Server 2008 Table to HTML table text, without knowing the structure of the table first?
I tried this:
USE [Altiris]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spCustomTable2HTML] (
#TABLENAME NVARCHAR(500),
#OUTPUT NVARCHAR(MAX) OUTPUT,
#TBL_STYLE NVARCHAR(1024) = '',
#HDR_STYLE NVARCHAR(1024) = '')
AS
-- #exec_str stores the dynamic SQL Query
-- #ParmDefinition stores the parameter definition for the dynamic SQL
DECLARE #exec_str NVARCHAR(MAX)
DECLARE #ParmDefinition NVARCHAR(500)
--We need to use Dynamic SQL at this point so we can expand the input table name parameter
SET #exec_str= N'
DECLARE #exec_str NVARCHAR(MAX)
DECLARE #ParmDefinition NVARCHAR(500)
--Make a copy of the original table adding an indexing columnWe need to add an index column to the table to facilitate sorting so we can maintain the
--original table order as we iterate through adding HTML tags to the table fields.
--New column called CustColHTML_ID (unlikely to be used by someone else!)
--
select CustColHTML_ID=0,* INTO #CustomTable2HTML FROM ' + #TABLENAME + '
--Now alter the table to add the auto-incrementing index. This will facilitate row finding
DECLARE #COUNTER INT
SET #COUNTER=0
UPDATE #CustomTable2HTML SET #COUNTER = CustColHTML_ID=#COUNTER+1
-- #HTMLROWS will store all the rows in HTML format
-- #ROW will store each HTML row as fields on each row are iterated through
-- using dymamic SQL and a cursor
-- #FIELDS will store the header row for the HTML Table
DECLARE #HTMLROWS NVARCHAR(MAX) DECLARE #FIELDS NVARCHAR(MAX)
SET #HTMLROWS='''' DECLARE #ROW NVARCHAR(MAX)
-- Create the first HTML row for the table (the table header). Ignore our indexing column!
SET #FIELDS=''<tr ' + #HDR_STYLE + '>''
SELECT #FIELDS=COALESCE(#FIELDS, '' '','''')+''<td>'' + name + ''</td>''
FROM tempdb.sys.Columns
WHERE object_id=object_id(''tempdb..#CustomTable2HTML'')
AND name not like ''CustColHTML_ID''
SET #FIELDS=#FIELDS + ''</tr>''
-- #ColumnName stores the column name as found by the table cursor
-- #maxrows is a count of the rows in the table, and #rownum is for marking the
-- ''current'' row whilst processing
DECLARE #ColumnName NVARCHAR(500)
DECLARE #maxrows INT
DECLARE #rownum INT
--Find row count of our temporary table
SELECT #maxrows=count(*) FROM #CustomTable2HTML
--Create a cursor which will look through all the column names specified in the temporary table
--but exclude the index column we added (CustColHTML_ID)
DECLARE col CURSOR FOR
SELECT name FROM tempdb.sys.Columns
WHERE object_id=object_id(''tempdb..#CustomTable2HTML'')
AND name not like ''CustColHTML_ID''
ORDER BY column_id ASC
--For each row, generate dymanic SQL which requests the each column name in turn by
--iterating through a cursor
SET #rowNum=0
SET #ParmDefinition=N''#ROWOUT NVARCHAR(MAX) OUTPUT,#rowNum_IN INT''
While #rowNum < #maxrows
BEGIN
SET #HTMLROWS=#HTMLROWS + ''<tr>''
SET #rowNum=#rowNum +1
OPEN col
FETCH NEXT FROM col INTO #ColumnName
WHILE ##FETCH_STATUS=0
BEGIN
--Get nth row from table
--SET #exec_str=''SELECT #ROWOUT=(select top 1 ['' + #ColumnName + ''] from (select top '' + cast(#rownum as varchar) + '' * from #CustomTable2HTML order by CustColHTML_ID ASC) xxx order by CustColHTML_ID DESC)''
SET #exec_str=''SELECT #ROWOUT=(select ['' + #ColumnName + ''] from #CustomTable2HTML where CustColHTML_ID=#rowNum_IN)''
EXEC sp_executesql
#exec_str,
#ParmDefinition,
#ROWOUT=#ROW OUTPUT,
#rowNum_IN=#rownum
SET #HTMLROWS =#HTMLROWS + ''<td>'' + #ROW + ''</td>''
FETCH NEXT FROM col INTO #ColumnName
END
CLOSE col
SET #HTMLROWS=#HTMLROWS + ''</tr>''
END
SET #OUTPUT=''''
IF #maxrows>0
SET #OUTPUT= ''<table ' + #TBL_STYLE + '>'' + #FIELDS + #HTMLROWS + ''</table>''
DEALLOCATE col
'
DECLARE #ParamDefinition nvarchar(max)
SET #ParamDefinition=N'#OUTPUT NVARCHAR(MAX) OUTPUT'
--Execute Dynamic SQL. HTML table is stored in #OUTPUT which is passed back up (as it's
--a parameter to this SP)
EXEC sp_executesql #exec_str,
#ParamDefinition,
#OUTPUT=#OUTPUT OUTPUT
RETURN 1
but when I execute the procedure
DECLARE #HTML NVARCHAR(MAX)
EXEC SpCustomTable2HTML 'Users', #HTML OUTPUT
SELECT #HTML
it keeps returning null.
Any ideas?
This SQL Fiddle DEMO shows your problem. When ALL the columns in ALL rows have values, you get a proper HTML table. When ANY NULLs exist, it turns the entire thing into NULL because
NULL + <any> = NULL
To fix it, simply change line 90 to handle nulls, i.e.
SET #HTMLROWS =#HTMLROWS + '''' + ISNULL(#ROW,'''') + ''''
The fixed SQL Fiddle DEMO
I realise it's been a while (to say the least) since this question was active but I thought I would post a few comments on this thread, as it turned up in a recent search.
Apologies if this (unintentionally) annoys the question asker but I believe the approach being used is both inefficient and difficult to understand - and therefore maintain.
There's no need to copy the database data before using it to generate the HTML table. It's just my humble opinion, but using dynamic SQL to generate dynamic SQL is also counter-intuitive.
Furthermore, database data values that contain HTML tags (or, worse still, malformed HTML tags) need to be escaped, so that they can be rendered correctly. For example, database data such as "value > 10" needs to generate the HTML "<td>value > 10</td>".
The following code addresses all of the above points, by using the built-in FOR XML clause:
CREATE PROCEDURE dbo.spCustomTable2HTML
#TABLENAME nvarchar(500),
#TBL_STYLE nvarchar(1024) = '',
#HDR_STYLE nvarchar(1024) = '',
#OUTPUT nvarchar(MAX) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
-- Declare variables
DECLARE #Columns nvarchar(MAX) = '',
#Data nvarchar(MAX),
#SQL nvarchar(MAX);
-- Snapshot columns (to force use of tempdb)
IF OBJECT_ID('tempdb.dbo.##spCustomTable2HTMLColumns') IS NOT NULL
BEGIN
DROP TABLE ##spCustomTable2HTMLColumns;
END
SET #SQL =
'SELECT TOP 0 *
INTO ##spCustomTable2HTMLColumns
FROM ' + #TABLENAME;
EXEC sp_executesql #SQL;
-- Build header row
SET #OUTPUT = (SELECT name AS td
FROM tempdb.sys.columns
WHERE object_id = OBJECT_ID('tempdb.dbo.##spCustomTable2HTMLColumns')
ORDER BY column_id
FOR XML RAW(''), ELEMENTS);
SET #OUTPUT += '</tr>'
-- Build column list
SELECT #Columns += '[' + name + '] AS td,'
FROM tempdb.sys.columns
WHERE object_id = OBJECT_ID('tempdb.dbo.##spCustomTable2HTMLColumns')
ORDER BY column_id;
SET #Columns = LEFT(#Columns, LEN(#Columns) - 1); -- Strip trailing comma
-- Delete columns snapshot
DROP TABLE ##spCustomTable2HTMLColumns;
-- Build data rows
SET #SQL =
'SET #Data = CONVERT(varchar(MAX),
(SELECT ' + #Columns +
' FROM ' + #TABLENAME +
' FOR XML RAW (''tr''), ELEMENTS XSINIL))';
EXEC sp_executesql #SQL, N'#Data NVARCHAR(MAX) OUTPUT', #Data = #Data OUTPUT;
SET #Data = REPLACE(#Data, ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"', ''); -- Remove XSI namespace
SET #Data = REPLACE(#Data, ' xsi:nil="true"', ''); -- Remove XSI attributes
SET #OUTPUT += #Data;
-- Prefix table/row headers
SET #OUTPUT = REPLACE(#OUTPUT, ' ', ' '); -- Use non-breaking spaces
SET #OUTPUT = REPLACE(#OUTPUT, '</tr>', '</tr>' + CHAR(13) + CHAR(10)); -- Add new line per row (to improve rendering in Microsoft Outlook)
SET #OUTPUT = '<table ' + #TBL_STYLE + '>' +
'<tr ' + #HDR_STYLE + '>' +
#OUTPUT +
'</table>';
END

scheduled execution of all TSQL scripts in a folder

I have a bunch of TSQL scripts that need to be executed on a daily basis.
I know I can use Job Agent to execute them but that requires me to change the actual job.
What I would like to do is create a job that simply says:
execute all TSQL-scripts in <some folder>
A bonus would be if a filter could be used based on the filename of the script: So that one job would execute all the files whose name start with a 'd', another job would execute all those with a 'w' in the name.
Can this be done? How can this be done?
I read some things that spoke of using the Windows-scheduler to run the SQLCMD-utility.
I'd rather have the SQL Server do the scheduling and executing. Is Powershell the way to go? If so, where and how to start? (Never had to use it so never gave it much attention :/)
Thanks for thinking with me!
Henro
To execute a script from a file you can use:
DECLARE #dir varchar(100) = 'C:\MyDir\'
DECLARE #file varchar(100) = 'myScript.sql'
DECLARE #cmd varchar(100) = 'sqlcmd -S ' + ##SERVERNAME + ' -i ' + #dir + #file
EXECUTE dbo.xp_cmdshell #command_string = #cmd
To get the list of files from a dir you can use
CREATE TABLE #tbl (Name varchar(400))
DECLARE #cmd varchar(100) = 'dir ' + #dir + ' *.sql'
INSERT #tbl EXECUTE dbo.xp_cmdshell #command_string = #cmd
DELETE FROM #tbl WHERE ISDATE(SUBSTRING(Name,1,10)) = 0
UPDATE #tbl SET Name = SUBSTRING(Name, 40, 100)
I guess you can do one thing:
Put all the scripts starting with name 'd' in one sproc.Here you have to put Go after each of your scripts in sproc.
Similarly create one more sproc with all the scripts starting with letter 'w'
Then schedule these jobs in sql server agent.
João, thanks for your help. Using your code I produced this: set dateformat dmy
DECLARE #scripts varchar(100) = 'C:\MYSCRIPTS\' -- folder with scripts
DECLARE #project varchar(100) = 'PROJECTX' -- specific folder
DECLARE #Identifier varchar(1) = 'D' -- All files of which the name starts with a 'T'
DECLARE #files varchar(100) = #scripts + #project + '\' + #Identifier + '*.sql'
CREATE TABLE #tbl1 (Name varchar(400))
DECLARE #cmd varchar(100) = 'dir ' + #files
INSERT #tbl1 EXECUTE master.dbo.xp_cmdshell #command_string = #cmd
DELETE FROM #tbl1 WHERE ISDATE(SUBSTRING(Name,1,10)) = 0
UPDATE #tbl1 SET Name = SUBSTRING(Name,37, 100)
CREATE TABLE #tbl2 (Counter smallint Primary Key IDENTITY(1,1), Name varchar(400))
INSERT INTO #tbl2 (Name)
Select #scripts + #project + '\' + Name from #tbl1
DECLARE #i int
DECLARE #NumRows int
DECLARE #File2BExecuted varchar(100)
SET #i = 1
SET #NumRows = (SELECT COUNT(*) FROM #tbl2)
IF #NumRows > 0
WHILE (#i <= (SELECT MAX(Counter) FROM #tbl2))
BEGIN
SELECT #File2BExecuted = Name FROM #tbl2 WHERE Counter = #i
DECLARE #script varchar(100) = 'sqlcmd -S ' + ##SERVERNAME + ' -i ' + #File2BExecuted
EXECUTE master.dbo.xp_cmdshell #command_string = #script
SET #i = #i + 1
END
drop table #tbl1
drop table #tbl2