Выберите узлы XML как строки

Я выбираю из таблицы, в которой есть столбец XML, используя T-SQL. Я хотел бы выбрать определенный тип узла и создать строку для каждого из них.

Например, предположим, что я выбираю из таблицы люди. В этой таблице есть столбец XML для адреса. XML сформирован следующим образом:

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

Как я могу получить такие результаты:

Имя         Город      NBSPR6BSP666  NBSPR6R6_SP66_SP66_SP66_SP66_SP666

Джо Бейкер   Seattle      WA

Джо Бейкер   Tacoma     WA

Фред Джонс  Ванкувер BC

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
15
0
60 475
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Если вы можете его использовать, linq api удобен для 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, 
        });

Я знаю, но linq отображает t-sql.

Wyatt 10.10.2008 21:59

Я предполагаю, что это для хранимой процедуры.

FlySwat 10.10.2008 22:02

На самом деле это для отчета, но да, я не могу использовать C#.

Bjørn 10.10.2008 22:07
Ответ принят как подходящий

Вот ваше решение:

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

Искал какое-то время, пока не нашел этот отличный пример. Престижность leoinfo!

Chris Ballance 07.05.2010 23:14

Вот как я это делаю в целом:

Я уничтожил исходный XML с помощью такого вызова, как



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)


с помощью скалярной функции


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

В случае, если это полезно для кого-то еще, ищущего "универсальное" решение, я создал процедуру CLR, которая может взять фрагмент Xml, как указано выше, и "измельчить" его в табличный набор результатов, без предоставления дополнительной информации об именах. или типы столбцов, или настроить ваш вызов любым способом для данного фрагмента Xml:

http://architectshack.com/ClrXmlShredder.ashx

Конечно, есть некоторые ограничения (XML должен быть "табличным" по своей природе, как этот образец, первая строка должна содержать все элементы / столбцы, которые будут поддерживаться, и т. д.), Но я надеюсь, что он на несколько шагов впереди того, что Доступен встроенный.

Вот альтернативное решение:

;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

К вашему сведению: я разместил еще одну версию этого SQL на сайте обзора кода здесь: https://codereview.stackexchange.com/questions/108805/select-field-in-an-xml-column-where-both-xml-and-table-contain-multiple-matches

Другие вопросы по теме