query - Crear tabla HTML con SQL FOR XML
sql server query to html table (8)
Esta es una solución genérica con FUNCTION
en base de XML
utilizando FLWOR
Transformará cualquier SELECT
en una tabla XHTML.
Funciona (probado) con 2008R2 +, pero estoy bastante seguro de que esto funcionaría en 2008, incluso en 2005. Si alguien quiere verificar esto, por favor deje un comentario. Gracias
La siguiente función reemplaza todas las funciones que proporcioné anteriormente (consulte la versión anterior si es necesario)
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
La llamada más fácil
Una maqueta con algunos valores
DECLARE @tbl TABLE(ID INT, [Message] VARCHAR(100));
INSERT INTO @tbl VALUES
(1,''Value 1'')
,(2,''Value 2'');
--La llamada debe incluir SELECT ... FOR XML
en paranthesis!
--haga clic en ejecutar fragmento para ver el resultado!
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>
Si necesita encabezados con espacios en blanco
Si su tabla contiene una columna con un espacio en blanco en su nombre , o si desea establecer el título de una columna de forma manual ( soporte de varios idiomas ), o si desea reemplazar un CamelCaseName con un subtítulo sobrescrito, puede pasar esto como atributo:
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>
Soporte completo de CSS e hipervínculos
Puede usar atributos para pasar un enlace o una clase basada en filas e incluso una basada en valores para marcar columnas e incluso celdas para el estilo de 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>
Como una posible mejora, se puede pasar en una fila de pie de página con valores agregados como parámetro adicional y anexarlo como <tfoot>
Estoy creando un documento de continuidad de atención (CCD) HL7 utilizando instrucciones FOR XML en SQL Server 2008 R2.
He hecho MUCHO con este método, pero esta es la primera vez que tengo que representar parte de los datos en una tabla HTML, lo que me está causando problemas.
Entonces, tengo la siguiente información en una tabla:
Problem | Onset | Status
---------------------------------
Ulcer | 01/01/2008 | Active
Edema | 02/02/2005 | Active
y estoy tratando de renderizar lo siguiente
<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>
Estoy usando esta consulta:
SELECT p.ProblemType AS "td"
, p.Onset AS "td"
, p.DiagnosisStatus AS "td"
FROM tblProblemList p
WHERE p.PatientUnitNumber = @PatientUnitNumber
FOR XML PATH(''tr'')
Y sigo recibiendo lo siguiente:
<tr>
<td>Ulcer2008-01-01Active</td>
</tr>
<tr>
<td>Edema2005-02-02Active</td>
</tr>
¿Alguien tiene algún consejo?
La respuesta de Mikael funciona, pero también lo hará esto:
En lugar de usar FOR XML PATH (''tr''), use FOR XML RAW (''tr''), ELEMENTS. Esto evitará que los valores se concatenen y le dará un resultado muy limpio. Su consulta se vería así:
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
Prefiero agregar la fila de encabezado con marcado puro para que pueda tener un poco mejor control sobre lo que está sucediendo. El bloque de código completo se vería así:
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>''
EDITAR
Quería agregar un valor extra que surgió en función de la necesidad de formatear la tabla de salida.
El alias "AS td" producirá elementos <td>value</td>
en el marcado pero no porque entienda que una celda de tabla es un td. Esta desconexión nos permite crear elementos HTML falsos que se pueden actualizar más tarde después de que se haya ejecutado la consulta. Por ejemplo, si quería que el valor ProblemType estuviera centrado en el centro, puedo ajustar el nombre del elemento para permitir esto. No puedo agregar un estilo o clase al nombre del elemento porque rompe las convenciones de nombres de alias en SQL, pero puedo crear un nuevo nombre de elemento como tdc. Esto producirá elementos <tdc>value</tdc>
. Si bien esto no es un marcado válido de ninguna manera, es fácil de manejar para una instrucción de reemplazo.
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>'')
Esto creará elementos de celda con el formato <td class="center">value</td>
. Un bloque rápido en la parte superior de la cadena y tendrá valores alineados en el centro con un simple ajuste.
Otra situación que necesitaba resolver era la inclusión de enlaces en el marcado. Siempre que el valor en la celda sea el valor que necesita en el href, esto es bastante fácil de resolver. Ampliaré el ejemplo para incluir un campo de ID que quiero vincular a una URL de detalle.
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>'')
Este ejemplo no explica el uso del valor en la celda dentro del texto del enlace, pero ese es un problema que se puede solucionar con algún trabajo CHARINDEX.
Mi implementación final de este sistema fue para enviar correos electrónicos HTML basados en consultas SQL. Tenía una necesidad repetida de alineación celular y tipos de enlaces comunes, así que moví las funciones de reemplazo a una función escalar compartida en SQL, así que no tuve que tenerlas en todos mis procedimientos almacenados que enviaron correos electrónicos.
Espero que esto agregue algo de valor.
Me encontré con este problema hace un rato. Así es como lo resolví:
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'')
Prefiero hacer esto:
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
Prueba esto:
FOR XML raw, elements, root(''tr'')
Todas estas respuestas funcionan bien, pero recientemente me encontré con un problema en el que quería tener un formato condicional en html, es decir. Quería que la propiedad de estilo de la td variara en función de los datos. El formato básico es similar con la adición de la configuración 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)
Para agregar un formato condicional a esto, simplemente necesita agregar una declaración de caso:
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)
Ya hay muchas respuestas. Solo quería agregar que también puede usar estilos dentro de su consulta que podrían ser buenos en términos de diseño.
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
Donde someColumn
es su atributo de su tabla
Y someTable
es tu nombre de tabla
Y someCondition
es opcional si está usando WHERE
claus
Tenga en cuenta que la consulta solo selecciona dos atributos, puede agregar tantos como desee y también puede cambiar los estilos.
Por supuesto, puedes usar estilos de otras maneras. De hecho, siempre es mejor usar CSS externo, pero es una buena práctica saber cómo poner estilos en línea porque puede que los necesites.
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'')
Para agregar el encabezado también puedes usar 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'')