it-swarm.dev

Criar tabela HTML com SQL FOR XML

Estou criando um CCD (Continuity of Care Document) do HL7 usando instruções FOR XML no SQL Server 2008 R2.

Fiz MUITO com esse método, mas esta é a primeira vez que tenho que representar parte dos dados em uma tabela HTML, o que está me causando problemas.

Então, eu tenho as seguintes informações em uma tabela:

  Problem  |   Onset    | Status
---------------------------------
  Ulcer    | 01/01/2008 | Active
  Edema    | 02/02/2005 | Active

e estou tentando processar o seguinte

<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>

Estou 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')

E continuo recebendo o seguinte:

<tr>
  <td>Ulcer2008-01-01Active</td>
</tr>
<tr>
  <td>Edema2005-02-02Active</td>
</tr>

Alguém tem algum conselho?

23
David Walker
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 adicionar o cabeçalho também, você pode 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')
29
Mikael Eriksson

A resposta de Mikael funciona, mas será assim:

Em vez de usar FOR XML PATH ('tr'), use FOR XML RAW ('tr'), ELEMENTS. Isso impedirá que os valores sejam concatenados e forneçam uma saída muito limpa. Sua consulta ficaria assim:

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

Eu prefiro acrescentar a linha de cabeçalho usando marcação pura para que eu possa ter um pouco mais de controle sobre o que está acontecendo. O bloco de código completo seria algo como isto:

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

Eu queria adicionar algum valor extra que surgiu com base na necessidade de formatar a tabela de saída.

O alias "AS td" produzirá elementos <td>value</td> na marcação, mas não porque entende que uma célula da tabela é um td. Essa desconexão nos permite criar elementos HTML falsos que podem ser atualizados posteriormente após a execução da consulta. Por exemplo, se eu quisesse que o valor de ProblemType fosse alinhado ao centro, eu poderia ajustar o nome do elemento para permitir isso. Não consigo adicionar um estilo ou classe ao nome do elemento porque ele quebra as convenções de nomenclatura de alias no SQL, mas posso criar um novo nome de elemento, como tdc. Isso produzirá elementos <tdc>value</tdc>. Embora isso não seja uma marcação válida, é fácil para uma instrução replace manipular.

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>')

Isso criará elementos de célula com o formato <td class="center">value</td>. Um bloco rápido na parte superior da string e você terá valores alinhados ao centro com um Tweak simples.

Outra situação que eu precisava resolver era a inclusão de links na marcação. Contanto que o valor na célula seja o valor que você precisa no href, isso é muito fácil de resolver. Vou expandir o exemplo para incluir um campo de ID que desejo vincular a um URL de detalhes.

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 exemplo não conta para usar o valor na célula dentro do texto do link, mas isso é um problema solucionável com algum trabalho CHARINDEX.

Minha implementação final deste sistema foi para o envio de e-mails HTML com base em consultas SQL. Eu tinha uma necessidade repetida de alinhamento de células e tipos de links comuns, então mudei as funções de substituição para uma função escalar compartilhada em SQL, então não precisei tê-las em todos os meus procedimentos armazenados que enviavam e-mails.

Espero que isso acrescente algum valor.

24
Chris Porter

Esta é uma solução genérica com um FUNCTION em XML- base usando FLWOR

Ele irá transformar qualquer SELECT em uma tabela XHTML.

Ele funciona (testado) com 2008R2 +, mas tenho certeza que isso funcionaria em 2008, pode ser até em 2005 também. Se alguém quiser verificar isso, por favor, deixe um comentário. THX

A função a seguir substitui todas as várias funções que forneci antes (veja a versão anterior, se necessário)

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

A chamada mais fácil

Uma tabela de mock-up com alguns valores

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

- A chamada deve conter o SELECT ... FOR XML em parantesia!
- clique em run snippet para ver o 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>

Se você precisar de cabeçalhos com espaços em branco

Se a sua tabela contiver uma coluna com um espaço em branco em seu nome , ou se você quiser definir manualmente a legenda de uma coluna ( suporte a vários idiomas! ), ou se desejar substituir a CamelCaseName com uma legenda fora de escrita, você pode passar isso 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>

Suporte completo a CSS e hyperlinks

Você pode usar atributos para transmitir um link ou uma classe baseada em linhas e até mesmo uma baseada em valor para marcar colunas e até mesmo células para estilo 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 um possível aprimoramento, pode-se passar um one-row-footer com valores agregados como parâmetro adicional e anexá-lo como <tfoot>

16
Shnugo

Todas essas respostas funcionam bem, mas me deparei com um problema recentemente, onde eu queria ter formatação condicional no html ou seja. Eu queria que a propriedade style do td variasse com base nos dados. O formato básico é semelhante com a adição da configuração 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 adicionar formatação condicional a isso, basta adicionar uma declaração 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)
2
CCarter

Eu corri para este problema há algum tempo atrás. Aqui está como eu resolvi isso:

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')
1
pd1138

eu prefiro fazer isso:

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
0
elle0087

Já existem respostas tremendas. Eu só queria acrescentar que você também pode usar estilos dentro de sua consulta, o que pode ser bom em termos de design.

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

Onde someColumn é o seu atributo da sua tabela

E someTable é o nome da sua tabela

E someCondition é opcional se você estiver usando WHERE claus

Por favor, note que a consulta é apenas selecionando dois atributos, você pode adicionar quantos você quiser e também você pode mudar nos estilos.

Claro que você pode usar estilos de outras maneiras. Na verdade, é sempre melhor usar CSS externo, mas é uma boa prática saber como colocar estilos in-line, porque você pode precisar deles

0
Ahmad Shli

Tente isto:

FOR XML raw, elements, root('tr')
0
Chains