使用 SQL FOR XML 创建 HTML 表
Posted
技术标签:
【中文标题】使用 SQL FOR XML 创建 HTML 表【英文标题】:Create HTML Table with SQL FOR XML 【发布时间】:2011-10-28 12:30:12 【问题描述】:我正在使用 SQL Server 2008 R2 中的 FOR XML 语句创建 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>
有人有什么建议吗?
【问题讨论】:
嗨,你可能会感兴趣:我刚刚发布了我的函数 (see my answer) 的VERSION 5
,它支持 CSS 类甚至动态超链接。
【参考方案1】:
我更喜欢这样做:
select
convert(xml,
(
select 'column1' as th,
'column2' as th
for xml raw('tr'),elements
)),
convert(xml,
(
select t1.column1 as td,
t1.column2 as td
from #t t1
for xml raw('tr'),elements
))
for xml raw('table'),elements
【讨论】:
什么是元素? elements是sql xml中的一个关键词:docs.microsoft.com/it-it/sql/relational-databases/xml/…【参考方案2】:这是一个通用解决方案,在 XML
-base 上使用 FUNCTION
使用 FLWOR
它将任何SELECT
转换为XHTML 表格。
它适用于 2008R2+ (已测试),但我很确定这将在 2008 年有效,甚至可能在 2005 年也有效。如果有人想验证这一点,请发表评论。谢谢
以下函数替换了我之前提供的所有各种函数(如果需要,请参阅之前的版本)
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/@classstring($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, [Message] VARCHAR(100));
INSERT INTO @tbl VALUES
(1,'Value 1')
,(2,'Value 2');
--调用必须将SELECT ... FOR XML
括在括号中!
--点击运行sn-p查看结果!
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>
如果您需要带有空格的标题
如果您的表格包含名称中包含空白的列,或者如果您想手动设置列的标题(多语言支持!),或者如果您想用写出的标题替换 CamelCaseName,您可以将其作为属性传递:
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>
完整的 CSS 支持和超链接
您可以使用属性传递链接或基于行甚至基于值的类来标记列甚至单元格以进行 CSS 样式设置。
--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>
作为一种可能的增强功能,可以传入一个 one-row-footer,其中包含聚合值作为附加参数,并将其附加为 <tfoot>
【讨论】:
不需要文本替换。除非列名中有空格。你应该这样做。为了可读性。此外,我更喜欢使用计数器为每个标签添加一个单独的类,以便访问具有有限 css Outlook 支持的元素。 @nurettin,我刚刚用更好的方法完全重写了这个答案来处理列的标题。 添加一个标题属性并检查它是否存在是一个好主意。干得好! @Markov,在我的最后一个示例中检查类 warning 是如何有条件地绑定到<tr>
的。您可以通过 CSS 将此类设置为任何可见格式。
@Markov 抱歉,这个答案来得太晚了...您可以使用上面的方法并将class="warning"
替换为字符串基础上的任何样式...【参考方案3】:
已经有很多答案了。我只是想补充一点,您还可以在查询中使用样式,这在设计方面可能会很好。
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
claus,someCondition
是可选的
请注意,查询仅选择两个属性,您可以添加任意数量的属性,也可以更改样式。
当然,您可以通过其他方式使用样式。事实上,使用外部 CSS 总是更好,但最好知道如何放置内联样式,因为您可能需要它们
【讨论】:
顺便说一句,SET NOCOUNT ON
不是运行此代码所必需的。我把它放在那里是因为我在编写查询时正在测试其他东西【参考方案4】:
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。这种断开连接允许我们创建假的 HTML 元素,这些元素可以在查询执行后进行更新。例如,如果我想让 ProblemType 值居中对齐,我可以调整元素名称以允许这样做。我无法为元素名称添加样式或类,因为它破坏了 SQL 中的别名命名约定,但我可以创建一个新的元素名称,例如 tdc。这将产生<tdc>value</tdc>
元素。虽然这在任何方面都不是有效的标记,但替换语句很容易处理。
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>')
这将创建格式为<td class="center">value</td>
的单元格元素。字符串顶部的快速块,您将通过简单的调整获得居中对齐的值。
我需要解决的另一种情况是在标记中包含链接。只要单元格中的值是您在 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 电子邮件。我反复需要单元格对齐和通用链接类型,因此我将替换函数移到 SQL 中的共享标量函数中,这样我就不必在所有发送电子邮件的存储过程中使用它们。
我希望这会增加一些价值。
【讨论】:
嗨,你可能会感兴趣:我刚刚发布了我的函数 (see my answer) 的VERSION 5
,它支持 CSS 类甚至动态超链接。无需文字替换...【参考方案5】:
所有这些答案都很好,但我最近遇到了一个问题,我想在 html 上设置条件格式,即。我希望 td 的样式属性根据数据而变化。基本格式与添加设置 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)
【讨论】:
【参考方案6】:我不久前遇到了这个问题。以下是我的解决方法:
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')
【讨论】:
【参考方案7】: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')
【讨论】:
很好的答案,谢谢!现在有一个问题;如何处理 NULL 日期?我不想使用 XSINIL,因为我有一个名称空间要稍后添加,而且我似乎无法 CASE 它,因为它呈现为 1900-01-01T00:00:00 @David Walker - 你可以使用coalesce(convert(varchar(23), p.Onset, 126), '')
嗨,你可能会感兴趣:我刚刚发布了我的函数 (see my answer) 的 VERSION 5
,它支持 CSS 类甚至动态超链接。
@MikaelEriksson 无论如何都要附加“表格”标签?
@user793468 尝试在查询末尾添加, root('table')
。【参考方案8】:
试试这个:
FOR XML raw, elements, root('tr')
【讨论】:
这会将<td>
节点作为子节点放入<row>
节点。以上是关于使用 SQL FOR XML 创建 HTML 表的主要内容,如果未能解决你的问题,请参考以下文章
如何将类名或样式名添加到使用 SQL 'FOR XML' 生成的 HTML 标记中