SQL-合并到-带子项的XML

时间:2018-07-23 15:44:36

标签: sql sql-server xml

我正在尝试编写SQL脚本以从XML导入数据。在数据库中,我准备了2个表: * CodeGroup带有以下字段:代码/说明

  • 具有以下字段的CodeValue:代码/说明/ CodeGroupId(FK-整数) => 1个CodeGroup有很多CodeValue

这是XML的一部分:

<MASTERDATA plant="SXB">
    <CODEGROUPS>
        <CODEGROUPC><CODEGROUP>ANODE</CODEGROUP><CODEGROUPX>Condition of anodes</CODEGROUPX>
            <CODEVALUES>
              <CODEVALUEC><CODEVALUE>02AD</CODEVALUE><CODEVALUEX>2-Minor Depletion / Damage</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>03AD</CODEVALUE><CODEVALUEX>3-Major Depletion / Plan to refurbish</CODEVALUEX></CODEVALUEC>
            </CODEVALUES>
        </CODEGROUPC>
        <CODEGROUPC><CODEGROUP>AUTO</CODEGROUP><CODEGROUPX>Measurement method</CODEGROUPX>
            <CODEVALUES>
              <CODEVALUEC><CODEVALUE>00ND</CODEVALUE><CODEVALUEX>0-Inspection Not Done/not inspectable</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>01AU</CODEVALUE><CODEVALUEX>Automatic</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>01MA</CODEVALUE><CODEVALUEX>Manual</CODEVALUEX></CODEVALUEC>
            </CODEVALUES>
        </CODEGROUPC>
      </CODEGROUPS>
</MASTERDATA>

我不知道是否可以在单个MERGE INTO语句中编写将值插入CodeGroup和CodeValue的功能:

MERGE INTO [dbo].[CodeGroup] AS TARGET
USING
(
SELECT DISTINCT
    d.x.value('../../@plant[1]', 'nvarchar(15)') as PlantId,
    d.x.value('INGRP[1]', 'nchar(3)') as Code,
    d.x.value('INGRPX[1]', 'nvarchar(20)') as Name
FROM @data.nodes('/MASTERDATA/CODEGROUPS/CODEGROUPC')as d(x)
)
AS SOURCE ON (SOURCE.Code= TARGET.Code
AND SOURCE.PlantId = TARGET.PlantId)
WHEN MATCHED
    THEN UPDATE SET SOURCE.Name= TARGET.Name
WHEN NOT MATCHED BY TARGET
    THEN INSERT (PlantId, Code, Name)
         VALUES (SOURCE.PlantId, SOURCE.Code, SOURCE.Name)
WHEN NOT MATCHED BY SOURCE AND (TARGET.PlantId in (SELECT PlantId FROM #TempTable))
    THEN DELETE;

这是我准备的,但是我不知道该怎么做以及“孩子”(CodeGroup的CodeValue) 谢谢您的帮助

2 个答案:

答案 0 :(得分:0)

您要定位两个不同的表。这不能在单个语句中完成。

尝试以下代码:

DECLARE @xml XML=
'<MASTERDATA plant="SXB">
    <CODEGROUPS>
        <CODEGROUPC><CODEGROUP>ANODE</CODEGROUP><CODEGROUPX>Condition of anodes</CODEGROUPX>
            <CODEVALUES>
              <CODEVALUEC><CODEVALUE>02AD</CODEVALUE><CODEVALUEX>2-Minor Depletion / Damage</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>03AD</CODEVALUE><CODEVALUEX>3-Major Depletion / Plan to refurbish</CODEVALUEX></CODEVALUEC>
            </CODEVALUES>
        </CODEGROUPC>
        <CODEGROUPC><CODEGROUP>AUTO</CODEGROUP><CODEGROUPX>Measurement method</CODEGROUPX>
            <CODEVALUES>
              <CODEVALUEC><CODEVALUE>00ND</CODEVALUE><CODEVALUEX>0-Inspection Not Done/not inspectable</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>01AU</CODEVALUE><CODEVALUEX>Automatic</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>01MA</CODEVALUE><CODEVALUEX>Manual</CODEVALUEX></CODEVALUEC>
            </CODEVALUES>
        </CODEGROUPC>
      </CODEGROUPS>
</MASTERDATA>';

SELECT @xml.value('(/MASTERDATA/@plant)[1]','varchar(100)') AS Masterdata_plant
      ,cGr.value('(CODEGROUP/text())[1]','varchar(100)') AS CodeGroup
      ,cGr.value('(CODEGROUPX/text())[1]','varchar(100)') AS CodeGroupX
      ,cVl.value('(CODEVALUE/text())[1]','varchar(100)') AS CodeValue
      ,cVl.value('(CODEVALUEX/text())[1]','varchar(100)') AS CodeValueX
INTO #tmpXmlData
FROM @xml.nodes('/MASTERDATA/CODEGROUPS/CODEGROUPC') A(cGr)
OUTER APPLY cGr.nodes('CODEVALUES/CODEVALUEC') B(cVl);

SELECT * FROM #tmpXmlData;

使用在#tmpXmlData中找到的结果对两个表分别运行MERGE语句。

答案 1 :(得分:0)

Shnugo through out the challenge, when he said it was impossible to do this in one MERGE statement. And he was right, I think. However, it got me thinking because I have used the OUTPUT clause many times to solve this type of problem, which I think is a MASTER - DETAIL merge, when the key of the master table is not the business key and is an IDENTITY() or NEWID().

What is the most efficient way of doing what you want to do? Shnugo gave you a partial solution, so I have enhanced it (thanks and credit to Shnugo) to give you what you want but it uses an INSERT into temp table, then two MERGE statements. One for the master and one for the detail table.

The first MERGE uses a trick that allows you to retrieve the new IDENTITY field of the new rows, so that you don't have to join back to the master table by the business keys when doing the detail table. I hope this helps.

Here's my code:

Assumptions:

I have put NOT NULLs on several fields. I have put a CASCADE DELETE on the foreign key reference.

create table dbo.CodeGroup (
    CodeGroupId int identity(1,1) primary key,
    Masterdata_plant varchar(100) not null,
    Code varchar(100) not null,
    Description varchar(100) not null

    )
go
CREATE NONCLUSTERED INDEX [IX_CodeGroup] ON dbo.CodeGroup
(
    [Masterdata_plant] ASC,
    [Code] ASC
)
go
create table dbo.CodeValue 
(
    Code varchar(100) not null,
    CodeGroupId int not null,
    Description varchar(100) not null,
     CONSTRAINT PK_CodeValue PRIMARY KEY CLUSTERED 
(
    Code ASC,
    CodeGroupId ASC
)
    )
go
ALTER TABLE dbo.CodeValue  WITH CHECK ADD  CONSTRAINT FK_CodeValue_CodeGroup FOREIGN KEY(CodeGroupId)
REFERENCES dbo.CodeGroup (CodeGroupId)
ON DELETE CASCADE
go
ALTER TABLE dbo.CodeValue CHECK CONSTRAINT FK_CodeValue_CodeGroup
go
delete from dbo.CodeGroup
go
delete from dbo.CodeValue
go

DECLARE @data XML=
'<MASTERDATA plant="SXB">
    <CODEGROUPS>
        <CODEGROUPC><CODEGROUP>ANODE</CODEGROUP><CODEGROUPX>Condition of anodes</CODEGROUPX>
            <CODEVALUES>
              <CODEVALUEC><CODEVALUE>02AD</CODEVALUE><CODEVALUEX>2-Minor Depletion / Damage</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>03AD</CODEVALUE><CODEVALUEX>3-Major Depletion / Plan to refurbish</CODEVALUEX></CODEVALUEC>
            </CODEVALUES>
        </CODEGROUPC>
        <CODEGROUPC><CODEGROUP>AUTO</CODEGROUP><CODEGROUPX>Measurement method</CODEGROUPX>
            <CODEVALUES>
              <CODEVALUEC><CODEVALUE>00ND</CODEVALUE><CODEVALUEX>0-Inspection Not Done/not inspectable</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>01AU</CODEVALUE><CODEVALUEX>Automatic</CODEVALUEX></CODEVALUEC>
              <CODEVALUEC><CODEVALUE>01MA</CODEVALUE><CODEVALUEX>Manual</CODEVALUEX></CODEVALUEC>
            </CODEVALUES>
        </CODEGROUPC>
      </CODEGROUPS>
</MASTERDATA>'
if object_id('tempdb..#tmpXmlData') is not null Begin
    drop table #tmpXmlData
End
create table #tmpXmlData (
    ExistingCodeGroupId int,
    Plant varchar(100),
    CodeGroup varchar(100),
    Description varchar(100),

    CodeValue varchar(100),
    CodeValueDescription varchar(100)
    )
insert into #tmpXmlData
SELECT gc.CodeGroupId
      ,@data.value('(/MASTERDATA/@plant)[1]','varchar(100)') AS Plant
      ,cGr.value('(CODEGROUP/text())[1]','varchar(100)') AS CodeGroup
      ,cGr.value('(CODEGROUPX/text())[1]','varchar(100)') AS Description
      ,cVl.value('(CODEVALUE/text())[1]','varchar(100)') AS CodeValue
      ,cVl.value('(CODEVALUEX/text())[1]','varchar(100)') AS CodeValueDescription

FROM @data.nodes('/MASTERDATA/CODEGROUPS/CODEGROUPC') A(cGr)
OUTER APPLY cGr.nodes('CODEVALUES/CODEVALUEC') B(cVl)
left join CodeGroup gc
on gc.Masterdata_plant = @data.value('(/MASTERDATA/@plant)[1]','varchar(100)')
and gc.Code = cGr.value('(CODEGROUP/text())[1]','varchar(100)')

SELECT * FROM #tmpXmlData;
if object_id('tempdb..#IDs') is not null Begin
    drop table #IDs
end
    create table #IDs(
    CodeGroupID int,
    Plant varchar(100),
    Code varchar(100),
    Description varchar(100)
    )
    go
    CREATE NONCLUSTERED INDEX [IX_CodeGroup] ON #IDs
(
    [Plant] ASC,
    [Code] ASC
)
;MERGE INTO dbo.CodeGroup AS TARGET
USING
(
select ExistingCodeGroupId, Plant, CodeGroup,Description
from #tmpXmlData
group by ExistingCodeGroupId, Plant, CodeGroup,Description
)
AS SOURCE ON (SOURCE.ExistingCodeGroupId= TARGET.CodeGroupId)
    --Plant, and CodeGroup must have matched already in left join above.
WHEN MATCHED
    and TARGET.Description <> SOURCE.Description
    THEN UPDATE SET TARGET.Description = SOURCE.Description
WHEN NOT MATCHED BY TARGET
    THEN INSERT (Masterdata_plant, Code, Description)
         VALUES (SOURCE.Plant, SOURCE.CodeGroup, SOURCE.Description)
WHEN NOT MATCHED BY SOURCE
    THEN DELETE
output inserted.*
into #IDs; --This gets us the new CodeGroupId's


    select * from #IDs

;MERGE INTO dbo.CodeValue AS TARGET
USING
(
    select CodeGroupID=coalesce(d.ExistingCodeGroupId,i.CodeGroupID), CodeValue, CodeValueDescription
    from #tmpXmlData d
    left join #IDs i
    on i.Plant = d.Plant
    and i.Code = d.CodeGroup

    group by coalesce(d.ExistingCodeGroupId,i.CodeGroupID), CodeValue, CodeValueDescription
)
AS SOURCE ON (SOURCE.CodeGroupID= TARGET.CodeGroupID
                and SOURCE.CodeValue = Target.Code)
WHEN MATCHED
    and TARGET.Description <> SOURCE.CodeValueDescription
    THEN UPDATE SET TARGET.Description = SOURCE.CodeValueDescription
WHEN NOT MATCHED BY TARGET
    THEN INSERT (CodeGroupID, Code, Description)
         VALUES (SOURCE.CodeGroupID, SOURCE.CodeValue, SOURCE.CodeValueDescription)
WHEN NOT MATCHED BY SOURCE
    THEN DELETE
output inserted.*;

select * from dbo.CodeGroup
select * from dbo.CodeValue