2014-09-30 1 views
3

У меня есть XML выглядит следующим образом:Массовой вставки вложенные XML с внешним ключом как столбец идентификации первой таблицы

<Records> 
    <Record> 
    <Name>Best of Pop</Name> 
    <Studio>ABC studio</Studio> 
    <Artists> 
     <Artist> 
     <ArtistName>John</ArtistName> 
     <Age>36</Age>    
     </Artist> 
     <Artist> 
     <ArtistName>Jessica</ArtistName> 
     <Age>20</Age>    
     </Artist> 
    </Artists> 
    </Record> 
    <Record> 
    <Name>Nursery rhymes</Name> 
    <Studio>XYZ studio</Studio> 
    <Artists> 
     <Artist> 
     <ArtistName>Judy</ArtistName> 
     <Age>10</Age>    
     </Artist> 
     <Artist> 
     <ArtistName>Rachel</ArtistName> 
     <Age>15</Age>    
     </Artist> 
    </Artists> 
    </Record> 
</Records> 

Этого файл может содержать миллионы записей. Мой MS SQL базы данных, работающие на Azure SQL Database, имеет следующие 2 таблицы для хранения этих записей:

  1. Record (RecordId [PK, идентичность, автоинкрементным], имя, студия)

  2. Artist (RecordId [внешний ключ ссылается Record.RecordId], ArtistName, Возраст)

можно ли насыпных вставки записей в Record таблицу, найдите RecordIds, а затем объем вставки информации художника в к таблице Artist в одном обходе xml с использованием подхода узлов xml?

Я искал эффективный способ сделать это в течение длительного времени, но напрасно.

Я пробовал подходы, подобные описанным here и here, но я не могу добраться до решения.

Любые указатели в направлении решения будут очень полезны.

Обновление: @srutzky: Спасибо за решение. Это работает точно так, как я хотел. Но есть один улов. Я должен использовать подход узлов для решения. Я изменил первую часть запроса. Но я застрял во второй половине. Вот что я получил.

DECLARE @Record TABLE (RecordId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
         Name NVARCHAR(400) UNIQUE, 
         Studio NVARCHAR(400)); 
DECLARE @Artist TABLE (ArtistId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
         RecordId INT NOT NULL, 
         ArtistName NVARCHAR(400), Age INT); 

INSERT INTO @Record (Name, Studio) 
    SELECT T.c.value(N'(Name/text())[1]', 'NVARCHAR(400)'), 
      T.c.value(N'(Studio/text())[1]', 'NVARCHAR(400)') 
FROM @ImportData.nodes('/Records/Record') T(c); 

SELECT * FROM @Record 

Не могли бы вы помочь мне со второй частью? Я новичок в этом подходе к обработке xml.

UPDATE2: И у меня это получилось .... Я несколько раз ломал себе мозги, пробовал несколько вещей и, наконец, пришел к решению.

DECLARE @Record TABLE (RecordId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
         Name NVARCHAR(400) UNIQUE, 
         Studio NVARCHAR(400)); 
DECLARE @Artist TABLE (ArtistId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
         RecordId INT NOT NULL, 
         ArtistName NVARCHAR(400), 
         Age INT); 

INSERT INTO @Record (Name, Studio) 
    SELECT T.c.value(N'(Name/text())[1]', 'NVARCHAR(400)'), 
      T.c.value(N'(Studio/text())[1]', 'NVARCHAR(400)') 
FROM @ImportData.nodes('/Records/Record') T(c); 

INSERT INTO @Artist (RecordId, ArtistName, Age) 
    SELECT (SELECT RecordId FROM @Record WHERE Name=T.c.value(N'(../../Name/text())[1]', 'NVARCHAR(400)')), 
      T.c.value(N'(ArtistName/text())[1]', 'NVARCHAR(400)'), 
      T.c.value(N'(Age/text())[1]', 'INT') 
FROM @ImportData.nodes('/Records/Record/Artists/Artist') T(c); 

SELECT * FROM @Record 
SELECT * FROM @Artist 

@srutzky: Спасибо за то, что указали мне в правильном направлении. Любые предложения по улучшению этого решения приветствуются.

+0

Зачем вам это нужно в один проход? Также, пожалуйста, покажите нам, что у вас уже есть. – RBarryYoung

+0

Is элемент в пределах уникальный? –

+0

@srutzky: Да, имя уникально. – nikhil

ответ

3

Это невозможно сделать за один проход в любом случае, так как вы не можете вставить две таблицы в один и тот же оператор DML (ну, вне триггеров и предложение OUTPUT, ни одно из которых не поможет здесь). Но это можно сделать эффективно за два прохода. Тот факт, что элемент <Name> в пределах <Record> является уникальным, является ключевым, поскольку это позволяет нам использовать таблицу Record в качестве таблицы поиска для второго прохода (т. Е. Когда мы получаем строки Artist).

Сначала вам нужно (ну, должно) создать UNIQUE INDEX на Record (Name ASC). В моем примере ниже я использую UNIQUE CONSTRAINT, но это связано только с тем, что я использую переменную таблицы вместо таблицы temp, чтобы упростить повторный запуск кода примера (без необходимости использования явного IF EXISTS DROP в верхней части). Этот показатель поможет выполнить второй проход.

В примере используется OPENXML, поскольку это, скорее всего, будет более эффективным, если использовать функцию .nodes(), так как один и тот же документ необходимо пройти дважды. Последний параметр для функции OPENXML, 2, указывает, что документ «основан на элементе», поскольку разбор по умолчанию ищет «Атрибут».

DECLARE @DocumentID INT, @ImportData XML; 

SET @ImportData = N' 
<Records> 
    <Record> 
    <Name>Best of Pop</Name> 
    <Studio>ABC studio</Studio> 
    <Artists> 
     <Artist> 
     <ArtistName>John</ArtistName> 
     <Age>36</Age>    
     </Artist> 
     <Artist> 
     <ArtistName>Jessica</ArtistName> 
     <Age>20</Age>    
     </Artist> 
    </Artists> 
    </Record> 
    <Record> 
    <Name>Nursery rhymes</Name> 
    <Studio>XYZ studio</Studio> 
    <Artists> 
     <Artist> 
     <ArtistName>Judy</ArtistName> 
     <Age>10</Age>    
     </Artist> 
     <Artist> 
     <ArtistName>Rachel</ArtistName> 
     <Age>15</Age>    
     </Artist> 
    </Artists> 
    </Record> 
</Records>'; 


DECLARE @Record TABLE (RecordId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
         Name NVARCHAR(400) UNIQUE, 
         Studio NVARCHAR(400)); 
DECLARE @Artist TABLE (ArtistId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
         RecordId INT NOT NULL, 
         ArtistName NVARCHAR(400), Age INT); 

EXEC sp_xml_preparedocument @DocumentID OUTPUT, @ImportData; 

-- First pass: extract "Record" rows 
INSERT INTO @Record (Name, Studio) 
    SELECT Name, Studio 
    FROM OPENXML (@DocumentID, N'/Records/Record', 2) 
      WITH (Name NVARCHAR(400) './Name/text()', 
        Studio NVARCHAR(400) './Studio/text()'); 


-- Second pass: extract "Artist" rows 
INSERT INTO @Artist (RecordId, ArtistName, Age) 
    SELECT rec.RecordId, art.ArtistName, art.Age 
    FROM OPENXML (@DocumentID, N'/Records/Record/Artists/Artist', 2) 
      WITH (Name  NVARCHAR(400) '../../Name/text()', 
        ArtistName NVARCHAR(400) './ArtistName/text()', 
        Age   INT './Age/text()') art 
    INNER JOIN @Record rec 
      ON rec.[Name] = art.[Name]; 


EXEC sp_xml_removedocument @DocumentID; 
------------------- 

SELECT * FROM @Record ORDER BY [RecordID]; 
SELECT * FROM @Artist ORDER BY [RecordID]; 

Ссылки:

EDIT:
С новым требованием к использовать .nodes() функцию вместо OPENXML, следующие будут работать:

DECLARE @ImportData XML; 

SET @ImportData = N' 
<Records> 
    <Record> 
    <Name>Best of Pop</Name> 
    <Studio>ABC studio</Studio> 
    <Artists> 
     <Artist> 
     <ArtistName>John</ArtistName> 
     <Age>36</Age>    
     </Artist> 
     <Artist> 
     <ArtistName>Jessica</ArtistName> 
     <Age>20</Age>    
     </Artist> 
    </Artists> 
    </Record> 
    <Record> 
    <Name>Nursery rhymes</Name> 
    <Studio>XYZ studio</Studio> 
    <Artists> 
     <Artist> 
     <ArtistName>Judy</ArtistName> 
     <Age>10</Age>    
     </Artist> 
     <Artist> 
     <ArtistName>Rachel</ArtistName> 
     <Age>15</Age>    
     </Artist> 
    </Artists> 
    </Record> 
</Records>'; 

IF (OBJECT_ID('tempdb..#Record') IS NOT NULL) 
BEGIN 
    DROP TABLE #Record; 
END; 
IF (OBJECT_ID('tempdb..#Artist') IS NOT NULL) 
BEGIN 
    DROP TABLE #Artist; 
END; 

CREATE TABLE #Record (RecordId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
         Name NVARCHAR(400) UNIQUE, 
         Studio NVARCHAR(400)); 
CREATE TABLE #Artist (ArtistId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
         RecordId INT NOT NULL, 
         ArtistName NVARCHAR(400), 
         Age INT); 


-- First pass: extract "Record" rows 
INSERT INTO #Record (Name, Studio) 
    SELECT col.value(N'(./Name/text())[1]', N'NVARCHAR(400)') AS [Name], 
      col.value(N'(./Studio/text())[1]', N'NVARCHAR(400)') AS [Studio] 
    FROM @ImportData.nodes(N'/Records/Record') tab(col); 


-- Second pass: extract "Artist" rows 
;WITH artists AS 
(
    SELECT col.value(N'(../../Name/text())[1]', N'NVARCHAR(400)') AS [RecordName], 
      col.value(N'(./ArtistName/text())[1]', N'NVARCHAR(400)') AS [ArtistName], 
      col.value(N'(./Age/text())[1]', N'INT') AS [Age] 
    FROM @ImportData.nodes(N'/Records/Record/Artists/Artist') tab(col) 
) 
INSERT INTO #Artist (RecordId, ArtistName, Age) 
    SELECT rec.RecordId, art.ArtistName, art.Age 
    FROM artists art 
    INNER JOIN #Record rec 
      ON rec.[Name] = art.RecordName; 

-- OR -- 
-- INSERT INTO #Artist (RecordId, ArtistName, Age) 
    SELECT rec.RecordId, 
      col.value(N'(./ArtistName/text())[1]', N'NVARCHAR(400)') AS [ArtistName], 
      col.value(N'(./Age/text())[1]', N'INT') AS [Age] 
    FROM @ImportData.nodes(N'/Records/Record/Artists/Artist') tab(col) 
    INNER JOIN #Record rec 
      ON rec.Name = col.value(N'(../../Name/text())[1]', N'NVARCHAR(400)'); 

------------------- 

SELECT * FROM #Record ORDER BY [RecordID]; 
SELECT * FROM #Artist ORDER BY [RecordID]; 

Есть два варианта для вставки в #Artist показано выше. Первый использует CTE для абстрагирования извлечения XML из запроса INSERT/SELECT. Другой - упрощенная версия, аналогичная вашему запросу в . ОБНОВЛЕНИЕ 2 вопроса.

+0

Еще раз спасибо за оптимизацию. Я должен использовать узлы, потому что мы имеем дело с SQL Azure db. Это не SQL-сервер. OpenXML там не работает. – nikhil

+0

@nikhil: Добро пожаловать. Тот факт, что это выполняется на базе Azure SQL DB, является критически важной деталью, поэтому я отредактировал вопрос, чтобы добавить эту информацию, а также добавить к ней тег. –

+0

Это было мило с вашей стороны. Благодарю. Я буду помнить об этом в будущем. – nikhil

Смежные вопросы