簡體   English   中英

SQL Server:使用外鍵合並多個表

[英]SQL Server : Merge across multiple tables with foreign key

這是我要執行的操作:基本上將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>

在此存儲過程中:

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 ON;

    IF DATALENGTH(@Formula1Xml) = 0
        RETURN 0

BEGIN TRANSACTION
BEGIN TRY

    DECLARE @hDoc INT
    EXEC sp_xml_preparedocument @hDoc OUTPUT, @Formula1Xml

-------------------
--Formula1 Table
-------------------
DECLARE @Formula1Id bigint = 0;

    MERGE INTO Formula1 AS tab
    USING 
    OPENXML (@hDoc, '/root/Formula1', 2)
     WITH (
        M_iFormula1Id bigint,
        M_bDataInUse bit,
        M_bActive bit
        ) AS [xml]
    ON (tab.Formula1Id = [xml].[M_iFormula1Id])
    WHEN MATCHED THEN UPDATE SET tab.DataInUse = [xml].M_bDataInUse,
                                 tab.Active = [xml].M_bActive,
                                 @Formula1Id = [xml].M_iFormula1Id
    WHEN NOT MATCHED THEN INSERT (DataInUse,
                                  Active)
                                 VALUES([xml].M_bDataInUse,
                                        [xml].M_bActive
                                        );

IF(@Formula1Id = 0)--then we haven''t updated so get inserted rowid
BEGIN
 SET @Formula1Id = SCOPE_IDENTITY();--get the inserted identity
END



-------------------
--Formula1Item Table
-------------------
    MERGE INTO Formula1Item AS tab
    USING 
    OPENXML (@hDoc, '/root/Formula1/M_lstItem', 2)
     WITH (
        M_iItemId bigint,
        M_iItemTypeId bit,
        M_sItemValue varchar(1000),
        M_iRaceId int,
        M_iDriverId int
        ) AS [xml]
    ON (tab.ItemId = [xml].M_iItemId)
    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(@Formula1Id,
                                        [xml].M_iItemTypeId,
                                        [xml].M_sItemValue,
                                        [xml].M_iRaceId,
                                        [xml].M_iDriverId
                                        );   
 COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH;  

END

當我在XML中有多個記錄時, @Formula1Id會被設置為在第一個merge語句中插入的最后一個記錄,因此XML中的所有Child數據都將使用此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>

有什么方法可以執行此操作以保持外鍵關系正確。

也許Merge語句是錯誤的處理方法,但它似乎是一次處理大量插入/更新的最佳方法。

也許您可以建議一種替代方法-主要標准是性能,因為可能有成千上萬的項目需要“保存”-我嘗試查看SqlBulkCopy,但這似乎也不能很好地處理外鍵關系...我我知道我一次可以保存到一張表,但是如果“保存”的一部分出錯,我將失去ROLLBACK功能!

任何幫助/建議,我們將不勝感激。 提前致謝。

嘗試使用以下解決方案(未經測試;我假設您可以有很多“ Formula1”元素;您應該仔細閱讀我的筆記):

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM