Самый быстрый способ импортировать большие XML в SQL -Server таблицу - PullRequest
0 голосов
/ 10 июля 2020

У меня действительно большой и не слишком красивый XML, который я хочу импортировать в свою базу данных sql -сервера. Формат XML выглядит ужасно, как я уже сказал:

<myxml xmlns="http://somenamespace.whatever.com/schemas/xmldata/1/" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance">
    <mydata>
        <item>
            <record>some</record>
            <record>123</record>
            <record xs:nil="true" />
            <record>random</record>
            <record>234</record>    
        </item>
        <item>
            <record>345</record>
            <record>in all</record>
            <record>these</record>
            <record>cells</record>
            <record>123asdf</record>            
        </item>
        <item>
            <record>how</record>
            <record>to</record>
            <record>import</record>
            <record>987654321</record>
            <record xs:nil="true" />
        </item>
    </mydata>
</myxml>

Это всего лишь небольшой пример. Фактически, XML превышает 100 мл, у него более 200 тыс. Элементов, и каждый элемент имеет 15 записей, но этот образец подойдет.

Я знаю, что каждая «запись» в «элементе» представляет, но для меня этого достаточно, чтобы импортировать все значения записи в столбец с varchar (100). Допустим, эта таблица: «

CREATE TABLE [dbo].[DataFromXml](
    [Column1] [varchar](100) NULL,
    [Column2] [varchar](100) NULL,
    [Column3] [varchar](100) NULL,
    [Column4] [varchar](100) NULL,
    [Column5] [varchar](100) NULL
) ON [PRIMARY]
GO

Я могу сделать это с помощью этого кода:

CREATE TABLE XmlTable
(
    XMLData XML
)

INSERT INTO XmlTable(XMLData)
SELECT CONVERT(XML, BulkColumn) 
FROM OPENROWSET(BULK 'D:\myverylarge.xml', SINGLE_CLOB) AS x;

DECLARE @XML AS XML
SELECT @XML=XMLData FROM XmlTable

;WITH XMLNAMESPACES ('http://www.w3.org/2001/XMLSchema-instance' as xs, DEFAULT 'http://somenamespace.whatever.com/schemas/xmldata/1/')
INSERT INTO DataFromXml(Column1, Column2, Column3, Column4, Column5)
SELECT  ref.value('record[1][not(@xs:nil = "true")]' ,'varchar(100)') as Column1
        ,ref.value('record[2][not(@xs:nil = "true")]' ,'varchar(100)') as Column2
        ,ref.value('record[3][not(@xs:nil = "true")]' ,'varchar(100)') as Column3
        ,ref.value('record[4][not(@xs:nil = "true")]' ,'varchar(100)') as Column4
        ,ref.value('record[5][not(@xs:nil = "true")]' ,'varchar(100)') as Column5
        FROM @XML.nodes('/myxml/mydata/item') xmlData( ref )

Это выполняется в течение минуты или 2, что может быть не так уж и плохо. У меня нет хорошей ссылки. Я считаю, что это могло бы быть намного быстрее, поскольку получение XML (более 100 МБ) в базе данных с помощью OPENROWSET занимает всего несколько секунд.

Могу ли я оптимизировать вставку, и если Итак, как мне это сделать?

Ответы [ 2 ]

1 голос
/ 10 июля 2020

Просто чтобы дополнить @ Shnu go ответ.

Вся заслуга принадлежит ему.

Это ваше точное SQL заявление. Это должно дать вам повышение производительности примерно на 20%. Пожалуйста, попробуйте.

;WITH XMLNAMESPACES ('http://www.w3.org/2001/XMLSchema-instance' as xs, DEFAULT 'http://somenamespace.whatever.com/schemas/xmldata/1/')
INSERT INTO DataFromXml(Column1, Column2, Column3, Column4, Column5)
SELECT  ref.value('(record[1]/text())[1]' ,'varchar(100)') as Column1
        ,ref.value('(record[2]/text())[1]' ,'varchar(100)') as Column2
        ,ref.value('(record[3]/text())[1]' ,'varchar(100)') as Column3
        ,ref.value('(record[4]/text())[1]' ,'varchar(100)') as Column4
        ,ref.value('(record[5]/text())[1]' ,'varchar(100)') as Column5
FROM @XML.nodes('/myxml/mydata/item') xmlData(ref);
1 голос
/ 10 июля 2020

Работа со значениями NULL - это нечто особенное в XML.

Определение значения NULL в XML: не существует . Итак,

<a>
    <b>hi</b>
    <c></c>
    <d/>
</a>
  • <a> - это элемент root.
  • <b> - это элемент с узлом text().
  • <c> - пустой элемент
  • <d> - самозакрывающийся элемент
  • <e> это - ммм - нет ...

Важный совет: <c> и <d> одинаковы, абсолютно никакой разницы!

Вы можете запросить элемент с помощью

.value('(/a/b)[1]','nvarchar(100)')

И вы можете запросить узел text() в частности

.value('(/a/b/text())[1]','nvarchar(100)')

Здесь вы найдете возможный ответ (немного скрытый): вы можете использовать весь свой код без предиката проверки NULL, если вы запрашиваете конкретно узел text().

Измените это

ref.value('record[1][not(@xs:nil = "true")]' ,'varchar(100)')

на это

ref.value('(record[1]/text())[1]' ,'varchar(100)')

Что может сломать это: если содержимое <record> может быть пустой строкой, вы получите NULL обратно, а не ''. Но это должно быть намного быстрее ... Надеюсь, это нормально для вас ...

О производительности: Прочтите этот ответ. Он хорошо освещает вашу проблему. Особенно в той части, где расходуется время (перейдите по ссылкам в этом ответе).

...