SQL Server:导入具有一对多关系的XML文档(插入多个表)

时间:2014-07-23 18:57:06

标签: sql-server

我正在使用SQL Server 2012导入一个简单的XML文档。以下是XML的示例:

<Orders>
    <Order>
        <Customer>Bob Smith</Customer>
        <Address>123 Main St, Anytown, NY</Address>
        <OrderItems>
            <Item>
                <ItemName>Table</ItemName>
                <Quantity>1</Quantity>
            </Item>
            <Item>
                <ItemName>Chair</ItemName>
                <Quantity>4</Quantity>
            </Item>
        </OrderItems>
    </Order>
    <Order>
        <Customer>Jane Doe</Customer>
        <Address>456 Broadway Ave, Someplace, TX</Address>
        <OrderItems>
            <Item>
                <ItemName>Banana Slicer</ItemName>
                <Quantity>1</Quantity>
            </Item>
        </OrderItems>
    </Order>
    <Order>
        <Customer>Joe Public</Customer>
        <Address>789 Euclid Rd, Random, ID</Address>
        <OrderItems>
            <Item>
                <ItemName>Hammer</ItemName>
                <Quantity>1</Quantity>
            </Item>
            <Item>
                <ItemName>Nails</ItemName>
                <Quantity>50</Quantity>
            </Item>
            <Item>
                <ItemName>Chisel</ItemName>
                <Quantity>2</Quantity>
            </Item>
        </OrderItems>
    </Order>
</Orders>

请注意,每个订单中都可以包含一个或多个商品。

以下是目标表(暂时使用临时表):

CREATE TABLE dbo.#Order
(
    [OrderID] int IDENTITY(1001,1) PRIMARY KEY,
    [Customer] varchar(200) NOT NULL,
    [Address] varchar(200) NOT null
);

CREATE TABLE dbo.#OrderItem
(
    [OrderItemID] int IDENTITY(5001,1) PRIMARY KEY,
    [OrderID] int
        FOREIGN KEY REFERENCES dbo.#Order(OrderID)
        ON DELETE CASCADE,
    [ItemName] varchar(100) NOT NULL,
    [Quantity] int NOT NULL
);

上述内容应按如下方式导入:

OrderID     Customer    Address
-------     --------    -------
1001        Bob Smith   123 Main St, Anytown, NY
1002        Jane Doe    456 Broadway Ave, Someplace, TX
1003        Joe Public  789 Euclid Rd, Random, ID

OrderItemID OrderID ItemName        Quantity
----------- ------- --------        --------
5001        1001    Table           1
5002        1001    Chair           4
5003        1002    Banana Slicer   1
5004        1003    Hammer          1
5005        1003    Nails           50
5006        1003    Chisel          2

有没有办法在不使用游标的情况下将所有数据插入OrderOrderItem表中?我不反对使用光标,但我想知道是否有一个更简单的,基于集合的替代方案。棘手的部分是OrderItem需要知道导入的每个订单的插入OrderID

这是我最初尝试导入数据(使用游标)。我正在使用临时表(而不是permnanent表)并对XML进行硬编码,以便更容易复制和运行此代码:

-------------------------------------------------------------------------------
-- Create Temp Tables
-------------------------------------------------------------------------------

IF OBJECT_ID('tempdb.dbo.#OrderItem') IS NOT NULL
    DROP TABLE #OrderItem
GO

IF OBJECT_ID('tempdb.dbo.#Order') IS NOT NULL
    DROP TABLE #Order
GO

CREATE TABLE dbo.#Order
(
    [OrderID] int IDENTITY(1001,1) PRIMARY KEY,
    [Customer] varchar(200) NOT NULL,
    [Address] varchar(200) NOT null
);

CREATE TABLE dbo.#OrderItem
(
    [OrderItemID] int IDENTITY(5001,1) PRIMARY KEY,
    [OrderID] int
        FOREIGN KEY REFERENCES dbo.#Order(OrderID)
        ON DELETE CASCADE,
    [ItemName] varchar(100) NOT NULL,
    [Quantity] int NOT NULL
);

-------------------------------------------------------------------------------
-- Define Sample XML Document
-------------------------------------------------------------------------------

DECLARE @xml xml = '
    <Orders>
        <Order>
            <Customer>Bob Smith</Customer>
            <Address>123 Main St, Anytown, NY</Address>
            <OrderItems>
                <Item>
                    <ItemName>Table</ItemName>
                    <Quantity>1</Quantity>
                </Item>
                <Item>
                    <ItemName>Chair</ItemName>
                    <Quantity>4</Quantity>
                </Item>
            </OrderItems>
        </Order>
        <Order>
            <Customer>Jane Doe</Customer>
            <Address>456 Broadway Ave, Someplace, TX</Address>
            <OrderItems>
                <Item>
                    <ItemName>Banana Slicer</ItemName>
                    <Quantity>1</Quantity>
                </Item>
            </OrderItems>
        </Order>
        <Order>
            <Customer>Joe Public</Customer>
            <Address>789 Euclid Rd, Random, ID</Address>
            <OrderItems>
                <Item>
                    <ItemName>Hammer</ItemName>
                    <Quantity>1</Quantity>
                </Item>
                <Item>
                    <ItemName>Nails</ItemName>
                    <Quantity>50</Quantity>
                </Item>
                <Item>
                    <ItemName>Chisel</ItemName>
                    <Quantity>2</Quantity>
                </Item>
            </OrderItems>
        </Order>
    </Orders>';

-------------------------------------------------------------------------------
-- Query XML Document
-------------------------------------------------------------------------------

--SELECT
--  Tbl.Col.value('Customer[1]', 'varchar(200)') AS [Customer],
--  Tbl.Col.value('Address[1]', 'varchar(200)') AS [Address],
--  Tbl.Col.query('./OrderItems/Item') AS [ItemsXML]
--FROM
--  @xml.nodes('//Order') AS Tbl(Col)


-------------------------------------------------------------------------------
-- Import XML document (Attempt 1 - using a cursor)
-------------------------------------------------------------------------------

DECLARE @OrderNode xml;
DECLARE @OrderID int;

DECLARE cur CURSOR FAST_FORWARD READ_ONLY LOCAL FOR
    SELECT Tbl.Col.query('.') FROM @xml.nodes('//Order') AS Tbl(Col)

OPEN cur

FETCH NEXT FROM cur INTO @OrderNode

WHILE @@FETCH_STATUS = 0 BEGIN

    ------------------------------------------
    -- Import order
    ------------------------------------------

    INSERT INTO #Order
    (
        [Customer],
        [Address]
    )
    SELECT
        Tbl.Col.value('Customer[1]', 'varchar(200)'),
        Tbl.Col.value('Address[1]', 'varchar(200)')
    FROM @OrderNode.nodes('Order') AS Tbl(Col);


    ------------------------------------------
    -- Get the inserted order ID
    ------------------------------------------

    SELECT @OrderID = SCOPE_IDENTITY();


    ------------------------------------------
    -- Import order items
    ------------------------------------------

    INSERT INTO #OrderItem
    (
        OrderID,
        ItemName,
        Quantity
    )
    SELECT
        @OrderID,
        Tbl.Col.value('ItemName[1]', 'varchar(100)'),
        Tbl.Col.value('Quantity[1]', 'int')
    FROM @OrderNode.nodes('Order/OrderItems/Item') AS Tbl(Col);


    -------------------------------------------------------------------------------
    -- Move on to next order
    -------------------------------------------------------------------------------

    FETCH NEXT FROM cur INTO @OrderNode

END

CLOSE cur
DEALLOCATE cur

-------------------------------------------------------------------------------
-- Show results
-------------------------------------------------------------------------------

SELECT * FROM #Order
SELECT * FROM #OrderItem

2 个答案:

答案 0 :(得分:1)

要扩展xQbert的注释,如果您的示例XML数据在XML变量@x中,您可以使用以下命令来放置所有订单&amp;将商品订购到临时表中:

DECLARE @x XML = '... your sample xml ...';

SELECT  Ord.n.value('for $i in . return count(../*[. << $i]) + 1', 'int') AS OrderNbr
        , Ord.n.value('./Customer[1]','varchar(200)') AS Customer
        , Ord.n.value('./Address[1]','varchar(200)') AS Address
        , Item.n.value('./ItemName[1]','varchar(200)') AS ItemName
        , Item.n.value('./Quantity[1]','int') AS Quantity
INTO    #Orders
FROM    @x.nodes('/Orders/Order') AS Ord(n)
        CROSS APPLY Ord.n.nodes('./OrderItems/Item') AS Item(n);

然后你需要做两次插入,一次插入包含DISTINCT主顺序记录的父表,用OUTPUT子句保留标识值,第二次插入包括所有订单项并使用第一次保存的PK值插入。看到这个链接:

How can I INSERT data into two tables simultaneously in SQL Server?

答案 1 :(得分:1)

进一步构建Kevin Suchlicki的答案,如果您将XML声明为XML变量@x,然后插入以下内容,您将在原始帖子中获得结果:

DECLARE @Order TABLE ([OrderID] int IDENTITY(1001,1) PRIMARY KEY,
    [Customer] varchar(200) NOT NULL,
    [Address] varchar(200) NOT NULL)

DECLARE @OrderItem TABLE (
    [OrderItemID] int IDENTITY(5001,1) PRIMARY KEY,
    [OrderID] int,
    [ItemName] varchar(100) NOT NULL,
    [Quantity] int NOT NULL
)

INSERT INTO @Order (Customer, Address)
SELECT  t.c.value('(Customer)[1]','varchar(200)') AS 'Customer',  
        t.c.value('(Address)[1]','varchar(200)') AS 'Address'
FROM    @x.nodes('Orders/Order') t(c);


INSERT INTO @OrderItem (OrderID, ItemName, Quantity)
SELECT  
        Ordr.OrderID
        , Item.n.value('./ItemName[1]','varchar(200)') AS ItemName
        , Item.n.value('./Quantity[1]','int') AS Quantity
FROM    @x.nodes('Orders/Order') Ord(n)
    CROSS APPLY Ord.n.nodes('./OrderItems/Item') AS Item(n)
    JOIN @Order Ordr ON Ordr.Customer = Ord.n.value('./Customer[1]','varchar(200)')

SELECT *
FROM @Order

SELECT *
FROM @OrderItem

显然这里使用表变量,但你可以很容易地将它们换成临时表。