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