问题
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