SQL Server:使用外键跨多个表合并

SQL Server:使用外键跨多个表合并,sql,sql-server,xml,merge,Sql,Sql Server,Xml,Merge,下面是我试图做的:基本上是将XML发送到SQL Server,以更新/插入(合并)我的数据,作为代码中的“保存”函数 如果我使用以下XML在XML中发送一个“项”,我就成功地做到了这一点: <root> <Formula1> <M_iFormula1Id>0</M_iFormula1Id> <M_bDataInUse>0</M_bDataInUse> <M_bActive>1</M_bActive>

下面是我试图做的:基本上是将XML发送到SQL Server,以更新/插入(合并)我的数据,作为代码中的“保存”函数

如果我使用以下XML在XML中发送一个“项”,我就成功地做到了这一点:

<root>
<Formula1>
<M_iFormula1Id>0</M_iFormula1Id>
<M_bDataInUse>0</M_bDataInUse>
<M_bActive>1</M_bActive>
<M_lstItem>
    <M_iItemId>0</M_iItemId>
    <M_iItemTypeId>1</M_iItemTypeId>
    <M_sItemValue>German</M_sItemValue>
    <M_iRaceId>1</M_iRaceId>
    <M_iDriverId>50</M_iDriverId>
</M_lstItem>
</Formula1>
</root>
当我在XML中有多条记录时,
@Formula1Id
被设置为第一个merge语句中插入的最后一条记录,因此XML中的所有子数据都使用此id合并,这意味着所有子数据都属于一个父数据

<root>
<Formula1>
<M_iFormula1Id>0</M_iFormula1Id>
<M_bDataInUse>0</M_bDataInUse>
<M_bActive>1</M_bActive>
<M_lstItem>
    <M_iItemId>0</M_iItemId>
    <M_iItemTypeId>1</M_iItemTypeId>
    <M_sItemValue>German</M_sItemValue>
    <M_iRaceId>1</M_iRaceId>
    <M_iDriverId>50</M_iDriverId>
</M_lstItem>
</Formula1>
<Formula1>
<M_iFormula1Id>0</M_iFormula1Id>
<M_bDataInUse>0</M_bDataInUse>
<M_bActive>1</M_bActive>
<M_lstItem>
    <M_iItemId>0</M_iItemId>
    <M_iItemTypeId>1</M_iItemTypeId>
    <M_sItemValue>French</M_sItemValue>
    <M_iRaceId>2</M_iRaceId>
    <M_iDriverId>50</M_iDriverId>
</M_lstItem>
</Formula1>
</root>

0
0
1.
0
1.
德国的
1.
50
0
0
1.
0
1.
法语
2.
50
是否有方法执行此操作以保持外键关系正确

也许Merge语句是错误的方法,但它似乎是一次处理大量插入/更新的最佳方法

也许您可以建议另一种方法-主要标准是性能,因为可能有数千项需要“保存”-我尝试过查看SqlBulkCopy,但这似乎也不能很好地处理外键关系。。。我知道我可以一次保存到一个表,但是如果“保存”的一部分出错,我就会失去回滚功能


非常感谢您的帮助/建议。提前感谢。

尝试使用以下解决方案(它没有经过测试;我假设您可以有许多“公式1”元素;您应该仔细阅读我的注释):


嗨,博格丹,非常感谢你在这方面的帮助-我设法调整了它,使它成功地工作。我刚刚对大约480个全新(插入)公式1节点进行了测试,每个节点在MlstItem节点中包含2个Item节点。花了1米28完成。你觉得这次演出怎么样?考虑到可能有1000个公式1节点,这对我来说似乎相当缓慢,但在我之前从未做过类似的事情,因此我没有任何时间可与之相比。我还应该提到,我将添加到数据中,因此它将最终成为一个带有4个子关系表的Formula1节点。同一SP的旧版本的性能如何?如果您认为存在性能问题,则应发布执行计划(实际执行计划:SSMS>包含实际执行计划/Ctrl+M)。
<root>
<Formula1>
<M_iFormula1Id>0</M_iFormula1Id>
<M_bDataInUse>0</M_bDataInUse>
<M_bActive>1</M_bActive>
<M_lstItem>
    <M_iItemId>0</M_iItemId>
    <M_iItemTypeId>1</M_iItemTypeId>
    <M_sItemValue>German</M_sItemValue>
    <M_iRaceId>1</M_iRaceId>
    <M_iDriverId>50</M_iDriverId>
</M_lstItem>
</Formula1>
<Formula1>
<M_iFormula1Id>0</M_iFormula1Id>
<M_bDataInUse>0</M_bDataInUse>
<M_bActive>1</M_bActive>
<M_lstItem>
    <M_iItemId>0</M_iItemId>
    <M_iItemTypeId>1</M_iItemTypeId>
    <M_sItemValue>French</M_sItemValue>
    <M_iRaceId>2</M_iRaceId>
    <M_iDriverId>50</M_iDriverId>
</M_lstItem>
</Formula1>
</root>
ALTER PROCEDURE [dbo].[spFormula1_Save]
    @Formula1Xml xml--Formula1 as xml
AS
BEGIN 
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT, XACT_ABORT ON;

    IF DATALENGTH(@Formula1Xml) = 0
        RETURN 0

------------------------
--Xml shredding
------------------------
-- I prefer using the new XML methods (nodes, value, exist) instead of sp_xml_preparedocument + OPENXML 
-- because you may get memory leaks if we don't use sp_xml_removedocument
DECLARE @Formula1_Table TABLE
(
    M_iFormula1Id bigint,
    Rnk bigint primary key, -- It's used to unique identify the old and the new rows
    M_bDataInUse bit,
    M_bActive bit
);
INSERT  @Formula1_Table (M_iFormula1Id, Rnk, M_bDataInUse, M_bActive)
SELECT  x.XmlCol.value('(M_iFormula1Id)[1]', 'BIGINT') AS M_iFormula1Id,
        ROW_NUMBER() OVER(ORDER BY x.XmlCol) AS Rnk, -- It's used to unique identify the old and the new rows
        x.XmlCol.value('(M_bDataInUse)[1]', 'BIT') AS M_bDataInUse,
        x.XmlCol.value('(M_bActive)[1]', 'BIT') AS M_bActive
FROM    @Formula1Xml.nodes('/root/Formula1') x(XmlCol);

DECLARE @Formula1_M_lstItem_Table TABLE
(
    M_iFormula1Id bigint,
    Rnk bigint, -- It's used to unique identify new "Formula1" rows (those rows having M_iFormula1Id=0)
    M_iItemId bigint,
    M_iItemTypeId bit,
    M_sItemValue varchar(1000),
    M_iRaceId int,
    M_iDriverId int
);
INSERT  @Formula1_M_lstItem_Table 
(
    M_iFormula1Id,
    Rnk, 
    M_iItemId,
    M_iItemTypeId,
    M_sItemValue,
    M_iRaceId,
    M_iDriverId
)
SELECT  /*x.XmlCol.value('(M_iFormula1Id)[1]', 'BIGINT')*/ 
        -- At this moment we insert only nulls
        NULL AS M_iFormula1Id,
        DENSE_RANK() OVER(ORDER BY x.XmlCol) AS Rnk, -- It's used to unique identify new and old "Formula1" rows
        y.XmlCol.value('(M_iItemId)[1]', 'BIGINT') AS M_iItemId,
        y.XmlCol.value('(M_iItemTypeId)[1]', 'BIT') AS M_iItemTypeId,
        y.XmlCol.value('(M_sItemValue)[1]', 'VARCHAR(1000)') AS M_sItemValue,
        y.XmlCol.value('(M_iRaceId)[1]', 'INT') AS M_iRaceId,
        y.XmlCol.value('(M_iDriverId)[1]', 'INT') AS M_iDriverId
FROM    @Formula1Xml.nodes('/root/Formula1') x(XmlCol)
CROSS APPLY x.XmlCol.nodes('M_lstItem') y(XmlCol);
------------------------
--End of Xml shredding
------------------------


BEGIN TRANSACTION
BEGIN TRY

-------------------
--Formula1 Table
-------------------
DECLARE @Merged_Rows TABLE
(
    Merge_Action nvarchar(10) not null,
    Rnk bigint not null,
    M_iFormula1Id bigint -- The old id's and the new inserted id's.
);
DECLARE @Formula1Id bigint = 0;

    MERGE INTO Formula1 WITH(HOLDLOCK) AS tab -- To prevent race condition. http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
    USING @Formula1_Table AS [xml]
    ON (tab.Formula1Id = [xml].[M_iFormula1Id])
    WHEN MATCHED THEN UPDATE SET tab.DataInUse = [xml].M_bDataInUse,
                                 tab.Active = [xml].M_bActive
                                 -- We no more need this line because of OUTPUT clause
                                 -- @Formula1Id = [xml].M_iFormula1Id 
    WHEN NOT MATCHED THEN INSERT (DataInUse,
                                  Active)
                                 VALUES([xml].M_bDataInUse,
                                        [xml].M_bActive
                                        )
    -- This OUTPUT clause will insert into @Merged_Rows the Rnk and the new M_iFormula1Id for every /root/Formula1 element  
    -- http://msdn.microsoft.com/en-us/library/ms177564.aspx
    OUTPUT $action, [xml].Rnk, inserted.M_iFormula1Id INTO @Merged_Rows (Merge_Action, Rnk, M_iFormula1Id);

-- This is replaced by previous OUTPUT clause
/*
IF(@Formula1Id = 0)--then we haven''t updated so get inserted rowid
BEGIN
 SET @Formula1Id = SCOPE_IDENTITY();--get the inserted identity
END
*/

-- At this moment we replace all previously inserted NULLs with the real (old and new) id's
UPDATE  x
SET     M_iFormula1Id = y.M_iFormula1Id
FROM    @Formula1_M_lstItem_Table x
JOIN    @Merged_Rows y ON x.Rnk = y.Rnk;

-------------------
--Formula1Item Table
-------------------
    MERGE INTO Formula1Item AS tab
    USING @Formula1_M_lstItem_Table AS [xml]
    ON (tab.ItemId = [xml].M_iItemId) 
    -- Maybe you should need also this join predicate (tab.M_iFormula1Id = [xml].M_iFormula1Id)
    WHEN MATCHED THEN UPDATE SET tab.ItemTypeId = [xml].M_iItemTypeId,
                                 tab.ItemValue = [xml].M_sItemValue,
                                 tab.RaceId = [xml].M_iRaceId,
                                 tab.DriverId = [xml].M_iDriverId
    WHEN NOT MATCHED THEN INSERT (Formula1Id,
                                  ItemTypeId,
                                  ItemValue,
                                  RaceId,
                                  DriverId)
                                 VALUES([xml].M_iFormula1Id,
                                        [xml].M_iItemTypeId,
                                        [xml].M_sItemValue,
                                        [xml].M_iRaceId,
                                        [xml].M_iDriverId
                                        );   
 COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    -- The caller should be informed when an error / exception is catched
    -- THROW
END CATCH;  

END