Using OPENXML in SQL Server 2008 stored proc - INSERT order differs from XML document

对着背影说爱祢 提交于 2019-11-28 09:11:24

问题


I'm using SQL Server 2008's XML-parsing abilities to iterate through an XML document and perform an INSERT each element.

However, my stored procedure appears to be inserting each element into the table in an order which differs from the order in the document.

Furthermore, the more times I try this, the INSERT order seems to change.

Here's a sample of the XML document - nothing too fancy going on here.

<ts>
    <t id="36a3c8c1-b958-42f0-82d1-dfa6bf9b99a1" encryptedAccountId="fQ/XF8lpeR9wEDUV3yMzvQ==" uploaded="2012-04-03T15:49:19.9615097Z" visible="1">
        <tv fieldId="301" officialValue="0, 0" friendlyValue="0, 0" />
        <tv fieldId="302" officialValue="0, 1" friendlyValue="0, 1" />
        <tv fieldId="303" officialValue="0, 2" friendlyValue="0, 2" />
        <tv fieldId="304" officialValue="0, 3" friendlyValue="0, 3" />
        <tv fieldId="305" officialValue="0, 4" friendlyValue="0, 4" />
        <tv fieldId="306" officialValue="0, 5" friendlyValue="0, 5" />
    </t>
    <t id="9d56d082-4b6a-4bdf-a7a2-f5c6af88344e" encryptedAccountId="fQ/XF8lpeR9wEDUV3yMzvQ==" uploaded="2012-04-03T15:49:19.9615097Z"  visible="1">
        <tv fieldId="301" officialValue="1, 0" friendlyValue="1, 0" />
        <tv fieldId="302" officialValue="1, 1" friendlyValue="1, 1" />
        <tv fieldId="303" officialValue="1, 2" friendlyValue="1, 2" />
        <tv fieldId="304" officialValue="1, 3" friendlyValue="1, 3" />
        <tv fieldId="305" officialValue="1, 4" friendlyValue="1, 4" />
        <tv fieldId="306" officialValue="1, 5" friendlyValue="1, 5" />
    </t>
    <t id="27db47a3-ad3f-4279-8f4f-0a8944ce32d4" encryptedAccountId="fQ/XF8lpeR9wEDUV3yMzvQ==" uploaded="2012-04-03T15:49:19.9615097Z" visible="1">
        <tv fieldId="301" officialValue="2, 0" friendlyValue="2, 0" />
        <tv fieldId="302" officialValue="2, 1" friendlyValue="2, 1" />
        <tv fieldId="303" officialValue="2, 2" friendlyValue="2, 2" />
        <tv fieldId="304" officialValue="2, 3" friendlyValue="2, 3" />
        <tv fieldId="305" officialValue="2, 4" friendlyValue="2, 4" />
        <tv fieldId="306" officialValue="2, 5" friendlyValue="2, 5" />
    </t>
    <t id="867ea26b-0341-4d60-ac48-f305492a60f0" encryptedAccountId="fQ/XF8lpeR9wEDUV3yMzvQ==" uploaded="2012-04-03T15:49:19.9615097Z" visible="1">
        <tv fieldId="301" officialValue="3, 0" friendlyValue="3, 0" />
        <tv fieldId="302" officialValue="3, 1" friendlyValue="3, 1" />
        <tv fieldId="303" officialValue="3, 2" friendlyValue="3, 2" />
        <tv fieldId="304" officialValue="3, 3" friendlyValue="3, 3" />
        <tv fieldId="305" officialValue="3, 4" friendlyValue="3, 4" />
        <tv fieldId="306" officialValue="3, 5" friendlyValue="3, 5" />
    </t>
</ts>

The stored procedure has a few operations taking place, but I've commented-out other parts leaving only the SQL which inserts the <t/> elements and then the <tv/> elements.

The SQL in the stored procedure is as follows.

(@xmlTransaction is an NVARCHAR (MAX) input param containing the above XML)

BEGIN
    SET NOCOUNT ON;

    DECLARE @encryptedAccountID AS VARCHAR(200)

    BEGIN TRANSACTION
        BEGIN TRY
            DECLARE @Handle AS INT
            DECLARE @TransactionCount AS INT

            EXEC sp_xml_preparedocument @Handle OUTPUT, @xmlTransaction

            /* encryptedAccountId is always the same for each @xmlTransaction param */
            /* Just take the value from the first <t/> element */
            SET @encryptedAccountID = (SELECT eID FROM OPENXML (@Handle, '/ts/t[1]', 2) WITH ( eID VARCHAR '@encryptedAccountId' ))

            /* Go through each <t/> element in the XML document and INSERT */
            INSERT INTO
            [Transactions] 
            (
                [ID],
                [EncryptedAccountID]
            )
            SELECT
                *
            FROM
                OPENXML (@Handle, '/ts/t', 2)
            WITH
            (
                rID UNIQUEIDENTIFIER '@id',
                rEncryptedAccountID VARCHAR (200) '@encryptedAccountId'
            )

            /* Loop through each TransactionValue in the XML document and INSERT */
            INSERT INTO
            [TransactionValues]
            (
                FieldID,
                TransactionID,
                OfficialValue,
                FriendlyValue
            )
            SELECT
                *
            FROM
                OPENXML (@Handle, '/ts/t/tv', 2)
            WITH
            (
                rFieldID INT '@fieldId',
                rTransactionID UNIQUEIDENTIFIER '../@id',
                rOfficialValue NVARCHAR (500) '@officialValue',
                rFriendlyValue NVARCHAR (500) '@friendlyValue'
            )

            /* Dispose of the XML document */
            EXEC sp_xml_removedocument @Handle

        COMMIT TRANSACTION
    END TRY
    BEGIN CATCH

        RETURN @@ERROR

        ROLLBACK TRANSACTION        
    END CATCH

END

Should be fairly straightforward. And yet if I query the results, they're not in the same order as the XML document. The second INSERT statement for the <tv/> elements does store the elements into a second table in the correct order, but the <t/> elements are not stored in their table in the correct order.

Can anyone explain to me why the <t/> elements are not being INSERTed into the table in the same order as they appear in the XML document?


回答1:


If I use the native XQuery support in SQL Server instead of the "legacy" OPENXML stuff, then it would appear that the <t> nodes are indeed inserted into the table in the order they appear in the XML document.

I've used code something like this:

INSERT INTO dbo.[Transactions]([ID], [EncryptedAccountID])
   SELECT
        XT.value('@id', 'uniqueidentifier'),
        XT.value('@encryptedAccountId', 'varchar(200)')
   FROM
      @xmlTransaction.nodes('/ts/t') AS Nodes(XT)

The same could be done for the <tv> subnodes, too.




回答2:


As far as I can see, the documentation on OPENXML does not guarantee anything about order. And order is not guaranteed in a relational table either. So why not just "order by" a certain column to get the order that you wish? That is always how you enforce ordering in sql.

I don't see why you're extracting the encryptedAccountId attribute separately. Why not just insert it in the maininsert statement?

Unrelated tip, if your transaction insert is generating an identity, you can snag a copy of it for your values insert by joining to that parent table on '../@id'. Even if you don't need anything else from the parent table, it seems like a good idea, to make sure that none of your transaction inserts failed.

Tip example:

 INSERT INTO
            [TransactionValues]
            (
                FieldID,
                TransactionID,
                OfficialValue,
                FriendlyValue,
                ParentIdentityFK --new
            )
            SELECT
                shredded.rFieldID, shredded.rTransactionID, shredded.rOfficialValue, shredded.rFriendlyValue, t.SomeIdentity
            FROM
                OPENXML (@Handle, '/ts/t/tv', 2)                
            WITH
            (
                rFieldID INT '@fieldId',
                rTransactionID UNIQUEIDENTIFIER '../@id',
                rOfficialValue NVARCHAR (500) '@officialValue',
                rFriendlyValue NVARCHAR (500) '@friendlyValue'
            ) as shredded
            join Transactions t
                on shredded.rTransactionID = t.ID


来源:https://stackoverflow.com/questions/9997520/using-openxml-in-sql-server-2008-stored-proc-insert-order-differs-from-xml-doc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!