Here is my implementation: any query result to html table.
I'm creating some helper procedures to achieve this. These helper procedures are flexible, and may be reused in various contexts.
fnValidateDynamicSql
- to validate passed dynamic statement
spAlterTblByRs
- to save any SQL statement result to #table
. Allows to completely remove dynamic SQL from the code
spQueryResultAsHtmlTable
- creates html table from any passed SQL statement
Enjoy :)
CREATE FUNCTION [dbo].[fnValidateDynamicSql]
(@Sql NVARCHAR(MAX), /* dynamic sql statement */
@Params NVARCHAR(MAX) /* parameters, if dynamic SQL is parametrized. Pass NULL if there are no params */
)
RETURNS NVARCHAR(MAX)
AS
/* Check or @Sql statement is valid
* Returns NULL if valid, exception message otherwise
*/
BEGIN
DECLARE @Result VARCHAR(1000);
IF EXISTS (SELECT NULL
FROM [sys].[dm_exec_describe_first_result_set](@Sql, @Params, 0)
WHERE [error_message] IS NOT NULL
AND [error_number] IS NOT NULL
AND [error_severity] IS NOT NULL
AND [error_state] IS NOT NULL
AND [error_type] IS NOT NULL
AND [error_type_desc] IS NOT NULL)
BEGIN
SELECT @Result = [error_message]
FROM [sys].[dm_exec_describe_first_result_set](@Sql, @Params, 0)
WHERE [column_ordinal] = 0;
END;
IF NULLIF(LTRIM(RTRIM(@Sql)), '') IS NULL
SET @Result = '@Sql is NULL';
RETURN @Result;
END;
GO
CREATE PROCEDURE [dbo].[spAlterTblByRs]
@ErrCode INT OUT,
@ErrMsg VARCHAR(4000) OUT,
@Sql NVARCHAR(MAX), /* Query stmt */
@Params NVARCHAR(MAX) = NULL, /* Query parameters (like in sp_executesql) */
@Tbl NVARCHAR(256), /* Table name */
@DummyCol NVARCHAR(256), /* Dummy column name (will be removed) */
@PopulateTable BIT = NULL /* If 1, then populate altered table by @Sql query data */
AS
/* Alters table by recordset to be used. Populates data, if required. */
BEGIN
SET NOCOUNT ON;
SET ARITHABORT ON;
BEGIN TRY
DECLARE @ERR_CODE_OK INT = 0
, @ERR_CODE_FAILURE INT = 50000;
SET @ErrCode = @ERR_CODE_OK;
IF NULLIF(LTRIM(RTRIM(@Tbl)), '') IS NULL THROW @ERR_CODE_FAILURE, '@Tbl is empty', 1;
IF NULLIF(LTRIM(RTRIM(@DummyCol)), '') IS NULL THROW @ERR_CODE_FAILURE, '@DummyCol is empty', 1;
IF [dbo].[fnValidateDynamicSql](@Sql, @Params) IS NOT NULL
BEGIN
SET @ErrMsg = 'Invalid @Sql received: ' + [dbo].[fnValidateDynamicSql](@Sql, @Params);
;THROW @ERR_CODE_FAILURE, @ErrMsg, 1;
END;
DECLARE @AlterStmt NVARCHAR(MAX) = SPACE(0);
DECLARE @RemColStmt NVARCHAR(MAX) = SPACE(0);
-- prepare existing table alter Stmt by previuos rs structure
SET @AlterStmt = 'ALTER TABLE ' + @tbl + ' ADD ' + CHAR(13);
;WITH [rsStructure] AS (
SELECT
[name]
, [system_type_name]
, [is_nullable]
FROM [sys].[dm_exec_describe_first_result_set](
@Sql
, @Params
, 0
)
)
SELECT
@AlterStmt += QUOTENAME([name]) + SPACE(1) + [system_type_name] + IIF([is_nullable] = 0, ' NOT NULL' , SPACE(0)) + ',' + CHAR(13)
FROM [rsStructure];
SET @AlterStmt = LEFT(@AlterStmt, LEN(@AlterStmt) - 2);
-- finally update table structure
EXEC [sys].[sp_executesql] @AlterStmt;
-- remove dummy column
SET @RemColStmt = 'ALTER TABLE ' + @tbl + ' DROP COLUMN ' + @DummyCol;
EXEC [sys].[sp_executesql] @RemColStmt;
-- populate table with @Sql statement data
IF @PopulateTable = 1
BEGIN
EXEC('INSERT INTO ' + @tbl + ' ' + @sql);
END;
END TRY
BEGIN CATCH
/* Use some error formatting sp instead */
SELECT @ErrCode = ERROR_NUMBER()
, @ErrMsg = ERROR_MESSAGE();
END CATCH
RETURN @ErrCode;
END
GO
GO
CREATE PROCEDURE [dbo].[spQueryResultAsHtmlTable]
@ErrCode INT OUT
, @ErrMsg NVARCHAR(4000) OUT
, @Sql NVARCHAR(MAX)
, @Params NVARCHAR(MAX)
, @HtmlTable NVARCHAR(MAX) OUT
AS
/* Makes Html table by result, returned by provided @Query
*/
BEGIN
SET NOCOUNT ON;
SET ARITHABORT ON;
BEGIN TRY
DECLARE @ERR_CODE_OK INT = 0
, @ERR_CODE_FAILED INT = 50000;
SET @ErrCode = @ERR_CODE_OK;
DECLARE @HtmlAsHml XML
, @ColumnList NVARCHAR(MAX) = SPACE(0);
IF NULLIF(LTRIM(RTRIM(@Sql)), SPACE(0)) IS NULL THROW @ERR_CODE_FAILED, 'Empty @Query received', 1;
IF OBJECT_ID('tempdb..#QueryResult') IS NOT NULL DROP TABLE [#QueryResult];
CREATE TABLE [#QueryResult] ([dummy_col] BIT);
EXEC [dbo].[spAlterTblByRs]
@ErrCode = @ErrCode OUT
, @ErrMsg = @ErrMsg OUT
, @Sql = @Sql
, @Params = @Params
, @Tbl = '#QueryResult'
, @DummyCol = 'dummy_col'
, @PopulateTable = 1;
IF @ErrCode <> 0 THROW @ErrCode, @ErrMsg, 1;
SELECT @ColumnList += IIF([column_ordinal] = 1, SPACE(0), ',') + '[td] = [' + [name] + ']'
FROM [sys].[dm_exec_describe_first_result_set](
@Sql /* @tsql */
, @Params /* @params */
, 0 /* @browse_information_mode */
)
ORDER BY [column_ordinal] ASC;
DECLARE @h XML
, @d XML;
/* Prepare headers */
;WITH [headers] AS (
SELECT [h] = CONVERT(XML, (SELECT
[th] = [name]
FROM [sys].[dm_exec_describe_first_result_set](
@Sql /* @tsql */
, @Params /* @params */
, 0 /* @browse_information_mode */
)
ORDER BY [column_ordinal] ASC
FOR XML PATH(''), ROOT('tr')))
)
SELECT @h = [h] FROM [headers];
/* Prepare rows */
SET @sql = N'
;WITH [data] AS (
SELECT [d] = (SELECT
' + @ColumnList + '
FROM [#QueryResult]
FOR XML RAW (''tr''), ELEMENTS XSINIL, TYPE)
)
SELECT @d = [d] FROM [data]';
SET @params = N'@d xml output';
EXECUTE [sp_executesql]
@stmt = @sql
, @params = @params
, @d = @d OUTPUT;
/* Make table html */
SET @HtmlAsHml = CONVERT(XML, (SELECT [*] = @h, [*] = @d FOR XML PATH('table')));
SET @HtmlAsHml.modify('insert attribute cellpadding {"2"} into (table)[1]')
SET @HtmlAsHml.modify('insert attribute cellspacing {"2"} into (table)[1]')
SET @HtmlAsHml.modify('insert attribute border {"1"} into (table)[1]')
/* Prepare value to be returned */
SET @HtmlTable = CONVERT(NVARCHAR(MAX), @HtmlAsHml);
END TRY
BEGIN CATCH
/* Use some error formatting sp instead */
SELECT @ErrCode = ERROR_NUMBER()
, @ErrMsg = ERROR_MESSAGE();
END CATCH;
RETURN @ErrCode;
END;
GO
/* Usage */
DECLARE
@ErrCode INT
, @ErrMsg NVARCHAR(4000)
, @Sql NVARCHAR(MAX) = 'select top (10) * from sys.tables'
, @HtmlTable NVARCHAR(MAX);
EXEC [dbo].[spQueryResultAsHtmlTable]
@ErrCode = @ErrCode OUT
, @ErrMsg = @ErrMsg OUT
, @Sql = @Sql
, @Params = NULL
, @HtmlTable = @HtmlTable OUT; /* YOur desired html table here */
IF @ErrCode <> 0 THROW @ErrCode, @ErrMsg, 1;