web-dev-qa-db-fra.com

Créer une table HTML avec SQL FOR XML

Je crée un document HL7 Continuity of Care (CCD) à l'aide d'instructions FOR XML dans SQL Server 2008 R2.

J'ai beaucoup utilisé cette méthode, mais c'est la première fois que je dois représenter une partie des données dans un tableau HTML, ce qui me pose problème.

Donc, j'ai les informations suivantes dans un tableau:

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

et j'essaie de rendre ce qui suit

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

J'utilise cette requête:

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

Et je continue à recevoir ce qui suit:

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

Quelqu'un a un conseil?

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

Pour ajouter également l'en-tête, vous pouvez utiliser 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

La réponse de Mikael fonctionne mais il en ira de même:

Plutôt que d'utiliser FOR XML PATH ('tr'), utilisez FOR XML RAW ('tr'), ELEMENTS. Ceci empêchera les valeurs d'être concaténées et vous donnera une sortie très propre. Votre requête ressemblerait à ceci:

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

Je préfère ajouter la ligne d'en-tête en utilisant du balisage pur afin d'avoir un peu plus de contrôle sur ce qui se passe. Le bloc de code complet ressemblerait à ceci:

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

MODIFIER

Je voulais ajouter une valeur supplémentaire que j'ai trouvée en raison de la nécessité de formater le tableau de sortie.

L'alias "AS td" produira des éléments <td>value</td> dans le balisage, mais pas parce qu'il comprend qu'une cellule de tableau est un td. Cette déconnexion nous permet de créer de faux éléments HTML pouvant être mis à jour ultérieurement une fois la requête exécutée. Par exemple, si je voulais que la valeur ProblemType soit centrée, je peux modifier le nom de l'élément pour permettre cela. Je ne peux pas ajouter de style ou de classe au nom de l'élément car il enfreint les conventions de dénomination des alias dans SQL, mais je peux créer un nouveau nom d'élément tel que tdc. Cela produira des éléments <tdc>value</tdc>. Bien qu'il ne s'agisse en aucun cas d'un balisage valide, il est facile de gérer une instruction de remplacement.

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

Cela créera des éléments de cellule au format <td class="center">value</td>. Un bloc rapide en haut de la chaîne et vous aurez des valeurs alignées au centre avec un simple Tweak.

Une autre situation que je devais résoudre était l'inclusion de liens dans le balisage. Tant que la valeur dans la cellule est la valeur dont vous avez besoin dans le href, c'est assez facile à résoudre. Je développerai l’exemple pour inclure un champ d’identification que je souhaite lier à une URL de détail.

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

Cet exemple ne tient pas compte de l'utilisation de la valeur dans la cellule à l'intérieur du texte du lien, mais il s'agit d'un problème pouvant être résolu avec certains travaux CHARINDEX.

Ma dernière implémentation de ce système consistait à envoyer des courriers électroniques HTML basés sur des requêtes SQL. Comme j'avais un besoin répété d'alignement de cellules et de types de liens communs, j'ai déplacé les fonctions de remplacement dans une fonction scalaire partagée en SQL afin de ne pas avoir à les inclure dans toutes mes procédures stockées envoyant un courrier électronique.

J'espère que cela ajoute de la valeur.

24
Chris Porter

Ceci est une solution générique avec une FUNCTION sur XML- base en utilisant FLWOR

Cela transformera toute SELECT en une table XHTML.

Cela fonctionne (testé) avec 2008R2 +, mais je suis à peu près sûr que cela fonctionnerait en 2008, voire même en 2005. Si quelqu'un veut vérifier cela, laissez s'il vous plaît un commentaire. THX

La fonction suivante remplace toutes les fonctions fournies précédemment (voir la version précédente si nécessaire)

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

L'appel le plus facile

Un tableau de maquette avec des valeurs

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

- L’appel doit inclure le SELECT ... FOR XML entre parenthèses!
-- cliquez exécuter le fragment pour voir le résultat!

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 vous avez besoin d’en-têtes avec des blancs

Si votre table contient une colonne avec un nom vide, ou si vous souhaitez définir manuellement la légende d'une colonne (support de multi langugage!), Ou si vous souhaitez remplacer un CamelCaseName avec une légende non écrite, vous pouvez passer ceci comme attribut: 

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>

Prise en charge complète de CSS et hyper-liens

Vous pouvez utiliser des attributs pour passer sur un lien ou une classe basée sur des lignes et même sur des valeurs pour marquer des colonnes et même des cellules pour un style 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>

Comme amélioration possible, vous pouvez passer un paramètre one-row-footer avec des valeurs agrégées en tant que paramètre supplémentaire et l'ajouter sous la forme <tfoot>

16
Shnugo

Toutes ces réponses fonctionnent bien, mais je me suis heurté récemment à un problème où je voulais avoir un formatage conditionnel sur le code HTML, par exemple. Je voulais que la propriété de style du td varie en fonction des données. Le format de base est similaire avec l’ajout du paramètre 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)

Pour ajouter une mise en forme conditionnelle à cela, vous devez simplement ajouter une instruction 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)
2
CCarter

J'ai rencontré ce problème il y a quelque temps. Voici comment je l'ai résolu:

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

je préfère faire ceci:

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

Il y a déjà une formidable réponse. Je voulais juste ajouter que vous pouvez également utiliser des styles dans votre requête, ce qui peut être un avantage en termes de conception.

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 est votre attribut de votre table

Et someTable est le nom de votre table

Et someCondition est facultatif si vous utilisez WHERE claus

Veuillez noter que la requête ne sélectionne que deux attributs, vous pouvez en ajouter autant que vous le souhaitez et vous pouvez également modifier les styles.

Bien sûr, vous pouvez utiliser les styles de différentes manières. En fait, il est toujours préférable d'utiliser un CSS externe, mais il est recommandé de savoir comment mettre des styles en ligne car vous pourriez en avoir besoin.

0
Ahmad Shli

Essaye ça:

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