XML转换为TABLE

时间:2018-05-26 04:48:00

标签: sql-server xml

我有一个表,每行包含一个XML文件(列 XMLRow )。每个xml包含两部分, PartI PartII 。对于每个partI,我们可以有多个PartII(SEQUENCE)。我想将它插入我的数据库。第一部分将插入TableI,PartII的第二个表称为TableII。

CREATE TABLE XMLDATA {
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
insertDate Datetime NOT NULL DEFAULT getdate(),
XMLRow XML NOT NULL
}

以下是XMLDATA表中的XMLRow示例,

<MessageFrame>
    <messageNumber>20</messageNumber>
    <value>
        <BasicSafetyMessage>
            <partI>
                <msgCnt>127</msgCnt>
            </partI>
            <partII>
                <SEQUENCE>
                    <partII-Id>0</partII-Id>
                    <partII-Value>BLUE</partII-Value>
                </SEQUENCE>
                <SEQUENCE>
                    <partII-Id>3</partII-Id>
                    <partII-Value>RED</partII-Value>
                </SEQUENCE>
            </partII>
        </BasicSafetyMessage>
    </value>
</MessageFrame>

创建如下表格,

CREATE TABLE TABLEI {
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
messageNumber INT NOT NULL,
msgCnt INT NOT NULL
};
GO
CREATE TABLE TABLEII {
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
T1_id Not NULL REFERENCES TableI (id) ON DELETE CASCADE,
partII-Id INT NOT NULL,
partII-Value NVARCHAR(50) NOT NULL
};
GO

我在每个表中都有Identity Primary Keys,因为数据库中存储的xml中没有唯一ID。

DECLARE @docHandle INT
DECLARE @XML AS XML
EXEC sp_xml_preparedocument @docHandle OUTPUT, @XML 
SELECT @XML = XMLRow FROM [XMLDATA] 

INSERT INTO TableI (messageId, msgCnt)
SELECT messageNumber,  msgCnt       
FROM OPENXML (@docHandle, '/MessageFrame/value/BasicSafetyMessage/partI')  
WITH (
        messageNumber int '../../../messageId',
        msgCnt        int 'msgCnt',
    ) 

INSERT INTO TableII (T1_id, partII-Id, partII-Value)
SELECT @XML = XMLRow FROM [XMLDATA] 
SELECT T1_id,  partII-Id , partII-Value 
FROM OPENXML (@docHandle, '/MessageFrame/value/BasicSafetyMessage/PartII/SEQUENCE')  
WITH (
        T1_id              int @@IDENTITY, 
        partII-Id          int 'partII-Id',
        partII-Value       int 'partII-Value',
    ) 
EXEC sp_xml_removedocument @docHandle

结果应该是这样的,

**Table1**
------------------------
id messageNumber msgCnt      
------------------------
1  20            127


**Table2**
-------------------------------
id T1_id partII-Id partII-Value
-------------------------------
1  1     0         BLUE
2  1     3         RED

第二个INSERT INTO返回NULL。有人可以帮我这个吗?我在这里做错了吗?

2 个答案:

答案 0 :(得分:1)

好的。所以这将使你的XML变得扁平化:

DECLARE @XmlData XML = N'<MessageFrame>
    <messageNumber>20</messageNumber>
    <value>
        <BasicSafetyMessage>
            <partI>
                <msgCnt>127</msgCnt>
            </partI>
            <partII>
                <SEQUENCE>
                    <partII-Id>0</partII-Id>
                    <partII-Value>BLUE</partII-Value>
                </SEQUENCE>
                <SEQUENCE>
                    <partII-Id>3</partII-Id>
                    <partII-Value>RED</partII-Value>
                </SEQUENCE>
            </partII>
        </BasicSafetyMessage>
    </value>
</MessageFrame>';

SELECT
    N1.MessageFrame.value('(messageNumber)[1]', 'INT') AS MessageNumber
  , N3.partI.value('(msgCnt)[1]', 'INT') AS MessageCount
  , N5.Sequence.value('(partII-Id)[1]', 'INT') AS Id
  , N5.Sequence.value('(partII-Value)[1]', 'NVARCHAR(50)') AS Name
FROM @XmlData.nodes('MessageFrame') AS N1(MessageFrame)
CROSS APPLY N1.MessageFrame.nodes('value/BasicSafetyMessage') AS N2(MessageBasicSafety)
CROSS APPLY N2.MessageBasicSafety.nodes('partI') AS N3(partI)
CROSS APPLY N2.MessageBasicSafety.nodes('partII') AS N4(partII)
CROSS APPLY N4.partII.nodes('//partII/SEQUENCE') AS N5(Sequence);

并会给你以下结果

MessageNumber MessageCount Id Name
------------- ------------ -- ---- 
20            127          0  BLUE 
20            127          3  RED  

所以现在只需将所有内容放到具有正确引用的表中,您完全可以这样做。

答案 1 :(得分:1)

首先:FROM OPENXML与相应的SP准备和删除文档已经过时,不应再使用了。而是使用适当的methods the XML data type provides

使用此查询可以轻松地展平您的XML:

SELECT id
      ,XMLRow.value(N'(/MessageFrame/messageNumber/text())[1]',N'int')
      ,XMLRow.value(N'(/MessageFrame/value/BasicSafetyMessage/partI/msgCnt/text())[1]',N'int')
      ,p2.value(N'(partII-Id/text())[1]',N'int')
      ,p2.value(N'(partII-Value/text())[1]',N'nvarchar(max)')
FROM XMLDATA
CROSS APPLY XMLRow.nodes(N'/MessageFrame/value/BasicSafetyMessage/partII/SEQUENCE') AS A(p2);

但是以下内容将为您提供一种简单的方法来实时交叉引用 。为此我向TABLEI添加了一列,希望对您有用:

CREATE TABLE XMLDATA  (
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
insertDate Datetime NOT NULL DEFAULT getdate(),
XMLRow XML NOT NULL
);

CREATE TABLE TABLEI (
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
XMLDATA_ID INT, --I add this column, to use the OUTPUT clause with INSERT
                --If this is no option for you, read this: 
                --https://stackoverflow.com/questions/10949730/is-it-possible-to-for-sql-output-clause-to-return-a-column-not-being-inserted
messageNumber INT NOT NULL,
msgCnt INT NOT NULL
);

CREATE TABLE TABLEII (
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
T1_id INT Not NULL REFERENCES TableI (id) ON DELETE CASCADE,
[partII-Id] INT NOT NULL,
[partII-Value] NVARCHAR(50) NOT NULL
);
GO

- 我插入两个XML来显示基于集合的原则:

INSERT INTO XMLDATA(XMLRow) VALUES
(N'<MessageFrame>
    <messageNumber>20</messageNumber>
    <value>
        <BasicSafetyMessage>
            <partI>
                <msgCnt>127</msgCnt>
            </partI>
            <partII>
                <SEQUENCE>
                    <partII-Id>0</partII-Id>
                    <partII-Value>BLUE</partII-Value>
                </SEQUENCE>
                <SEQUENCE>
                    <partII-Id>3</partII-Id>
                    <partII-Value>RED</partII-Value>
                </SEQUENCE>
            </partII>
        </BasicSafetyMessage>
    </value>
</MessageFrame>')
,(N'<MessageFrame>
    <messageNumber>30</messageNumber>
    <value>
        <BasicSafetyMessage>
            <partI>
                <msgCnt>300</msgCnt>
            </partI>
            <partII>
                <SEQUENCE>
                    <partII-Id>10</partII-Id>
                    <partII-Value>BLACK</partII-Value>
                </SEQUENCE>
                <SEQUENCE>
                    <partII-Id>20</partII-Id>
                    <partII-Value>WHITE</partII-Value>
                </SEQUENCE>
            </partII>
        </BasicSafetyMessage>
    </value>
</MessageFrame>');

- 我们需要一个临时表来获取基于IDENTITY的ID

DECLARE @tblGetInsertedId TABLE(XMLDATA_ID INT, Id INT);

- 我们使用OUTPUT子句来缓冲XMLDATA中的id和TABLEI中新给定的id:

INSERT INTO TableI(XMLDATA_ID, messageNumber, msgCnt)
OUTPUT inserted.XMLDATA_ID, inserted.id INTO @tblGetInsertedId(XMLDATA_ID, id)
SELECT id
      ,XMLRow.value(N'(/MessageFrame/messageNumber/text())[1]',N'int')
      ,XMLRow.value(N'(/MessageFrame/value/BasicSafetyMessage/partI/msgCnt/text())[1]',N'int')
FROM XMLDATA;

- 我们将其余部分插入TABLEII,加入id-puffer

INSERT INTO TABLEII(T1_id,[partII-Id], [partII-Value])
SELECT i.Id
      ,p2.value(N'(partII-Id/text())[1]',N'int')
      ,p2.value(N'(partII-Value/text())[1]',N'nvarchar(max)')
FROM XMLDATA xd
CROSS APPLY xd.XMLRow.nodes(N'/MessageFrame/value/BasicSafetyMessage/partII/SEQUENCE') AS A(p2)
LEFT JOIN @tblGetInsertedId i ON xd.id=i.XMLDATA_ID;

- 对结果感到满意:-D

SELECT * FROM TABLEI
SELECT * FROM TABLEII

- 清理(小心真实数据!)

GO
/*
DROP TABLE TABLEII;
DROP TABLE TABLEI;
DROP TABLE XMLDATA;
*/