web-dev-qa-db-ja.com

SQL Server 2008 R2でデータベースメールを介してフォーマットされたHTMLメールを送信する必要がある

私の要件は、自動メールを(データベースメール経由のフォーマット済みHTMLメール)として作成することです。ジョブがトリガーされると、データがテーブルに挿入され、その情報がメールとして送信されるようにスケジュールされています。しかし今クライアントはこのフォーマットで要求しました。

私が行った現在のフォーマット

 PO Date:2014-11-26 PO ID:PO1 SAP Ref:0001106102 GRN:1067 DealerID:045 Dealer Name:ABC(Pvt)Ltd. 2 Status:New

フォーマットのようなテーブルでクライアントからリクエストされたフォーマット

PO Date |PO ID| SAP Ref| GRN|DealerID|Dealer Name|Status

これは、Email_Table(_ERROR_MAIL)にデータを挿入するために使用するSQLクエリであり、これは要件に従って必要とされます

IF EXISTS (SELECT * FROM sysobjects WHERE type = 'P' AND name = 'ImpCancelledGRN')
BEGIN
    DROP PROCEDURE [dbo].[ImpCancelledGRN]
END
GO


CREATE PROCEDURE [dbo].[ImpCancelledGRN] 
WITH ENCRYPTION
AS

SET NOCOUNT ON
BEGIN
BEGIN TRY
    -- Begin transaction
    BEGIN TRAN

            INSERT INTO _ERROR_MAIL(Recipients, Subject, CreationDate, IsNew, LastModifiedBy, Body)
            SELECT '[email protected]', 'SAP CANCELLED GRN', GETDATE(), 1, 'sapws',
                'PO Date:' + CONVERT(VARCHAR(10),P.Date,120) + 
                ' PO ID:' + P.ID + 
                ' SAP Ref:' + P.ID2 + 
                ' GRN:' + G.ID + 
                ' Dealer ID:' + D.ID + 
                ' Dealer Name:' + D.Name + 
                ' Status:' + 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;

            UPDATE TxnGRN 
            SET ExpiryDate = GETDATE() 
                , SAPCancellationDate = I.Date
                , SAPCancelledBy = 'SAP'
                , IsCancelled = 1
            FROM I_CancelledGRN I
                INNER JOIN TxnGRN G ON G.ID = I.ID
            WHERE IsCancelled IS NULL;

    -- Commit transaction
    COMMIT TRAN 

END TRY
BEGIN CATCH
    -- Get error message, severity and satus information
    DECLARE @ErrorMessage NVARCHAR(4000), @ErrorSeverity INT, @ErrorState INT;
    SELECT @ErrorMessage = ERROR_MESSAGE(), @ErrorSeverity = ERROR_SEVERITY(),    @ErrorState = ERROR_STATE();
    -- Rollback transaction
    ROLLBACK TRAN;
    -- Log error message details
    INSERT INTO _ERROR_LOG(Module, SubModule, Text, Date)
    VALUES('SAP', '[ImpCancelledGRN]', @ErrorMessage, GETDATE());
    -- Raise error
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
RETURN 0;   
END
SET NOCOUNT OFF
GO     
10
Heisenberg

ここで、メールのHTMLボディ部分を作成する方法を説明します。

ステップ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

ステップ2:データベースメールプロファイルの作成

ステップ3:メールを送信

EXEC sp_send_dbmail 
  @profile_name='DatabaseEmailProfile',
  @copy_recipients ='[email protected]',
  @recipients='[email protected]',
  @subject='Query Result',
  @body=@Body ,
  @body_format = 'HTML' ;
15
AA.SC

私のユーザーは「XML PATH」ソリューションを見つけてそれを使おうとしていますが、プログラマーではなく、混乱しています。 XML PATHの使い方を学ぶ手助けをする代わりに、テーブルの名前を受け取り、HTMLマークアップを含む文字列を返し、テーブルをHTMLテーブルとして表示するプロシージャを作成しました。私のソリューションでは、HTMLテーブルの列名をデータソースから動的に導出しています。

プロシージャ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    

@tableHtml変数には、HTMLテーブルのマークアップのみが含まれます。これは、メール本文に含めるのに適しています。単独で表示するには、出力をhtml、head、およびbodyタグでラップする必要があります。

プロシージャHtmlTableからの出力

contents of @tableHtml

プロシージャ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

プロシージャ「DataSourceCheck」を呼び出して、パラメータ@data_sourceに有効なテーブルの名前が含まれていることを確認していることに注意してください。 DataSourceCheckは別のプロシージャである必要はありません。SQLはHtmlTableプロシージャに直接含めることができますが、モジュール性と再利用性のために分離しています。

プロシージャ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

出力

tempdb
##foo

次に、テーブルをドロップして、何が起こるかを確認します。

DROP table ##foo

DECLARE @table varchar(200), @db varchar(50)
EXEC dbo.DataSourceCheck
    '##foo', 
    @db output,
    @table output
PRINT @db
PRINT @Table

出力

<br>Table ##foo does not exist or is improperly qualified.<br>

プロシージャ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

(HTMLテーブルではなく)テーブルからCSVファイルを作成するバージョンのHtmlTableもあります。

3
Leigh Haynes