2015-03-11 2 views
1

У меня есть список данных, которые я передаю в хранимую процедуру как XML. Данные представляют собой список Widget, а виджет содержит список WidgetItem (данные родительского ребенка). Я хотел бы сделать MERGE на основе подмножества Widget на основе ParentID. Некоторые данные для ParentID были обновлены, некоторые из них были удалены (так что отсутствует в xml), а некоторые данные являются новыми.SQL Объединить список родительских данных ребенка

Обновленные данные никогда не будут нуждаться в данных ребенка, обновленных, поскольку запись Widget может быть отрегулирована, а не элементы (дочерние данные) внутри нее. Вставка всегда будет содержать одну или несколько дочерних записей (WidgetItems).

Я не могу понять, как это сделать в MERGE, поскольку это похоже на лучший подход по сравнению с обработкой слияния в слое данных.

Вот что я до сих пор ... Я помещал комментарии, где я застрял:

CREATE PROCEDURE dbo.pWidgetsMerge 
    @Widgets XML 
AS 

/* 
Assumed XML input @Widgets xml: 
<Widgets> 
    <Widget> 
     <WidgetID> 
     <ParentID> 
     <StartDate> 
     <EndDate> 
     <Details> 
      <WidgetDetailItem> 
       <WidgetDetailItemID> 
       <WidgetID> 
       <SomeID>     
       <SomeData> 
*/ 

MERGE 
    [dbo].[Widget] as w 
USING 
    (
     SELECT 
      'WidgetID' = P.value('WidgetID[1]', 'INT'), 
      'ParentID' = P.value('ParentID[1]', 'INT'), 
      'StartDate' = P.value('EffectiveStartDate[1]', 'DATETIME'), 
      'EndDate' = P.value('EffectiveEndDate[1]', 'DATETIME') 
     FROM 
      @Widgets.nodes('/Widgets/Widget') PROPERTYFEED(P) 
    ) 
    AS xmlIn 
    (
     [WidgetID], 
     [StartDate], 
     [EndDate] 
    ) 
    ON 
     w.[WidgetID] = xmlIn.[WidgetID] 
    WHEN 
     NOT MATCHED 
    THEN 
     INSERT 
     (
      [ParentID], 
      [StartDate], 
      [EndDate] 
     ) 
     VALUES 
     (
      xmlIn.[ParentID], 
      xmlIn.[StartDate], 
      xmlIn.[EndDate] 
     ) 


     /*STUCK HERE: After the insert, need to put in the child 
      records into a new table [WidgetItems]. Maybe it's another 
      operation outside of the merge?*/ 

    WHEN 
     MATCHED AND (
      (w.[StartDate] <> xmlIn.[StartDate]) OR 
      (w.[EndDate] <> xmlIn.[EndDate])) 
    THEN 
     UPDATE SET 
      w.[StartDate] = xmlIn.[StartDate], 
      w.[EndDate] = xmlIn.[EndDate] 
    WHEN 
     NOT MATCHED BY SOURCE AND w.[ParentID] = xmlIn.[ParentID] 
    THEN 
     UPDATE SET 
      w.[DeletedDate] = GETDATE() 

Кроме того, если я приближаюсь к этому неправильному альтернативное решение будет иметь в виду, или, может быть, мне нужно обрабатывать это на уровне данных.

ответ

1

Ниже приведен обновленный код, который должен отвечать на ваш вопрос. Я добавил комментарии, чтобы объяснить, что происходит. Надеюсь, это имеет смысл.

Как указано, ParentID является одинаковым для всех виджетов прошли в, так что я рассматривая его в качестве параметра вместо элемента XML

DECLARE @ParentID INT = 1 

DECLARE @Widgets AS XML = 
N'<Widgets> 
    <Widget> 
     <WidgetID /> 
     <StartDate /> 
     <EndDate /> 
     <Details> 
      <WidgetDetailItem> 
       <WidgetDetailItemID></WidgetDetailItemID> 
       <WidgetID/> 
       <SomeID>4</SomeID>    
       <SomeData/> 
      </WidgetDetailItem> 
      <WidgetDetailItem> 
       <WidgetDetailItemID></WidgetDetailItemID> 
       <WidgetID/> 
       <SomeID>323</SomeID>    
       <SomeData/> 
      </WidgetDetailItem> 
      <WidgetDetailItem> 
       <WidgetDetailItemID></WidgetDetailItemID> 
       <WidgetID/> 
       <SomeID>1</SomeID>    
       <SomeData/> 
      </WidgetDetailItem> 
     </Details> 
    </Widget> 
    <Widget> 
     <WidgetID>10</WidgetID> 
     <StartDate>January 1, 2015</StartDate> 
     <EndDate /> 
     <Details> 
      <WidgetDetailItem> 
       <WidgetDetailItemID></WidgetDetailItemID> 
       <WidgetID/> 
       <SomeID>4</SomeID>   
       <SomeData/> 
      </WidgetDetailItem> 
      <WidgetDetailItem> 
       <WidgetDetailItemID></WidgetDetailItemID> 
       <WidgetID/> 
       <SomeID>99</SomeID>   
       <SomeData/> 
      </WidgetDetailItem> 
      <WidgetDetailItem> 
       <WidgetDetailItemID></WidgetDetailItemID> 
       <WidgetID/> 
       <SomeID>6</SomeID>    
       <SomeData/> 
      </WidgetDetailItem> 
     </Details> 
    </Widget> 
</Widgets>'; 

--Used to hold the pseudoID -> WidgetID relationship for inserting the details 
DECLARE @WidgetIds AS TABLE ([Action] varchar(10), PseudoID INT, WidgetID INT); 

; 
--Use a CTE of the subset of data to be more performant. If we just went straight to the 
--merge we'd be operating on the entire table and that can have some major performance hits 
WITH T AS (
       SELECT 
        w.* 
       FROM 
        [dbo].[Widget] as w 
       WHERE 
        w.[ParentID] = @ParentID 
) 
MERGE INTO T 
USING (
     SELECT 
      --Generate a pseudoid based on the order of the Widget elements so that we have some way of 
      --linking the detail records to the master 
      row_number() OVER(ORDER BY PROPERTYFEED.P) PseudoID, 
      'WidgetID' = P.value('WidgetID[1]', 'INT'), 
      'ParentID' = @ParentID, 
      'StartDate' = P.value('StartDate[1]', 'DATETIME'), 
      'EndDate' = P.value('EndDate[1]', 'DATETIME') 
     FROM 
      @Widgets.nodes('/Widgets/Widget') PROPERTYFEED(P) 
    ) 
    AS xmlIn 
    (
      [PseudoID], 
     [WidgetID], 
     [ParentID], 
     [StartDate], 
     [EndDate] 
    ) 
    ON 
     T.[WidgetID] = xmlIn.[WidgetID] 
    WHEN 
     NOT MATCHED 
    THEN 
     INSERT 
     (
      [ParentID], 
      [StartDate], 
      [EndDate] 
     ) 
     VALUES 
     (
      xmlIn.[ParentID], 
      xmlIn.[StartDate], 
      xmlIn.[EndDate] 
     ) 
    WHEN 
     MATCHED AND (
      (T.[StartDate] <> xmlIn.[StartDate]) OR 
      (T.[EndDate] <> xmlIn.[EndDate])) 
    THEN 
     UPDATE SET 
      T.[StartDate] = xmlIn.[StartDate], 
      T.[EndDate] = xmlIn.[EndDate] 
    WHEN 
     NOT MATCHED BY SOURCE AND T.[DeletedDate] IS NULL 
    THEN 
     UPDATE SET 
      T.[DeletedDate] = GETDATE()   
OUTPUT $action, xmlIn.PseudoID, INSERTED.WidgetID INTO @WidgetIds 

; 

--This is some magic to generate a temp table of numbers from 1 to COUNT(Widget) 
--This is so we can reference the parent Widget row in the same order as the pseudoid generated above 
--http://stackoverflow.com/a/1134379/4375845 
;WITH Total(TotalWidgets) AS (SELECT COUNT(1) TotalWidgets FROM @Widgets.nodes('/Widgets/Widget') PROPERTYFEED(P)) 
     , Numbers(Num) as (
       SELECT 1 AS Num 
       UNION ALL 
       SELECT Num+1 
       FROM Numbers 
       JOIN Total t ON 1 = 1 
       WHERE Num < t.TotalWidgets) 
INSERT INTO WidgetDetailItem (WidgetID,SomeID,SomeData) 
SELECT 
     w.WidgetID 
     ,Details.SomeID 
     ,Details.SomeData 
FROM 
    (SELECT 
     P.value('WidgetDetailItemID[1]','int') WidgetDetailItemID   
     , P.value('SomeID[1]','int') SomeID 
     , P.value('SomeData[1]','varchar(5)') SomeData 
     , n.Num AS PsuedoID 
    FROM Numbers n 
    --This is what gives us our pseudo ID to link to the row_number() function from the first merge statement 
    CROSS APPLY @Widgets.nodes('/Widgets/Widget[sql:column("n.Num")]/Details/WidgetDetailItem') AS M(P) 
    ) Details 
JOIN @WidgetIds w on Details.PsuedoID = w.PseudoID 
WHERE w.Action = 'INSERT' --We only want inserts by your spec 

SELECT * FROM Widget; 
SELECT * FROM WidgetDetailItem; 
0

Я де-сериализую входящий XML в таблицу. Это позволило проверить данные в XML-строке.

Пункт 1: Новая таблица с сериализацией позволит легко фильтровать, какие данные включены в MERGE.

Пункт 2: Вставка данных в дочернюю таблицу должна выполняться отдельным вызовом. MERGE может обрабатывать только красные (Create Update Delete) операции на одной таблице.

ПРИМЕЧАНИЕ: MERGE будет использовать все записи в таблице DESTINATION. Поэтому, когда вы не сопоставлены с Source, это будет действовать на все записи, не включенные в таблицу.

+0

Я делал кучу правок, вероятно, так как вы читаете вопрос, но следующее, я думаю, обращается к части, чтобы воздействовать на все записи (влияет только на удаление): 'КОГДА НЕ СООТВЕТСТВУЕТ ИСТОЧНИКОМ И w. [ParentID] = xmlIn. [ParentID] THEN UPDATE SET w. [DeletedDate] = GETDATE() ' – Kelsey

+0

Не означает ли это, что он не сопоставляется в таблице базы данных, а идентификатор родительской таблицы базы данных должен соответствовать идентификатору родительского элемента XML? Если я правильно их читаю, источник MATCHED BY не будет срабатывать. Я поместил флаг в XML для записей, которые нужно удалить из базы данных. – Steven

+0

Да, если 'NOT MATCHED' не имеет соответствия в ParentID, операция удаления будет проигнорирована. По крайней мере, это то, на что я надеюсь, поскольку я хочу сравнить только удаление с подмножеством данных на основе «ParentID». – Kelsey

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