2008-10-10 10 views
14

Je sélectionne dans une table qui contient une colonne XML à l'aide de T-SQL. Je voudrais sélectionner un certain type de noeud et créer une ligne pour chacun d'entre eux. Par exemple, supposons que je sélectionne un personnes. Cette table a une colonne XML pour adresses. Le XML est formatté semblable au suivant:Sélection de noeuds XML en tant que lignes

<address> 
    <street>Street 1</street> 
    <city>City 1</city> 
    <state>State 1</state> 
    <zipcode>Zip Code 1</zipcode> 
</address> 
<address> 
    <street>Street 2</street> 
    <city>City 2</city> 
    <state>State 2</state> 
    <zipcode>Zip Code 2</zipcode> 
</address> 

Comment puis-je obtenir de tels résultats:

Nom                   Ville                   État

Joe Baker       Seattle             WA

Joe Baker       Tacoma           WA

Fred Jones     Vancouver   BC

Répondre

31

Voici votre solution:

/* TEST TABLE */ 
DECLARE @PEOPLE AS TABLE ([Name] VARCHAR(20), [Address] XML) 
INSERT INTO @PEOPLE SELECT 
    'Joel', 
    '<address> 
     <street>Street 1</street> 
     <city>City 1</city> 
     <state>State 1</state> 
     <zipcode>Zip Code 1</zipcode> 
    </address> 
    <address> 
     <street>Street 2</street> 
     <city>City 2</city> 
     <state>State 2</state> 
     <zipcode>Zip Code 2</zipcode> 
    </address>' 
UNION ALL SELECT 
    'Kim', 
    '<address> 
     <street>Street 3</street> 
     <city>City 3</city> 
     <state>State 3</state> 
     <zipcode>Zip Code 3</zipcode> 
    </address>' 

SELECT * FROM @PEOPLE 

-- BUILD XML 
DECLARE @x XML 
SELECT @x = 
(SELECT 
     [Name] 
    , [Address].query(' 
      for $a in //address 
      return <address 
       street="{$a/street}" 
       city="{$a/city}" 
       state="{$a/state}" 
       zipcode="{$a/zipcode}" 
      /> 
     ') 
    FROM @PEOPLE AS people 
    FOR XML AUTO 
) 

-- RESULTS 
SELECT [Name] = T.Item.value('../@Name', 'varchar(20)'), 
     street = T.Item.value('@street' , 'varchar(20)'), 
     city  = T.Item.value('@city' , 'varchar(20)'), 
     state  = T.Item.value('@state' , 'varchar(20)'), 
     zipcode = T.Item.value('@zipcode', 'varchar(20)') 
FROM @x.nodes('//people/address') AS T(Item) 

/* OUTPUT*/ 

Name | street | city | state | zipcode 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
Joel | Street 1 | City 1 | State 1 | Zip Code 1 
Joel | Street 2 | City 2 | State 2 | Zip Code 2 
Kim | Street 3 | City 3 | State 3 | Zip Code 3 
+3

J'ai cherché pendant un moment jusqu'à ce que je trouve cet excellent exemple. Bravo leoinfo! –

-4

Si vous pouvez l'utiliser, l'api LINQ est pratique pour XML:

var addresses = dataContext.People.Addresses 
    .Elements("address") 
     .Select(address => new { 
      street = address.Element("street").Value, 
      city = address.Element("city").Value, 
      state = address.Element("state").Value, 
      zipcode = address.Element("zipcode").Value, 
     }); 
+3

Il travaille dans T-SQL, pas C# – FlySwat

+0

Je sais , mais linq rend à t-sql. – Wyatt

+1

Je suppose que c'est pour une procédure stockée. – FlySwat

1

Voilà comment je le fais génériquement:

Je déchire le code source XML via un appel tel que

 


DECLARE @xmlEntityList xml 
SET @xmlEntityList = 
' 
<ArbitrarilyNamedXmlListElement> 
       <ArbitrarilyNamedXmlItemElement><SomeVeryImportantInteger>1</SomeVeryImportantInteger></ArbitrarilyNamedXmlItemElement> 
       <ArbitrarilyNamedXmlItemElement><SomeVeryImportantInteger>2</SomeVeryImportantInteger></ArbitrarilyNamedXmlItemElement> 
       <ArbitrarilyNamedXmlItemElement><SomeVeryImportantInteger>3</SomeVeryImportantInteger></ArbitrarilyNamedXmlItemElement> 
</ArbitrarilyNamedXmlListElement> 
' 

    DECLARE @tblEntityList TABLE(
     SomeVeryImportantInteger int 
    ) 

    INSERT @tblEntityList(SomeVeryImportantInteger) 
    SELECT 
     XmlItem.query('//SomeVeryImportantInteger[1]').value('.','int') as SomeVeryImportantInteger 
    FROM 
     [dbo].[tvfShredGetOneColumnedTableOfXmlItems] (@xmlEntityList) 


 

en utilisant la fonction scalaire à valeur

 

/* Example Inputs */ 
/* 
DECLARE @xmlListFormat xml 
SET  @xmlListFormat = 
      ' 
      <ArbitrarilyNamedXmlListElement> 
       <ArbitrarilyNamedXmlItemElement>004421UB7</ArbitrarilyNamedXmlItemElement> 
       <ArbitrarilyNamedXmlItemElement>59020UH24</ArbitrarilyNamedXmlItemElement> 
       <ArbitrarilyNamedXmlItemElement>542514NA8</ArbitrarilyNamedXmlItemElement> 
      </ArbitrarilyNamedXmlListElement> 
      ' 
declare @tblResults TABLE 
(
    XmlItem xml 
) 

*/ 

-- ============================================= 
-- Author:  6eorge Jetson 
-- Create date: 01/02/3003 
-- Description: Shreds a list of XML items conforming to 
--    the expected generic @xmlListFormat 
-- ============================================= 
CREATE FUNCTION [dbo].[tvfShredGetOneColumnedTableOfXmlItems] 
(
    -- Add the parameters for the function here 
    @xmlListFormat xml 
) 
RETURNS 
@tblResults TABLE 
(
    -- Add the column definitions for the TABLE variable here 
    XmlItem xml 
) 
AS 
BEGIN 

    -- Fill the table variable with the rows for your result set 
    INSERT @tblResults 
    SELECT 
     tblShredded.colXmlItem.query('.') as XmlItem 
    FROM 
     @xmlListFormat.nodes('/child::*/child::*') as tblShredded(colXmlItem) 

    RETURN 
END 

--SELECT * FROM @tblResults 

 
0

Dans le cas où cela est utile à tous ceux qui cherchent d'autre là-bas pour une solution « générique », j'ai créé une procédure CLR qui peut prendre un fragment de Xml comme ci-dessus et « déchiqueter » dans un resultset tableau, sans vous fournir des informations supplémentaires sur les noms ou les types de colonnes, ou la personnalisation de votre appel de quelque manière que le fragment de Xml donné:

http://architectshack.com/ClrXmlShredder.ashx

il y a bien sûr quelques restrictions ctions (le xml doit être "tabulaire" dans la nature comme cet exemple, la première ligne doit contenir tous les éléments/colonnes qui seront supportés, etc) - mais j'espère que c'est un peu en avance sur ce qui est disponible.

0

est ici une solution de rechange:

;with cte as 
(
    select id, name, addresses, addresses.value('count(/address/city)','int') cnt 
    from @demo 
) 
, cte2 as 
(
    select id, name, addresses, addresses.value('((/address/city)[sql:column("cnt")])[1]','nvarchar(256)') city, cnt-1 idx 
    from cte 
    where cnt > 0 

    union all 

    select cte.id, cte.name, cte.addresses, cte.addresses.value('((/address/city)[sql:column("cte2.idx")])[1]','nvarchar(256)'), cte2.idx-1 
    from cte2 
    inner join cte on cte.id = cte2.id and cte2.idx > 0 
) 
select id, name, city 
from cte2 
order by id, city 

FYI: J'ai posté une autre version de ce SQL sur le site de la revue de code ici: https://codereview.stackexchange.com/questions/108805/select-field-in-an-xml-column-where-both-xml-and-table-contain-multiple-matches

Questions connexes