Create HTML Table with SQL FOR XML

前端 未结 8 1312
夕颜
夕颜 2020-11-22 08:34

I\'m creating a HL7 Continuity of Care Document (CCD) using FOR XML statements in SQL Server 2008 R2.

I\'ve done A LOT with this method, but this is the first time I

相关标签:
8条回答
  • 2020-11-22 08:54

    This is a generic solution with a FUNCTION on XML-base using FLWOR

    It will transform any SELECT into a XHTML table.

    It works (tested) with 2008R2+, but I'm pretty sure this would work on 2008, might be even on 2005, too. If someone wants to verify this, please leave a comment. Thx

    The following function replaces all the various functions I provided before (see the previous version if needed)

    CREATE FUNCTION dbo.CreateHTMLTable
    (
        @SelectForXmlPathRowElementsXsinil XML
       ,@tblClass VARCHAR(100) --NULL to omit this class
       ,@thClass VARCHAR(100)  --same
       ,@tbClass VARCHAR(100)  --same
    )
    RETURNS XML
    AS
    BEGIN
    
    RETURN 
    (
        SELECT @tblClass AS [@class]  
        ,@thClass AS [thead/@class]
        ,@SelectForXmlPathRowElementsXsinil.query(
                  N'let $first:=/row[1]
                    return 
                    <tr> 
                    {
                    for $th in $first/*
                    return <th>{if(not(empty($th/@caption))) then xs:string($th/@caption) else local-name($th)}</th>
                    }
                    </tr>') AS thead
        ,@tbClass AS [tbody/@class]
        ,@SelectForXmlPathRowElementsXsinil.query(
                   N'for $tr in /row
                     return 
                     <tr>{$tr/@class}
                     {
                     for $td in $tr/*
                     return
                     if(empty($td/@link)) 
                     then <td>{$td/@class}{string($td)}</td>
                     else <td>{$td/@class}<a href="{$td/@link}">{string($td)}</a></td>
                     }
                     </tr>') AS tbody
        FOR XML PATH('table'),TYPE
    ) 
    END
    GO
    

    The easiest call

    A mock-up table with some values

    DECLARE @tbl TABLE(ID INT, [Message] VARCHAR(100));
    INSERT INTO @tbl VALUES
     (1,'Value 1')
    ,(2,'Value 2');
    

    --The call must enclose the SELECT ... FOR XML in paranthesis!
    --click run snippet to see the result!

    SELECT dbo.CreateHTMLTable
    (
         (SELECT * FROM @tbl FOR XML PATH('row'),ELEMENTS XSINIL)
         ,NULL,NULL,NULL
    );
    

        <table>
    	  <thead>
    		<tr>
    		  <th>ID</th>
    		  <th>Message</th>
    		</tr>
    	  </thead>
    	  <tbody>
    		<tr>
    		  <td>1</td>
    		  <td>Value 1</td>
    		</tr>
    		<tr>
    		  <td>2</td>
    		  <td>Value 2</td>
    		</tr>
    	  </tbody>
    	</table>

    If you need headers with blanks

    If your table contains a column with a blank in its name, or if you want to set a column's caption manually (multi langugage support!), or if you want to replace a CamelCaseName with an out-written caption, you can pass this as attribute:

    DECLARE @tbl2 TABLE(ID INT, [With Blank] VARCHAR(100));
    INSERT INTO @tbl2 VALUES
     (1,'Value 1')
    ,(2,'Value 2');
    
    SELECT dbo.CreateHTMLTable
    (
         (
         SELECT ID
               ,'The new name' AS [SomeOtherName/@caption] --set a caption 
               ,[With Blank] AS [SomeOtherName] 
         FROM @tbl2 FOR XML PATH('row'),ELEMENTS XSINIL
         )
         ,NULL,NULL,NULL
    );
    

    	<table>
    	  <thead>
    		<tr>
    		  <th>ID</th>
    		  <th>The new name</th>
    		</tr>
    	  </thead>
    	  <tbody>
    		<tr>
    		  <td>1</td>
    		  <td>Value 1</td>
    		</tr>
    		<tr>
    		  <td>2</td>
    		  <td>Value 2</td>
    		</tr>
    	  </tbody>
    	</table>

    Full CSS-support and hyper-links

    You can use attributes to pass over a link or a row-based and even a value-based class to mark columns and even cells for CSS styling.

    --a mock-up table with a row based condition and hyper-links
    
    DECLARE @tbl3 TABLE(ID INT, [With blank] VARCHAR(100),Link VARCHAR(MAX),ShouldNotBeNull INT);
    INSERT INTO @tbl3 VALUES
     (1,'NoWarning',NULL,1)
    ,(2,'No Warning too','http://www.Link2.com',2)
    ,(3,'Warning','http://www.Link3.com',3)
    ,(4,NULL,NULL,NULL)
    ,(5,'Warning',NULL,5)
    ,(6,'One more warning','http://www.Link6.com',6);
    --The query adds an attribute Link to an element (NULL if not defined)
    SELECT dbo.CreateHTMLTable
    (
         (
         SELECT 
           CASE WHEN LEFT([With blank],2) != 'No' THEN 'warning' ELSE NULL END AS [@class]      --The first @class is the <tr>-class
          ,ID
          ,'center' AS [Dummy/@class]                                                    --a class within TestText (appeary always)
          ,Link AS [Dummy/@link]                                                         --a mark to pop up as link
          ,'New caption' AS [Dummy/@caption]                                             --a different caption
          ,[With blank] AS [Dummy]                                                       --blanks in the column's name must be tricked away...
          ,CASE WHEN ShouldNotBeNull IS NULL THEN 'MarkRed' END AS [ShouldNotBeNull/@class] --a class within ShouldNotBeNull (appears only if needed)
          ,'Should not be null' AS [ShouldNotBeNull/@caption]                             --a caption for a CamelCase-ColumnName
          ,ShouldNotBeNull
         FROM @tbl3 FOR XML PATH('row'),ELEMENTS XSINIL),'testTbl','testTh','testTb'
    );
    

    <style type="text/css" media="screen,print">
    .center
    {
        text-align: center;
    }
    .warning
    {
        color: red;
    }
    .MarkRed
    {
        background-color: red;
    }
    table,th
    {
    	border: 1px solid black;
    }
    </style>
    <table class="testTbl">
      <thead class="testTh">
        <tr>
          <th>ID</th>
          <th>New caption</th>
          <th>Should not be null</th>
        </tr>
      </thead>
      <tbody class="testTb">
        <tr>
          <td>1</td>
          <td class="center">NoWarning</td>
          <td>1</td>
        </tr>
        <tr>
          <td>2</td>
          <td class="center">
            <a href="http://www.Link2.com">No Warning too</a>
          </td>
          <td>2</td>
        </tr>
        <tr class="warning">
          <td>3</td>
          <td class="center">
            <a href="http://www.Link3.com">Warning</a>
          </td>
          <td>3</td>
        </tr>
        <tr>
          <td>4</td>
          <td class="center" />
          <td class="MarkRed" />
        </tr>
        <tr class="warning">
          <td>5</td>
          <td class="center">Warning</td>
          <td>5</td>
        </tr>
        <tr class="warning">
          <td>6</td>
          <td class="center">
            <a href="http://www.Link6.com">One more warning</a>
          </td>
          <td>6</td>
        </tr>
      </tbody>
    </table>

    As a possible enhancement one might pass in a one-row-footer with aggregated values as additional parameter and append it as <tfoot>

    0 讨论(0)
  • 2020-11-22 08:59

    Mikael's answer works but so will this:

    Rather than using FOR XML PATH('tr'), use FOR XML RAW('tr'), ELEMENTS. This will prevent the values from being concatenated and give you very clean output. Your query would look like this:

    SELECT  p.ProblemType AS td,
            p.Onset AS td,
            p.DiagnosisStatus AS td
    FROM    tblProblemList p
    WHERE   p.PatientUnitNumber = @PatientUnitNumber
    FOR XML RAW('tr'), ELEMENTS
    

    I prefer to append the header row using pure markup so I can have a little better control over what is happening. The full code block would look something like this:

    DECLARE @body NVARCHAR(MAX)
    SET     @body = N'<table>'
        + N'<tr><th>Problem</th><th>Onset</th><th>Status</th></tr>'
        + CAST((
            SELECT  p.ProblemType AS td,
                    p.Onset AS td,
                    p.DiagnosisStatus AS td
            FROM    tblProblemList p
            WHERE   p.PatientUnitNumber = @PatientUnitNumber
            FOR XML RAW('tr'), ELEMENTS
        ) AS NVARCHAR(MAX))
        + N'</table>'
    

    EDIT

    I wanted to add some extra value that I came up based on the need to format the output table.

    The "AS td" alias will produce <td>value</td> elements in the markup but not because it understands that a table cell is a td. This disconnect allows us to create fake HTML elements that can be later updated after the query has been executed. For instance, if I wanted to the ProblemType value to be center aligned I can tweak the element name to allow for this. I can't add a style or class to the element name because it breaks alias naming conventions in SQL, but I can create a new element name such as tdc. This will produce <tdc>value</tdc> elements. While this is not valid markup in any way, it is easy for a replace statement to handle.

    DECLARE @body NVARCHAR(MAX)
    SET     @body = N'<table>'
        + N'<tr><th>Problem</th><th>Onset</th><th>Status</th></tr>'
        + CAST((
            SELECT  p.ProblemType AS tdc,
                    p.Onset AS td,
                    p.DiagnosisStatus AS td
            FROM    tblProblemList p
            WHERE   p.PatientUnitNumber = @PatientUnitNumber
            FOR XML RAW('tr'), ELEMENTS
        ) AS NVARCHAR(MAX))
        + N'</table>'
    
    SET @body = REPLACE(@body, '<tdc>', '<td class="center">')
    SET @body = REPLACE(@body, '</tdc>', '</td>')
    

    This will create cell elements with the format <td class="center">value</td>. A quick block at the top of the string and you'll have center aligned values with a simple tweak.

    Another situation I needed to solve was inclusion of links in the markup. As long as the value in the cell is the value you need in the href this is pretty easy to solve. I'll expand the example to include an ID field that I want linked to a detail URL.

    DECLARE @body NVARCHAR(MAX)
    SET     @body = N'<table>'
        + N'<tr><th>Problem</th><th>Onset</th><th>Status</th></tr>'
        + CAST((
            SELECT  p.ID as tda
                    p.ProblemType AS td,
                    p.Onset AS td,
                    p.DiagnosisStatus AS td
            FROM    tblProblemList p
            WHERE   p.PatientUnitNumber = @PatientUnitNumber
            FOR XML RAW('tr'), ELEMENTS
        ) AS NVARCHAR(MAX))
        + N'</table>'
    
    SET @body = REPLACE(@body, '<tda>', '<td><a href="http://mylinkgoeshere.com/id/')
    SET @body = REPLACE(@body, '</tda>', '">click-me</a></td>')
    

    This example doesn't account for using the value in the cell inside of the link text but that is a solvable problem with some CHARINDEX work.

    My final implementation of this system was for sending HTML emails based on SQL queries. I had a repeated need for cell alignment and common link types so I moved the replace functions into a shared scalar function in SQL so I didn't have to have them in all my stored procedures that sent email.

    I hope this adds some value.

    0 讨论(0)
提交回复
热议问题