SQL合并父子数据列表

SQL合并父子数据列表,sql,sql-server,merge,parent-child,master-detail,Sql,Sql Server,Merge,Parent Child,Master Detail,我有一个以XML形式传递给存储过程的数据列表。数据是小部件列表,小部件包含WidgetItem父子数据列表。我想基于ParentID对小部件的子集进行合并。ParentID的一些数据已经更新,一些已经从xml中删除,因此丢失,一些数据是新的 更新的数据永远不需要更新子数据,因为小部件记录只能调整,不能调整其中的子数据项。Insert将始终具有一个或多个子记录WidgetItems 我似乎不知道如何在合并中做到这一点,因为与在数据层中处理合并相比,这似乎是最好的方法 这是我到目前为止所拥有的。。。

我有一个以XML形式传递给存储过程的数据列表。数据是小部件列表,小部件包含WidgetItem父子数据列表。我想基于ParentID对小部件的子集进行合并。ParentID的一些数据已经更新,一些已经从xml中删除,因此丢失,一些数据是新的

更新的数据永远不需要更新子数据,因为小部件记录只能调整,不能调整其中的子数据项。Insert将始终具有一个或多个子记录WidgetItems

我似乎不知道如何在合并中做到这一点,因为与在数据层中处理合并相比,这似乎是最好的方法

这是我到目前为止所拥有的。。。我在我被卡住的地方放了一条评论:

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

此外,如果我处理这个问题的方法有误,我会很感激另一种解决方案,或者我确实需要在数据层处理这个问题。

我已经将传入的XML反序列化到一个表中。这样就有机会验证XML字符串中的数据

第1项:新的反序列化表将允许一种简单的方法来过滤合并中包含的数据

第2项:将数据插入子表必须在单独的调用中进行。合并只能处理一个表上的crud Create Update Delete操作


注意:合并将使用目标表中的所有记录。因此,当您的来源不匹配时,这将作用于表中未包含的所有记录。

下面是应回答您问题的更新代码。我添加了一些评论来解释发生了什么。希望这是有道理的

正如您所说的,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;

可能在您阅读该问题后,我已经做了一系列编辑,但我认为以下内容解决了作用于所有记录的部分只会影响删除:当源与w.[ParentID]=xmlIn.[ParentID]不匹配时,则更新集w.[DeletedDate]=GetDate这不意味着它在数据库表中不匹配,并且数据库表父Id必须与XML父Id匹配吗?如果我读的是正确的,那么匹配的源将不会触发。我在XML中为应该从数据库中删除的记录添加了一个标志。是的,如果不匹配的在ParentID上没有匹配项,则删除操作将被忽略。至少这是我所希望的,因为我只想将删除与基于ParentID的数据子集进行比较,但看起来永远不会是这样。不在XML文件中并且具有匹配的ParentID传入XML中的每个小部件记录都将具有相同的ParentID。