SQLServerCentral Article

Creating Markdown Formatted Text for Results from T-SQL


Markdown documents are becoming increasingly more popular and relevant with the emergence of notebooks. Markdown is a markup language for creating formatted text. It is widely used in tools for collaboration, tools for creating documentation and notebooks. Formatting is easy to understand, readable, simple to adopt, and agnostic. I can use a markdown document on different platforms (JupyterHub, Databricks, Github, etc.), with different scripting languages (Python, R, Julia, Scala, SQL), and among different operating systems (Linux, Windows, Unix, MacOS, etc.).

Azure Data Studio (ADS) has offered creation of notebooks for a long time and delivers the basic concept of notebooks: executable code and formatted text in the same document. These notebooks can be execute many different script language, since their kernel can be based on R, Python, Powershell, Scala interpreters.

Markdown text in Azure Data Studio

I normally use notebooks in ADS with all the different kernels. Switching between kernels gives me much more flexibility to execute different functions that are available for me in different kernels. This also applies for T-SQL. In the case below, I have created a new notebook, and executed a simple SELECT statement. The notebook gives me back the results, and I can also add the additional Markdown text as a comment to code. When I want to share this document with others, they will receive a complete notebook with code, comments, and text. In this way, they will have a better chance for data exploration, data understanding, and further collaboration with others.

These capabilities with select statements (as shown in second cell) are created, so that people, I will be sharing notebook with, and myself, get most from your result set. I can also sort and, filter the columns without rerunning the SELECT statement. Furthermore, I can export the result set to CSV, JSON or XML is another way to use the dataset with any other tool.

And ADS offers me also the easy way to visualize the results. But in addition, I can also create the markdown formatted text of the table content (or result set), so that the results are persistent, can be shared, copied elsewhere or simply added to documentation. This is shown in third cell, which holds the same result set but the results themselves are created with markdown language.

In this article, I will explain how to create markdown formatted result set.

Implementation of Markdown language

I will walk you through the process of creating the markdown formatted text for the T-SQL table output using a stored procedure. You don't need any additional software and no additional requirements in order to to create markdown text. The script is based on T-SQL and is standardize in such way, that you can use it in multiple platforms.

Creating a test data set

First, I will create a simple database with a sample table. In next step, I will populate this table with couple of sample rows for the demo purpose.

,Name VARCHAR(100)
,Salary MONEY
,Height DECIMAL(10,2)
,MaritalStatus CHAR(1)

SELECT 'Tim', 31, 300, 191.2, 1 UNION ALL
SELECT 'Tom', 21, 400, 181.87, 2 UNION ALL
SELECT 'Tam', 51, 500, 176.54, 3

Preparing the data set

The procedure will take as input parameter the table name and not the T-SQL query. For example, I have a long T-SQL query and I want to store the results as markdown text. What I need to do, is to run the T-SQL query and store the results into a persistent table. Procedure will taking this table to export the result as markdown text.

Creating main stored procedure

The process of exporting table rows into markdown format is done through stored procedure. This stored procedure is constructed from three parts:

  • get the column names
  • get the data types
  • add the header and footer to the markdown notebook
  • remove unnecessary annotations
-- Select statement
Author: Tomaz Kastrun
Date: 08.Nov.2021
Description: Turns result set of selected table into Markdown
        EXEC dbo.select2MD
                @table_name = 'TestForMD'
               ,@schema_name = 'dbo'
*/    @table_name VARCHAR(200)
    ,@schema_name VARCHAR(20)
    -- get the columns of the table
        INTO  #temp
        ON t.table_name = c.table_name
        AND t.table_schema = c.table_schema
        AND t.table_Catalog = c.table_Catalog
        t.table_type = 'BASE TABLE'
        AND t.Table_name = @table_name
        AND t.table_schema = @schema_name
            -- Title
            DECLARE @title NVARCHAR(MAX) = (SELECT '##Result for table: _**' + CAST(@table_name AS NVARCHAR(MAX)) + '**_
            ###SchemaName: _'+CAST(@schema_name AS NVARCHAR(MAX)) +'_')

            -- header |name |name2 |name3 |name4 |name5 |name6 
            DECLARE @header VARCHAR(MAX)
            SELECT @header = COALESCE(@header + '**|**', '') + column_name 
            FROM #temp
            ORDER BY Ordinal_position ASC
            SELECT @header = '|**' + @header + '**|'

            -- delimiter |-- |-- |-- |-- |-- |-- 
            DECLARE @nof_columns INT = (SELECT MAX(Ordinal_position) FROM #temp)
            DECLARE @firstLine NVARCHAR(MAX) = (SELECT  REPLICATE('|---',@nof_columns) + '|')  

            SET @MD = @title +CHAR(10) + @header + CHAR(13) + CHAR(10) + @firstLine  + CHAR(10)
            -- body
            DECLARE @body NVARCHAR(MAX)
            SET @body = 'SELECT
            ''|'' + CAST(' 
            DECLARE @i INT = 1
            WHILE @i <= @nof_columns
                DECLARE @w VARCHAR(1000) =  (SELECT column_name FROM #temp WHERE Ordinal_position = @i)
                    SET @body = @body + @w + ' AS VARCHAR(MAX))+ ''|'' + CAST( '
                SET @i = @i + 1
            SET @body  = (SELECT SUBSTRING(@body,1, LEN(@body)-8))
            SET @body = @body + ' FROM ' + @table_name

            DECLARE @bodyTable TABLE(MD VARCHAR(MAX))
            INSERT INTO @BodyTable
            EXEC sp_executesql @body

            DECLARE @body2 NVARCHAR(MAX)
            SELECT @body2 = COALESCE(@body2 + ' ', ' ') + MD + CHAR(10) 
            FROM @bodyTable
            SET @MD = @MD + @body2
            --Addint timestamp
            DECLARE @Timestamp VARCHAR(100) = (SELECT GETDATE())
            DECLARE @UserName VARCHAR(100) = (SELECT SUSER_SNAME())
            DECLARE @FootNote VARCHAR(200) = CHAR(10) + 'Created on: ' + @Timestamp + ' by user: ' + @UserName + CHAR(10)
            SET @MD = @MD + @FootNote
            SELECT @MD

After the procedure reads all the column names and get the data types, it starts constructing the table elements. This elements is »| -- |« and optionally, I can specify text left or right alignment based on data type. This element would be »|:-- |« for left alignment, respectively.

Procedure will also concatenate the content of the table into a string. At the end of the result, procedure will also add the username and timestamp. In this way, I will always know, when the content of the table was converted into markdown and who is the author.

Working with the results

Executing procedure is straightforward. I must the define the name and schema of the table, where I have previously stored the results of my query.

EXEC dbo.select2MD
    @table_name = 'TestForMD'
   ,@schema_name = 'dbo'

In Azure Data Studio, I can copy and paste the content of the result cell into an empty notebook.

In SSMS, I can do the same. Or I can set the query result to »print to text«, instead of having the results to table.

The output in both cases would be the same. This is the example of markdown formatted query results.

##Result for table: _**TestForMD**_
###SchemaName: _dbo_ 

In last case, you can also directly store the table content into markdown file with Powershell script.

-- Store to file with Powershell
Invoke-Sqlcmd -ServerInstance "localhost" -Database "TestMD" -Query "EXEC dbo.select2MD @table_name = 'TestForMD' ,@schema_name = 'dbo'" | Export-Csv "d:\md\result2.md" -NoTypeInformation

This way is convenient when you have larger tables.


Creating markdown text for the table content is a great way to represent your data in a notebook. You will also be able to present your data to end-user better, giving better visibility, and adding the query as a code block (SELECT * FROM TestForMD) with the markdown text. With the use of this procedure, you and people you will be sharing the notebooks with, will be able to create a better documentation and understanding of the data.


You rated this post out of 5. Change rating




You rated this post out of 5. Change rating