U-SQL с XmlExtractor - элементы внутри элементов

В U-SQL я пытаюсь получить список элементов внутри элементов, используя XmlExtractor. Но я не могу получить вложенную коллекцию.

Это список предметов, который имеет местоположения. С XmlExtractor я могу получить коллекцию элементов, но я не вижу, как я могу получить коллекцию, которая содержит коллекцию. Пример XML показан ниже.

Есть идеи?

<root>
<Item>
    <Header>
        <id>111</id>
    </Header>
    <Body>
        <Locations>
            <Location>
                <Station>k4</Station>
                <Timestamp>2017-08-30T02:04:18.2506945+02:00</Timestamp>
            </Location>
            <Location>
                <Station>k5</Station>
                <Timestamp>2017-08-30T02:04:18.2506945+02:00</Timestamp>
            </Location>
        </Locations>
    </Body>
</Item>
<Item>
    <Header>
        <id>222</id>
    </Header>
    <Body>
        <Locations>
            <Location>
                <Station>k4</Station>
                <Timestamp>2017-08-30T02:12:36.1218601+02:00</Timestamp>
            </Location>
            <Location>
                <Station>k5</Station>
                <Timestamp>2017-08-30T02:12:36.1218601+02:00</Timestamp>
            </Location>
        </Locations>
    </Body>
</Item>
</root>

3 ответа

Решение

Решается созданием экстрактора, который принимает XML в одну строку, а затем вызывает метод с использованием xpath, возвращая SQL.Array, где строка имеет разделенные запятыми значения результата. Результат выглядит так:

111;k4,2017-08-30T02:04:18.2506945+02:00
111;k5,2017-08-30T02:04:18.2506945+02:00
222;k4,2017-08-30T02:12:36.1218601+02:00
222;k5,2017-08-30T02:12:36.1218601+02:00

Стандартный XmlExtractor не может этого сделать, и я также решил, что лучше отложить синтаксический анализ xml до его извлечения, поскольку в одном и том же xml может быть несколько шагов.

База данных SQL Azure обладает мощными способностями уничтожать XML. Может быть, если это уже в вашем состоянии / архитектуре, это может сделать простую альтернативу пользовательскому коду? Простой пример:

DECLARE @xml XML = '<root>
<Item>
    <Header>
        <id>111</id>
    </Header>
    <Body>
        <Locations>
            <Location>
                <Station>k4</Station>
                <Timestamp>2017-08-30T02:04:18.2506945+02:00</Timestamp>
            </Location>
            <Location>
                <Station>k5</Station>
                <Timestamp>2017-08-30T02:04:18.2506945+02:00</Timestamp>
            </Location>
        </Locations>
    </Body>
</Item>
<Item>
    <Header>
        <id>222</id>
    </Header>
    <Body>
        <Locations>
            <Location>
                <Station>k4</Station>
                <Timestamp>2017-08-30T02:12:36.1218601+02:00</Timestamp>
            </Location>
            <Location>
                <Station>k5</Station>
                <Timestamp>2017-08-30T02:12:36.1218601+02:00</Timestamp>
            </Location>
        </Locations>
    </Body>
</Item>
</root>'


/*
111;k4,2017-08-30T02:04:18.2506945+02:00
111;k5,2017-08-30T02:04:18.2506945+02:00
222;k4,2017-08-30T02:12:36.1218601+02:00
222;k5,2017-08-30T02:12:36.1218601+02:00
*/

SELECT 
    r.c.value('(Header/id/text())[1]', 'int' ) id,
    b.c.value('(Station/text())[1]', 'varchar(10)' ) station,
    b.c.value('(Timestamp/text())[1]', 'varchar(40)' ) [timestamp],
    b.c.value('(Timestamp/text())[1]', 'datetimeoffset' ) [timestamp2]
FROM @xml.nodes('root/Item') r(c)
    CROSS APPLY r.c.nodes('Body/Locations/Location') b(c)

Вы можете сделать что-то подобное, если XML также хранится в таблице.

Мои результаты: Мои результаты

Вот скрипт, который достигает желаемых результатов, используя предоставленные экстракторы.

USE master;

REFERENCE SYSTEM ASSEMBLY [System.Xml]
REFERENCE ASSEMBLY master.[Microsoft.Analytics.Samples.Formats.Xml]

@e = EXTRACT a string, b string
     FROM "CollectTest.xml"
     USING new Microsoft.Analytics.Samples.Formats.Xml.XmlDomExtractor(rowPath:"Item",
                             columnPaths:new SQL.MAP<string, string> { {"Header", "a"}, {"Body", "b"} });
@f = SELECT @e.a, t.c, t.d
     FROM @e
         CROSS APPLY new Microsoft.Analytics.Samples.Formats.Xml.XmlApplier("b","Location", new SQL.MAP<string,string> { {"Station", "c"}, {"Timestamp", "d"} })  AS t(c string, d string);


OUTPUT @f TO "foo.txt" USING  Outputters.Tsv(outputHeader:true);
OUTPUT @e TO "foo2.txt" USING  Outputters.Tsv(outputHeader:true);

Первый набор строк @e использует XmlDomExtractor для создания набора строк, содержащего "ID" в столбце a и дочерний XML-код в столбце b.

Затем второй набор строк @f использует XmlApplier для извлечения значений из вложенного XML-кода и перекрестного применения его к правильным строкам. Пример xml был скопирован из поста выше и сохранен в папке USQLDataRoot как "CollectTest.xml".

  • Примечание: Получил ленивый и вывод для заголовка содержит некоторый нежелательный синтаксис узла, но добавление промежуточного шага xpath или XmlApplier между @e и @f должно решить эту проблему.
Другие вопросы по тегам