SQL Server Update Value XML Node

谁都会走 提交于 2019-12-07 20:00:22

问题


I have 2 tables in SQL Server

Table1

   ID   - Name  - Phone
   1      HK      999    
   2      RK      888
   3      SK      777
   4      PK      666

Table2

   ID   - XMLCol
   1      XMLVal1

XMLVal1

   <Root>
    <Data1>
     <ID>1</ID>
     <Name>HK</Name> 
     </Data1>
    <Data1>
     <ID>2</ID>
     <Name>RK</Name>
     </Data1>
    </Root>

Now I am inserting a GUID column into Table1

Table1

   ID   - Name  - Phone  - GUID
   1      HK      999      HJHHKHJHJHKJH8788 
   2      RK      888      OONMNy7878HJHJHSD
   3      SK      777      POMSDHBSNB775SD87
   4      PK      666      HRBMASJMN76448NDN

In Table2 XML column, I want to update the ID node with the new GUID value without changing the element name.

So now the XML would be

   <Root>
    <Data1>
     <ID>HJHHKHJHJHKJH8788</ID>
     <Name>HK</Name> 
     </Data1>
    <Data1>
     <ID>OONMNy7878HJHJHSD</ID>
     <Name>RK</Name>
     </Data1>
    </Root>

This will happen for all rows in Table2.

Please help me with the query for this.


回答1:


It is not possible to update the XML in more than one place at a time so you have to do this in a loop of some kind. The best I could come up with was to extract the ID's from the XML in Table2 and join against Table1.ID to produce a temp table that holds Table2.ID ordinal position of the Data1 node in the XML (OrdPos) and the new GUID value.

Then you can loop over the max number of nodes present in the XML column and do the update.

-- Variable used to loop over nodes
declare @I int 

-- Temp table to hold the work that needs to be done.
create table #T
(
  ID int, -- ID from table2
  OrdPos int, -- Ordinal position of node Data1 in root
  GUID uniqueidentifier, -- New ID
  primary key (OrdPos, ID)
)

-- Shred the XML in Table2, join to Table1 to get GUID
insert into #T(ID, OrdPos, GUID)
select T2.ID,
       row_number() over(partition by T2.ID order by D.N) as OrdPos,
       T1.GUID
from Table2 as T2
  cross apply T2.XMLCol.nodes('Root[1]/Data1') as D(N)
  inner join Table1 as T1
    on T1.ID = D.N.value('(ID/text())[1]', 'int')

-- Get the max number of nodes in one row that needs to be updated
set @I = 
  (
    select top(1) count(*)
    from #T
    group by ID
    order by 1 desc
  )

-- Do the updates in a loop, one level at a time
while @I > 0
begin
  update T2
  set XMLCol.modify('replace value of (/Root[1]/Data1[sql:variable("@I")]/ID/text())[1] 
                     with sql:column("T.GUID")')
  from Table2 as T2
    inner join #T as T
      on T2.ID = T.ID
  where T.OrdPos = @I

  set @I = @I - 1
end

drop table #T

SQL Fiddle




回答2:


I got one of them to update.

Close, but no cigar. But it's end of the day.

IF OBJECT_ID('tempdb..#XmlHolderTable') IS NOT NULL
begin
        drop table #XmlHolderTable
end



IF OBJECT_ID('tempdb..#ScalarHolderTable') IS NOT NULL
begin
        drop table #ScalarHolderTable
end



CREATE TABLE #ScalarHolderTable
(
ScalarKey int not null , 
Name varchar(16) , 
Phone varchar(16) , 
UUID uniqueidentifier 
)


CREATE TABLE #XmlHolderTable
(
XmlSurrogateIdentityKey int not null identity (1001, 1), 
TheXml xml
)


INSERT INTO #ScalarHolderTable 
( ScalarKey , Name , Phone , UUID )
            select  1   ,  'HK' ,    999 , NEWID() 
union all   select  2   ,  'RK' ,    888 , NEWID()
union all   select  3   ,  'SK' ,    777 , NEWID()
union all   select  4   ,  'PK' ,    66  , NEWID()


-- Declare XML variable

DECLARE @data XML;

-- Element-centered XML

SET @data = N'


 <Root>
    <Data1>
        <ID>1</ID>
        <Name>HK</Name> 
    </Data1>
    <Data1>
        <ID>2</ID>
        <Name>RK</Name>
    </Data1>
</Root>

';


INSERT INTO #XmlHolderTable ( TheXml) values ( @data )



select TheXml.value('(//Data1/ID)[1]','int') ,  * from #XmlHolderTable


SELECT Data.Col.value('(.)[1]','Int') AS Id
FROM #XmlHolderTable xmlHolder
CROSS APPLY
TheXml.nodes('//Data1/ID') AS Data(Col)

/*
SELECT Data.Col.value('(Id)[1]','Int') AS Id
FROM @Data.nodes('/Root/Data') AS Data(Col)
*/



declare @counter int
select @counter = 0

/*
WHILE ( 
            exists ( select top 1 null
                    From
                        #XmlHolderTable xmlHolder
                    CROSS APPLY
                    TheXml.nodes('//Data1/ID') AS Data(Col) , #ScalarHolderTable scalarHolder
            Where
                ISNUMERIC (     Data.Col.value('(.)[1]','varchar(40)')      ) > 0 

                    )
    )
BEGIN

    select @counter= @counter + 1
    print '/@counter/'
    print @counter
    print ''
*/



UPDATE 
   #XmlHolderTable
SET 
   TheXml.modify('replace value of (//Data1/ID/text())[1] with sql:column("scalarHolder.UUID")') 

--select Data.Col.value('(.)[1]','Int') as MyValue , scalarHolder.ScalarKey

From
    #XmlHolderTable xmlHolder CROSS APPLY TheXml.nodes('//Data1/ID') AS Data(Col) 
    , #ScalarHolderTable scalarHolder
Where
    Data.Col.value('(.)[1]','Int') = scalarHolder.ScalarKey


/*
END
*/



select * from #ScalarHolderTable
select TheXml from #XmlHolderTable




IF OBJECT_ID('tempdb..#XmlHolderTable') IS NOT NULL
begin
        drop table #XmlHolderTable
end



IF OBJECT_ID('tempdb..#ScalarHolderTable') IS NOT NULL
begin
        drop table #ScalarHolderTable
end



回答3:


Do you absolutely want to modify current xml? because if you can just generate it from your data, it will be much simplier:

update Table2 set
     XMLCol =
     (
         select T1.GUID as ID, T1.Name as Name
         from T2.XMLCol.nodes('Root/Data1') as T(C)
             inner join Table1 as T1 on
                 T1.ID = T.C.value('ID[1]', 'int') and
                 T1.Name = T.C.value('Name[1]', 'varchar(10)')
         for xml path('Data1'), root('Root'), type
     )
from Table2 as T2

see sql fiddle example

update Ok, as far as I understand, each Data1 have only one ID. Then you can do this:

declare @temp table(ID int, T1_ID int, XMLcol xml)

-- split xml, each ID goes in own row    
insert into @temp
select ID, T.C.value('ID[1]', 'int') as ID, T.C.query('.') as XMLCol
from Table2 as T2
    outer apply T2.XMLCol.nodes('Root/Data1') as T(C)

-- modify xml    
update @temp set
    XMLCol.modify('
        replace value of (Data1/ID/text())[1]
        with sql:column("T1.GUID")
    ')
from @temp as T
    inner join Table1 as T1 on T1.ID = T.T1_ID

-- modify original table    
update Table2 set
   XMLCol =
   (
       select (select T.XMLcol)
       from @temp as T
       where T.ID = T2.ID
       for xml path(''), root('Root'), type
   )
from Table2 as T2


来源:https://stackoverflow.com/questions/18132697/sql-server-update-value-xml-node

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