Need to Send a formatted HTML Email via Database Mail in Sql Server 2008 R2
Here how can you create HTML body part of your mail.
Step 1:
DECLARE @Body NVARCHAR(MAX),
@TableHead VARCHAR(1000),
@TableTail VARCHAR(1000)
SET @TableTail = '</table></body></html>' ;
SET @TableHead = '<html><head>' + '<style>'
+ 'td {border: solid black;border-width: 1px;padding-left:5px;padding-right:5px;padding-top:1px;padding-bottom:1px;font: 11px arial} '
+ '</style>' + '</head>' + '<body>' + 'Report generated on : '
+ CONVERT(VARCHAR(50), GETDATE(), 106)
+ ' <br> <table cellpadding=0 cellspacing=0 border=0>'
+ '<tr> <td bgcolor=#E6E6FA><b>PO Date</b></td>'
+ '<td bgcolor=#E6E6FA><b>PO ID</b></td>'
+ '<td bgcolor=#E6E6FA><b>SAP Ref</b></td>'
+ '<td bgcolor=#E6E6FA><b>GRN</b></td>'
+ '<td bgcolor=#E6E6FA><b>DealerID</b></td>'
+ '<td bgcolor=#E6E6FA><b>Dealer Name</b></td>'
+ '<td bgcolor=#E6E6FA><b>Status</b></td></tr>' ;
SET @Body = ( SELECT td = CONVERT(VARCHAR(10), P.Date, 120), '',
td = P.ID, '',
td = P.ID2, '',
td = G.ID, '',
td = D.ID,'',
td = D.Name,'',
td = CASE WHEN G.SubmittedDate IS NULL THEN 'New'
ELSE 'Dealer Submitted'
END, ''
FROM I_CancelledGRN I
INNER JOIN TxnGRN G ON G.ID = I.ID
INNER JOIN Distributor D ON D.UID = G.DistributorUID
INNER JOIN POTxn P ON P.SiteUID = G.POTxn_SiteUID
AND P.UID = G.POTxnUID
WHERE IsCancelled IS NULL
FOR XML RAW('tr'),
ELEMENTS
)
SELECT @Body = @TableHead + ISNULL(@Body, '') + @TableTail
Step 2: create database email profile
Step 3: Send Email
EXEC sp_send_dbmail
@profile_name='DatabaseEmailProfile',
@copy_recipients ='[email protected]',
@recipients='[email protected]',
@subject='Query Result',
@body=@Body ,
@body_format = 'HTML' ;
My users are finding the "XML PATH" solution and trying to use it, but they are not programmers, and become confused. Instead of trying to help them learn to use XML PATH, I created a procedure that takes the name of a table and returns a string containing the HTML markup to display the table as an HTML table. My solution dynamically derives the column names of the HTML table from the data source.
Sample Use of Procedure HtmlTable
CREATE table ##foo (bar1 int, bar2 varchar(20), bar3 datetime)
INSERT into ##foo values (1, 'Abcdef', getdate())
INSERT into ##foo values (2, 'Ghijkl', '05/05/15')
DECLARE @tableHtml varchar(max)
EXEC dbo.HtmlTable
'##foo',
@tableHtml output
PRINT @tableHtml
The @tableHtml variable only contains markup for an HTML table, which is appropriate for including in an email body. To view alone, you need to wrap the output with html, head, and body tags.
Output from Procedure HtmlTable
Code for Procedure HtmlTable
/*
Author: Leigh Haynes
Date: February 2015
Notes: Takes a table name as string parameter and returns a string that contains HTML markup to display the table contents as an HTML table.
The input table should be sorted before invoking HtmlTable.
*/
CREATE PROCEDURE [dbo].[HtmlTable]
@data_source varchar (100) = NULL,
@tableHTML varchar(max) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@db varchar(50),
@table varchar(100),
@cmd varchar(400),
@rcd_cnt int,
@sql nvarchar(1000);
--use procedure DataSourceCheck to see if @data_source is valid
EXEC dbo.DataSourceCheck @data_source, @db output, @table output;
IF @db is NULL --if the data source is not good, @db comes back NULL, and @table holds info as to the problem (either the table does not exist, or it is empty).
BEGIN
SET @tableHtml = @table;
RETURN;
END;
--We have a good table. Use information_schema metadata for table to get column names.
IF OBJECT_ID ('tempdb..##columnNames') IS not null DROP TABLE ##columnNames;
CREATE table ##columnNames (column_name varchar(50), position int identity);
SET @sql = 'USE ' + @db + '; INSERT into ##columnNames SELECT column_name from information_schema.columns where table_name = ''' + @table + ''' order by ordinal_position';
EXEC master.sys.sp_executesql @sql;
--use ##columnNames to create table ##columnPivot with the proper number of fields to hold data
IF OBJECT_ID ('tempdb..##columnPivot') IS not null DROP TABLE ##columnPivot;
CREATE table ##columnPivot (f1 varchar(200));
DECLARE
@i int = 2,
@fieldct int,
@column varchar(50),
@field varchar(200),
@value varchar(100),
@html varchar(max) = '';
SET @fieldct = (SELECT COUNT(*) from ##columnNames);
WHILE @i <= @fieldct --loop through adding a field to ##columnPivot for each column. Max field len is 200.
BEGIN
SET @sql = 'ALTER table ##columnPivot ADD f' + cast (@i as varchar(2)) + ' varchar(200)';
EXEC master.sys.sp_executesql @sql;
SET @i = @i + 1;
END
--##columnPivot is constructed but empty. Columns are named f1, f2, f3, etc
--construct dynamic SQL string that will be executed to populate ##columnPivot
SET @sql = 'INSERT into ##columnPivot SELECT ';
SET @i = 1;
SET @fieldct = (SELECT count(*) from ##columnNames);
WHILE @i <= @fieldct - 1
BEGIN
SET @column = (SELECT top 1 column_name from ##columnNames where position = cast (@i as varchar(2)));
SET @field = 'CAST([' + @column + '] as varchar(200)),';
SET @sql = @sql + @field;
SET @i = @i + 1;
END
SET @column = (SELECT top 1 column_name from ##columnNames where position = @fieldct);
SET @field = 'CAST([' + @column + '] as varchar(200)) FROM ' + @data_source;
SET @sql = @sql + @field; --@sql now contains the SQL statement that will insert data from @data_source into ##columnPivot
--execute @sql to insert into ##columnPivot the data from @data_source table
EXEC master.sys.sp_executesql @sql;
--format the output
IF OBJECT_ID ('tempdb..#columns') IS not null DROP TABLE #columns;
--use a copy of ##columnNames, because next steps delete from this table, and ##columnNames data is needed below. Does not need to be a global temp.
SELECT *
into #columns
from ##columnNames
order by position;
SET @fieldct = (SELECT count(*) from #columns);
SET @i = 1;
--create the header row for the table containing column names from the @data_source
WHILE @i <= @fieldct
BEGIN
SET @field = (SELECT top 1 column_name from #columns order by position);
SET @html = @html + '<td bgcolor="#dedede"><b>' + @field + '</b></td>';
SET @i = @i + 1;
DELETE from #columns where column_name = @field;
END
SET @html = '<tr>' + @html + '</tr>'; --now @html contains the header row of the output table
--populate ##columnPivot by working through the data row by row.
ALTER table ##columnPivot add id_key int identity;
DECLARE
@j int = 1,
@fieldcnt int,
@cell varchar(100),
@row varchar(500) = '';
SET @i = 1;
SET @fieldcnt = (SELECT count(*) from ##columnNames);
SET @rcd_cnt = (SELECT count(*) from ##columnPivot);
WHILE @i <= @rcd_cnt --this loop executes one time for each row of data
BEGIN
SET @j = 1;
WHILE @j <= @fieldcnt --this loop executes one time for each column (cell) of data
BEGIN
SET @sql = 'SELECT @value = f' + cast (@j as varchar(2)) + ' from ##columnPivot where id_key = ' + cast (@i as varchar(2));
EXEC master.sys.sp_executesql @sql, N'@value varchar(200) OUTPUT', @value OUTPUT;
SET @cell = '<td>' + ISNULL (@value, '<br>') + '</td>'; --need to use <br> if the cell is empty
SET @row = @row + @cell;
SET @j = @j + 1;
END
SET @row = '<tr>' + @row + '</tr>';
SET @html = @html + @row;
SET @row = '';
DELETE from ##columnPivot where id_key = cast (@i as varchar(2));
SET @i = @i + 1;
END
SET @tableHTML = '<table border="1" cellspacing="0" cellpadding="5">' + @html + '</table><br>';
END
Note that I am invoking procedure "DataSourceCheck" to determine that the parameter @data_source contains the name of a valid table. DataSourceCheck does not have to be a separate procedure--the SQL could be included directly in the HtmlTable procedure, but I keep it separate for modularity and reusability.
Sample Use of Procedure DataSourceCheck
CREATE table ##foo (bar1 int, bar2 varchar(20), bar3 datetime)
INSERT into ##foo values (1, 'Abcdef', getdate())
INSERT into ##foo values (2, 'Ghijkl', '05/05/15')
DECLARE @table varchar(200), @db varchar(50)
EXEC dbo.DataSourceCheck
'##foo',
@db output,
@table output
PRINT @db
PRINT @Table
Output
tempdb
##foo
Now drop the table and see what happens:
DROP table ##foo
DECLARE @table varchar(200), @db varchar(50)
EXEC dbo.DataSourceCheck
'##foo',
@db output,
@table output
PRINT @db
PRINT @Table
Output
<br>Table ##foo does not exist or is improperly qualified.<br>
Code for Procedure DataSourceCheck
/*
Author: Leigh Haynes
Date: February 2015
Notes: Called by HtmlTable and CreateCsvFile to check validity of data source that is going to turn into an HTML table or a CSV file.
*/
CREATE PROCEDURE [dbo].[DataSourceCheck]
@dataSource varchar (100) = NULL,
@db varchar(50) = NULL output,
@table varchar(100) = NULL output
AS
BEGIN
DECLARE
@buffer varchar(100),
@object varchar(100),
@objectId bigint,
@schema varchar(50),
@rcd_cnt int,
@tableHtml varchar(200),
@sql nvarchar(1000)
SET @buffer = @dataSource;
--cannot accesss a local temp table. Return.
IF SUBSTRING (@buffer, 1, 1) = '#' and SUBSTRING (@buffer, 2, 1) <> '#'
BEGIN
--use LEFT 25 to make sure the local temp table name isn't too long for the @table varchar(100) variable.
SET @table = '<br>Table ' + LEFT (@dataSource, 25) + ' is a local temp table. Must use a global temp or permanent table.<br>';
RETURN;
END;
--set up the object name in the right format so you can check the OBJECT_ID
ELSE IF (SUBSTRING (@buffer, 1, 2) = '##')
BEGIN
SET @db = 'tempdb';
SET @table = @dataSource;
SET @object = @db + '..' + @table; --need to include tempdb so OBJECT_ID finds the temp table
END;
ELSE
BEGIN
--deal with schema
SET @db = SUBSTRING (@buffer, 1, charindex ('.', @buffer) - 1);
SET @buffer = replace (@buffer, @db + '.', '');
IF SUBSTRING (@buffer, 1, 1) = '.'
BEGIN
SET @schema = '..';
SET @buffer = replace (@buffer, '.', '');
END
ELSE
BEGIN
SET @schema = SUBSTRING (@buffer, 1, charindex ('.', (@buffer)) - 1);
SET @buffer = replace (@buffer, @schema + '.', '');
END
SET @table = @buffer;
SET @object = @dataSource;
END;
--does our data source exist? Check the object_id. If object does not exist, return.
SET @objectId = OBJECT_ID (@object, 'U');
IF @objectId is NULL
BEGIN
SET @db = NULL;
SET @table = '<br>Table ' + @dataSource + ' does not exist or is improperly qualified.<br>';
RETURN;
END;
--we have a valid data source. Check that it has rows and notify if empty.
SET @sql = 'SELECT @rcd_cnt = count(*) from ' + @dataSource;
EXEC master.sys.sp_executesql @sql, N'@rcd_cnt int OUTPUT', @rcd_cnt OUTPUT;
IF @rcd_cnt = 0
BEGIN
SET @db = NULL;
SET @table = '<br>Table ' + @dataSource + ' is empty.<br>';
RETURN;
END;
END
I also have a version of HtmlTable that creates a CSV file from a table (instead of an HTML table).