用SQL FOR XML创buildHTML表格
我正在SQL Server 2008 R2中使用FOR XML语句创build一个HL7连续性护理文档(CCD)。
我用这种方法做了很多,但这是我第一次在HTML表格中表示部分数据,这给我带来了麻烦。
所以,我有一个表中的以下信息:
Problem | Onset | Status --------------------------------- Ulcer | 01/01/2008 | Active Edema | 02/02/2005 | Active
我正在尝试渲染以下内容
<tr> <th>Problem</th> <th>Onset</th> <th>Status</th> </tr> <tr> <td>Ulcer</td> <td>01/01/2008</td> <td>Active</td> </tr> <tr> <td>Edema</td> <td>02/02/2005</td> <td>Active</td> </tr>
我正在使用这个查询:
SELECT p.ProblemType AS "td" , p.Onset AS "td" , p.DiagnosisStatus AS "td" FROM tblProblemList p WHERE p.PatientUnitNumber = @PatientUnitNumber FOR XML PATH('tr')
我一直得到以下内容:
<tr> <td>Ulcer2008-01-01Active</td> </tr> <tr> <td>Edema2005-02-02Active</td> </tr>
任何人有任何build议?
select (select p.ProblemType as 'td' for xml path(''), type), (select p.Onset as 'td' for xml path(''), type), (select p.DiagnosisStatus as 'td' for xml path(''), type) from tblProblemList p where p.PatientUnitNumber = @PatientUnitNumber for xml path('tr')
要添加标题,你也可以使用union all
。
select (select 'Problem' as th for xml path(''), type), (select 'Onset' as th for xml path(''), type), (select 'Status' as th for xml path(''), type) union all select (select p.ProblemType as 'td' for xml path(''), type), (select p.Onset as 'td' for xml path(''), type), (select p.DiagnosisStatus as 'td' for xml path(''), type) from tblProblemList p where p.PatientUnitNumber = @PatientUnitNumber for xml path('tr')
Mikael的答案有效,但是这样做:
而不是使用FOR XML PATH('tr'),使用FOR XML RAW('tr'),ELEMENTS。 这将防止这些值连接在一起,并给你非常干净的输出。 您的查询将如下所示:
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
我更喜欢使用纯标记追加标题行,这样我就可以更好地控制发生的事情。 完整的代码块看起来像这样:
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>'
编辑
我想添加一些额外的价值,因为需要格式化输出表。
“AS td”别名将在标记中产生<td>value</td>
元素,但不是因为它理解表格单元是td。 此断开连接允许我们创build伪造的HTML元素,可以在查询执行后稍后更新。 例如,如果我想将ProblemType值设为中心alignment,我可以调整元素名称以允许这样做。 我不能添加样式或类到元素名称,因为它破坏了SQL中的别名命名约定,但是我可以创build一个新的元素名称,如tdc。 这将产生<tdc>value</tdc>
元素。 尽pipe这不是有效的标记,但replace语句很容易处理。
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>')
这将创build具有<td class="center">value</td>
格式的单元格元素。 在string顶部的快速块,你会有一个简单的调整中心alignment的值。
我需要解决的另一个情况是在标记中包含链接。 只要单元格中的值是您在href中需要的值,这个问题就很容易解决。 我将展开示例以包含要链接到详细信息URL的ID字段。
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>')
这个例子没有考虑在链接文本中使用单元格中的值,但对于一些CHARINDEX工作来说这是一个可解决的问题。
我最终实现这个系统是基于SQL查询发送HTML电子邮件。 我有一个重复的需要单元格alignment和常见的链接types,所以我把replace函数移动到SQL中的共享标量函数,所以我不必在他们发送电子邮件的所有存储过程。
我希望这增加了一些价值。
这是一个通用的解决scheme,使用FLWOR的基于XML
BASE的FUNCTION
它将把任何SELECT
转换成一个XHTML表格:调用就像这样简单:
SELECT dbo.CreateHTMLTable((SELECT TOP 5 * FROM sys.objects FOR XML RAW,ELEMENTS XSINIL));
现在有五个版本:
- 版本1:简单(标准标签的CSS)
- 版本2:支持
<table>
,<tbody>
和<thead>
CSS类名 - 版本3:额外支持值为
<tr>
CSS类 - 版本4:允许根据
<td>
CSS类引入所有上述的加值 - 版本5:以上所有加上超链接的支持
版本1:简单
CREATE FUNCTION dbo.CreateHTMLTable(@SelectForXmlRawElementsXsinil XML) RETURNS XML AS BEGIN RETURN ( SELECT @SelectForXmlRawElementsXsinil.query('let $first:=/row[1] return <tr> { for $th in $first/* return <th>{local-name($th)}</th> } </tr>') AS thead ,@SelectForXmlRawElementsXsinil.query('for $tr in /row return <tr> { for $td in $tr/* return <td>{string($td)}</td> } </tr>') AS tbody FOR XML PATH('table'),TYPE ); END GO
– 一个模拟数据表
declare @tv table ( id int, username varchar(50), department varchar(50) ) --NULL value in row=2!!! insert into @tv values(1,'tom','finance'),(2,NULL,'business');
– 这是你使用它的方式
SELECT dbo.CreateHTMLTable((SELECT * FROM @tv FOR XML RAW,ELEMENTS XSINIL));
– 清理
DROP FUNCTION dbo.CreateHTMLTable;
返回意识到最后一行的NULL值!
<table> <thead> <tr> <th>id</th> <th>username</th> <th>department</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>tom</td> <td>finance</td> </tr> <tr> <td>2</td> <td /> <td>business</td> </tr> </tbody> </table>
作为一个可能的增强,可以将一个聚合值作为附加parameter passing给一行页脚 ,并将其附加为<tfoot>
版本2:具有CSS布局类的函数
CREATE FUNCTION dbo.CreateHTMLTable ( @SelectForXmlRawElementsXsinil XML ,@tblClass VARCHAR(100) ,@thClass VARCHAR(100) ,@tbClass VARCHAR(100) ) RETURNS XML AS BEGIN RETURN ( SELECT @tblClass AS [@class] ,@thClass AS [thead/@class] ,@SelectForXmlRawElementsXsinil.query('let $first:=/row[1] return <tr> { for $th in $first/* return <th>{local-name($th)}</th> } </tr>') AS thead ,@tbClass AS [tbody/@class] ,@SelectForXmlRawElementsXsinil.query('for $tr in /row return <tr> { for $td in $tr/* return <td>{string($td)}</td> } </tr>') AS tbody FOR XML PATH('table'),TYPE ); END GO
这个电话
SELECT dbo.CreateHTMLTable((SELECT database_id,name FROM sys.databases FOR XML RAW,ELEMENTS XSINIL),'testTable','testHead','testBody');
会导致这个结果
<table class="testTable"> <thead class="testHead"> <tr> <th>database_id</td> <th>name</td> </tr> </thead> <tbody class="testBody"> <tr> <td>1</td> <td>master</td> </tr> <tr> <td>2</td> <td>tempdb</td> </tr> <tr> <td>3</td> <td>model</td> </tr> [...] </tbody> </table>
好的是,省略了类名的NULL
值。
版本3:在行级上引入类名
以下增强function允许将一列引入您想要传入该函数的SELECT
。 如果有一个名为_tr
的列,它将不会显示在标题中,但它的值将用作给定行的<tr>
-element的类名。
这允许根据行值使用CSS
这个额外的列必须是XML本身,以便将值作为预先创build的属性引入。
CREATE FUNCTION dbo.CreateHTMLTable ( @SelectForXmlRawElementsXsinil XML ,@tblClass VARCHAR(100) ,@thClass VARCHAR(100) ,@tbClass VARCHAR(100) ) RETURNS XML AS BEGIN RETURN ( SELECT @tblClass AS [@class] ,@thClass AS [thead/@class] ,@SelectForXmlRawElementsXsinil.query( N'let $first:=/row[1] return <tr> { for $th in $first/*[substring(local-name(),1,3)!="_tr"] return <th>{local-name($th)}</th> } </tr>') AS thead ,@tbClass AS [tbody/@class] ,@SelectForXmlRawElementsXsinil.query( N'for $tr in /row return <tr>{$tr/_tr/tr/@class} { for $td in $tr/*[substring(local-name(),1,3)!="_tr"] return <td>{string($td)}</td> } </tr>') AS tbody FOR XML PATH('table'),TYPE ); END GO
–一些有价值的模型表
DECLARE @tbl TABLE(ID INT, TestText VARCHAR(100)); INSERT INTO @tbl VALUES (1,'NoWarning') ,(2,'No Warning too') ,(3,'Warning') ,(4,'One more warning');
– 所有以“No”开始的值都不会得到属性class=""
SELECT dbo.CreateHTMLTable((SELECT * ,(SELECT CASE WHEN LEFT(TestText,2) != 'No' THEN 'warning' ELSE NULL END AS [@class] FOR XML PATH('tr'),TYPE) AS [_tr] FROM @tbl FOR XML RAW,ELEMENTS XSINIL),'testTable','testHead','testBody'); GO DROP FUNCTION dbo.CreateHTMLTable
结果
<table class="testTable"> <thead class="testHead"> <tr> <th>ID</th> <th>TestText</th> </tr> </thead> <tbody class="testBody"> <tr> <td>1</td> <td>NoWarning</td> </tr> <tr> <td>2</td> <td>No Warning too</td> </tr> <tr class="warning"> <td>3</td> <td>Warning</td> </tr> <tr class="warning"> <td>4</td> <td>One more warning</td> </tr> </tbody> </table>
embedded在HTML中可能看起来像这样:
版本4:另外引入<td>
依赖于CSS的值
以下版本使用FOR XML PATH
来具体指定值的属性:
CREATE FUNCTION dbo.CreateHTMLTable ( @SelectForXmlPathRowElementsXsinil XML ,@tblClass VARCHAR(100) ,@thClass VARCHAR(100) ,@tbClass VARCHAR(100) ) 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>{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 <td>{$td/@class}{string($td)}</td> } </tr>') AS tbody FOR XML PATH('table'),TYPE ); END GO
– 模型表包含NULL
DECLARE @tbl TABLE(ID INT, TestText VARCHAR(100),ShouldNotBeNull INT); INSERT INTO @tbl VALUES (1,'NoWarning',1) ,(2,'No Warning too',2) ,(3,'Warning',3) ,(4,NULL,NULL) ,(5,'Warning',5) ,(6,'One more warning',6);
– 实际select使用SELECT ... FOR XML PATH
作为内部select
SELECT dbo.CreateHTMLTable ( ( SELECT CASE WHEN LEFT(TestText,2) != 'No' THEN 'warning' ELSE NULL END AS [@class] --The first @class is the <tr>-class ,ID ,'center' AS [TestText/@class] --a class within TestText (appeary always) ,TestText ,CASE WHEN ShouldNotBeNull IS NULL THEN 'MarkRed' END AS [ShouldNotBeNull/@class] --a class within ShouldNotBeNull (appears only if needed) ,ShouldNotBeNull FROM @tbl FOR XML PATH('row'),ELEMENTS XSINIL),'testTbl','testTh','testTb' ); GO DROP FUNCTION dbo.CreateHTMLTable
结果是: <tr>
– 只有按需,第二个<td>
总是center
和第三个获得MarkRed
的需求
<table class="testTbl"> <thead class="testTh"> <tr> <th>ID</th> <th>TestText</th> <th>ShouldNotBeNull</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">No Warning too</td> <td>2</td> </tr> <tr class="warning"> <td>3</td> <td class="center">Warning</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">One more warning</td> <td>6</td> </tr> </tbody> </table>
embedded在HTML中可能看起来像这样:
版本5:所有VERSION 4加上对超级链接的支持
以下将使用表的Link
列dynamic添加超链接:
CREATE FUNCTION dbo.CreateHTMLTable ( @SelectForXmlPathRowElementsXsinil XML ,@tblClass VARCHAR(100) ,@thClass VARCHAR(100) ,@tbClass VARCHAR(100) ) 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>{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
– 一些有价值的模型表
DECLARE @tbl TABLE(ID INT, TestText VARCHAR(100),Link VARCHAR(MAX),ShouldNotBeNull INT); INSERT INTO @tbl 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);
– 查询将Link
添加到元素(如果未定义,则为NULL
)
SELECT dbo.CreateHTMLTable ( ( SELECT CASE WHEN LEFT(TestText,2) != 'No' THEN 'warning' ELSE NULL END AS [@class] --The first @class is the <tr>-class ,ID ,'center' AS [TestText/@class] --a class within TestText (appeary always) ,Link AS [TestText/@link] --a mark to pop up as link ,TestText ,CASE WHEN ShouldNotBeNull IS NULL THEN 'MarkRed' END AS [ShouldNotBeNull/@class] --a class within ShouldNotBeNull (appears only if needed) ,ShouldNotBeNull FROM @tbl FOR XML PATH('row'),ELEMENTS XSINIL),'testTbl','testTh','testTb' ); GO DROP FUNCTION dbo.CreateHTMLTable
结果
<table class="testTbl"> <thead class="testTh"> <tr> <th>ID</th> <th>TestText</th> <th>ShouldNotBeNull</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>
embedded在HTML中可能看起来像这样
所有这些答案工作正常,但我最近碰到一个问题,我想在HTML上有条件格式。 我希望td的style属性根据数据而变化。 基本格式与添加设置td =类似:
declare @body nvarchar(max) set @body = cast (select 'color:red' as 'td/@style', td = p.ProblemType, '', td = p.Onset, '', td = p.DiagnosisStatus, '' from tblProblemList p where p.PatientUnitNumber = @PatientUnitNumber for xml path('tr'), type) as nvarchar(max)
要添加条件格式,只需要添加一个case语句:
declare @body nvarchar(max) set @body = cast select cast (case when p.ProblemType = 1 then 'color:#ff0000;' else 'color:#000;' end as nvarchar(30)) as 'td/@style', td = p.ProblemType, '', td = p.Onset, '', td = p.DiagnosisStatus, '' from tblProblemList p where p.PatientUnitNumber = @PatientUnitNumber for xml path('tr'), type) as nvarchar(max)
我之前遇到过这个问题。 这是我如何解决它:
SELECT p.ProblemType AS "td" , '' AS "text()" , p.Onset AS "td" , '' AS "text()" , p.DiagnosisStatus AS "td" FROM tblProblemList p WHERE p.PatientUnitNumber = @PatientUnitNumber FOR XML PATH('tr')
尝试这个:
FOR XML raw, elements, root('tr')
已经有了很大的答案。 我只是想补充一点,你也可以在你的查询中使用样式,这在devise方面可能是很好的。
BEGIN SET NOCOUNT ON; DECLARE @htmlOpenTable VARCHAR(200) = '<table style="border-collapse: collapse; border: 1px solid #2c3e50; background-color: #f9fbfc;">' DECLARE @htmlCloseTable VARCHAR(200) = '</table>' DECLARE @htmlTdTr VARCHAR(max) = ( SELECT 'border-top: 1px solid #2c3e50' as [td/@style], someColumn as td, '', 'border-top: 1px solid #2c3e50' as [td/@style], someColumn as td, '' FROM someTable WHERE someCondition FOR XML PATH('tr') ) SELECT @htmlOpenTable + @htmlTdTr + @htmlCloseTable END
其中someColumn
是你的表中的属性
someTable
表是你的表名
如果您使用WHERE
someCondition
是可选的
请注意,查询只select了两个属性,您可以根据需要添加任意数量的属性,也可以更改样式。
当然,你可以用其他方式使用样式。 实际上,使用外部CSS总是比较好,但是知道如何放置内联样式是个好习惯,因为您可能需要它们