Generating HTML reports from SQL Server

от автора

В относительно недавнем прошлом, возникла задача автоматизировать процесс генерации и рассылки HTML отчетов руководству по продажам за текущий месяц. Так уж вышло, что для каждого руководящего лица создавались отдельные таблицы с необходимой только им информацией.

Поскольку, для каждого отчета, все делалось вручную, для начала мы решили воспользоваться возможностями dbForge, который позволял делать экспорт группы таблиц в HTML формат.

Однако, рассылку по-прежнему приходилось формировать вручную, что, мягко говоря, было нерациональным.

Было решено генерировать HTML со стороны сервера базы данных и через Database Mail формировать рассылку путем выполнения команды sp_send_dbmail.

Большинство примеров создавали разметку вручную — это было не слишком эффективным подходом. При этом я не нашел универсального решения, позволяющего работать с таблицой имеющей произвольную структуру.

Чтобы заполнить этот пробел предлагаю на рассмотрение мой вариант решения.

Из системного представления получаем список столбцов для требуемой таблицы:

DECLARE @object_name SYSNAME       , @object_id INT 	  , @SQL NVARCHAR(MAX)  SELECT @object_name = '[dbo].[Products]'      , @object_id = OBJECT_ID(@object_name)  SELECT @SQL = 'SELECT  	  [header/style/@type] = ''text/css'' 	, [header/style] = '' 		table {border-collapse:collapse;}  		td, table { 			border:1px solid silver;  			padding:3px; 		} 		th, td { 			vertical-align: top;  			font-family: Tahoma;  			font-size: 8pt;  			text-align: left; 		}'' 	, body = ( SELECT * FROM ( 	SELECT tr = ( 		SELECT * FROM ( 			VALUES ' + 			STUFF(CAST(( 				SELECT ', (''' + c.name + ''')' 				FROM sys.columns c WITH(NOLOCK) 				WHERE c.[object_id] = @object_id 					AND c.system_type_id NOT IN (34, 36, 98, 128, 129, 130, 165, 173, 189, 241) 				ORDER BY c.column_id 				FOR XML PATH(''), TYPE) AS NVARCHAR(MAX)), 1, 2, '') + ' 		) t (th) 		FOR XML PATH('''') 	) 	UNION ALL   	SELECT ( 		SELECT * FROM ( 			VALUES' + STUFF(CAST(( 			SELECT ', ' +  				CASE WHEN c.is_nullable = 1 					THEN '(ISNULL(' ELSE '(' END + 				CASE WHEN TYPE_NAME(c.system_type_id) NOT IN ('nvarchar', 'nchar', 'varchar', 'char')  					THEN 'CAST(' + '[' + c.name + '] AS NVARCHAR(MAX))' ELSE '[' + c.name + ']' END + 				CASE WHEN c.is_nullable = 1  					THEN ',''''))' ELSE ')' END 			FROM sys.columns c WITH(NOLOCK) 			WHERE c.[object_id] = @object_id 				AND c.system_type_id NOT IN (34, 36, 98, 128, 129, 130, 165, 173, 189, 241) 			ORDER BY c.column_id 			FOR XML PATH(''), TYPE) AS NVARCHAR(MAX)), 1, 2, ' ') + ' 	) t (td)  	FOR XML PATH(''''), TYPE)  	FROM ' + @object_name + ' ) t FOR XML PATH(''''), ROOT(''table''), TYPE ) FOR XML PATH(''''), ROOT(''html''), TYPE'  PRINT @SQL EXEC sys.sp_executesql @SQL 

Далее динамическим SQL создаем запрос, который генерирует XML:

SELECT  	  [header/style/@type] = 'text/css' 	, [header/style] = 'css style ...' 	, body = ( 		SELECT *  		FROM ( 			SELECT tr = ( 				SELECT *  				FROM ( 					VALUES ('column_name1', 'column_name2', ...) 				) t (th) 				FOR XML PATH('') 			) 			UNION ALL  			SELECT ( 				SELECT *  				FROM ( 					VALUES ([column_value1], [column_value2], ...)  				)t (td)  				FOR XML PATH(''), TYPE 			)  			FROM [table] 		) t 		FOR XML PATH(''), ROOT('table'), TYPE 	) FOR XML PATH(''), ROOT('html'), TYPE 

При этом столбцы, содержащие специфичные типы данных (например, UNIQUEIDENTIFIER) в генерируемый отчет не включаются:

SELECT name  FROM sys.types WHERE user_type_id IN ( 	34, 36, 98,  	128, 129, 130,  	165, 173, 189, 241 ) 

При выполнении запроса мы получаем следующую HTML разметку, которая прикреплялась к письму:

<html>   <header>     <style type="text/css">         ...     </style>   </header>   <body>     <table>       <tr>         <th>column_name1</th>         <th>column_name2</th>         ...       </tr>       <tr>         <td>column_value1</td>         <td>column_value2</td>         ...       </tr>     </table>   </body> </html> 

Чтобы вручную не выполнять этот скрипт каждую неделю, в SQL Agent был добавлен Job, который автоматически генерировал и отправлял отчеты.

Надеюсь, что приведенное здесь решение будет полезно при решении подобных задач.

PS: Многострочная конструкция VALUES появилась только в SQL Server 2008, поэтому, для экономии времени, привожу пример того же скрипта, но для 2005 сервера:

DECLARE @object_name SYSNAME       , @object_id INT 	  , @SQL NVARCHAR(MAX)  SELECT @object_name = '[dbo].[Products]'      , @object_id = OBJECT_ID(@object_name)  SELECT @SQL = 'SELECT  	  [header/style/@type] = ''text/css'' 	, [header/style] = '' 		table {border-collapse:collapse;}  		td, table { 			border:1px solid silver;  			padding:3px; 		} 		th, td { 			vertical-align: top;  			font-family: Tahoma;  			font-size: 8pt;  			text-align: left; 		}'' 	, body = ( SELECT * FROM ( 	SELECT tr = ( 		SELECT * FROM ( 			' + 			STUFF(CAST(( 				SELECT ' UNION ALL SELECT ''' + c.name + '''' 				FROM sys.columns c WITH(NOLOCK) 				WHERE c.[object_id] = @object_id 					AND c.system_type_id NOT IN (34, 36, 98, 128, 129, 130, 165, 173, 189, 241) 				ORDER BY c.column_id 				FOR XML PATH(''), TYPE) AS NVARCHAR(MAX)), 1, 17, 'SELECT th =') + ' 		) t 		FOR XML PATH('''') 	) 	UNION ALL   	SELECT  ( 		SELECT * FROM ( 			' + STUFF(CAST(( 			SELECT ' UNION ALL SELECT ' +  				CASE WHEN c.is_nullable = 1 					THEN 'ISNULL(' ELSE '' END + 				CASE WHEN TYPE_NAME(c.system_type_id) NOT IN ('nvarchar', 'nchar', 'varchar', 'char')  					THEN 'CAST(' + '[' + c.name + '] AS NVARCHAR(MAX))' ELSE '[' + c.name + ']' END + 				CASE WHEN c.is_nullable = 1  					THEN ','''')' ELSE '' END 			FROM sys.columns c WITH(NOLOCK) 			WHERE c.[object_id] = @object_id 				AND c.system_type_id NOT IN (34, 36, 98, 128, 129, 130, 165, 173, 189, 241) 			ORDER BY c.column_id 			FOR XML PATH(''), TYPE) AS NVARCHAR(MAX)), 1, 17, 'SELECT td =') + ' 	) t 	FOR XML PATH(''''), TYPE)  	FROM ' + @object_name + ' ) t FOR XML PATH(''''), ROOT(''table''), TYPE ) FOR XML PATH(''''), ROOT(''html''), TYPE'  PRINT @SQL EXEC sys.sp_executesql @SQL 

ссылка на оригинал статьи http://habrahabr.ru/post/203076/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *