这是一个两部分的问题。我有一个带有XML列的表,其中包含零件的组件和属性数据。需要在所有记录的所有组件和属性中更新属性之一。 我从下面的Technet文章中找到了使用技术的部分解决方案: https://social.technet.microsoft.com/wiki/contents/articles/28601.t-sql-tips-search-and-replace-string-from-multiple-nodes-within-a-xml-document.aspx 不足之处:
这是数据的简化形式。实际上,还有许多条目需要重新映射。
我确实使用了蛮力版本来重新映射除零长度字符串以外的所有值,但如果可能的话,我还是宁愿在一个语句中执行所有更新。
示例代码:
DECLARE @Map TABLE ([Old] VARCHAR(1) NOT NULL, [New] VARCHAR(1) NOT NULL)
insert @Map ([Old], [New])
Values ('D', 'E'),('3', 'I'),('', 'I');
DECLARE @ConfigMaster TABLE (
[ConfigCode] [nvarchar](15) NOT NULL,
[ConfigMaster] [xml] NOT NULL
)
INSERT @configMaster (ConfigCode, [ConfigMaster])
values('TestPart01', '<ConfigMaster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Components>
<Component Name="FRSTK">
<Attributes>
<Attribute Name="O-FLGOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="22" />
<Attribute Name="O-GRDLGT" Value="0" OptionListId="" PrintCode="" DataEntryOrder="0" />
<Attribute Name="O-LEADLG" Value="72" OptionListId="" PrintCode="D" DataEntryOrder="4" />
<Attribute Name="O-LEADTP" Value="R" OptionListId="1F3" PrintCode="D" DataEntryOrder="3" />
<Attribute Name="O-LOCOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="24" />
<Attribute Name="O-CODE" Value="N18A13" OptionListId="1F1" PrintCode="D" DataEntryOrder="1" />
<Attribute Name="O-CONOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="25" />
<Attribute Name="O-FITOPT" Value="Y" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="23" />
<Attribute Name="O-FITTIN" Value="BG" OptionListId="1F5" PrintCode="D" DataEntryOrder="32" />
<Attribute Name="O-FLANGE" Value="" OptionListId="1F2" PrintCode="" DataEntryOrder="0" />
</Attributes>
</Component>
</Components>
</ConfigMaster>')
select * from @ConfigMaster; --before
WHILE EXISTS
(
SELECT *
FROM @ConfigMaster
WHERE ConfigMaster.exist('//Component/Attributes/Attribute[ contains(@PrintCode, "D")]') = 1
)
BEGIN
UPDATE [m]
SET ConfigMaster.modify('replace value of
(//Component/Attributes/Attribute[ contains(@PrintCode , "D")]/@PrintCode)[1]
with sql:column("New") ')
FROM
(
SELECT ConfigMaster,
t.u.value('./@PrintCode[1]','varchar(1)') AS OrigValue
FROM @ConfigMaster as [p]
CROSS APPLY p.ConfigMaster.nodes('/ConfigMaster/Components/Component/Attributes/Attribute[ contains(@PrintCode , "D")]') as t(u)
) as [m]
Inner join @Map as s on s.Old = m.OrigValue
END
select * from @ConfigMaster --after pass 1: "D" updated to "E"
WHILE EXISTS
(
SELECT *
FROM @ConfigMaster
WHERE ConfigMaster.exist('//Component/Attributes/Attribute[ contains(@PrintCode, "3")]') = 1
)
BEGIN
UPDATE [m]
SET ConfigMaster.modify('replace value of (//Component/Attributes/Attribute[ contains(@PrintCode , "3")]/@PrintCode)[1] with sql:column("New") ')
FROM
(
SELECT ConfigMaster,
t.u.value('./@PrintCode[1]','varchar(1)') AS OrigValue
FROM @ConfigMaster as [p]
CROSS APPLY p.ConfigMaster.nodes('/ConfigMaster/Components/Component/Attributes/Attribute[ contains(@PrintCode , "3")]') as t(u)
) as [m]
Inner join @Map as s on s.Old = m.OrigValue
END
select * from @ConfigMaster --after pass 2: "3" updated to "I"
WHILE EXISTS
(
SELECT *
FROM @ConfigMaster
WHERE ConfigMaster.exist('//Component/Attributes/Attribute[contains(@PrintCode , "")]') = 1 --the empty string "" has special meaning to the contains() function
)
BEGIN
UPDATE [m]
SET ConfigMaster.modify('replace value of (//Component/Attributes/Attribute[ contains(@PrintCode , "")]/@PrintCode)[1] with sql:column("New") ')
FROM
(
SELECT ConfigMaster,
t.u.value('./@PrintCode[1]','varchar(1)') AS OrigValue
FROM @ConfigMaster as [p]
CROSS APPLY p.ConfigMaster.nodes('/ConfigMaster/Components/Component/Attributes/Attribute[ contains(@PrintCode , "")]') as t(u)
) as [m]
Inner join @Map as s on s.Old = isnull(m.OrigValue,'')
break --this will not exit otherwise...
END
select * from @ConfigMaster --after pass 3: no change
谢谢!
克莱顿
答案 0 :(得分:1)
我讨厌SQL Server中的循环...有时我们无法避免它,但是在大多数情况下,还有其他方法,并且在大多数情况下,这些基于集合的方法更好:
您似乎不需要声明的名称空间,所以我让他们放弃...
我减少了一点,所以它可能无法完全满足您的需求。让我知道,如果有什么遗漏:
DECLARE @ConfigMaster TABLE (
[ConfigCode] [nvarchar](15) NOT NULL,
[ConfigMaster] [xml] NOT NULL
)
INSERT @configMaster (ConfigCode, [ConfigMaster])
values('TestPart01', '<ConfigMaster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Components>
<Component Name="FRSTK">
<Attributes>
<Attribute Name="O-FLGOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="22" />
<Attribute Name="O-GRDLGT" Value="0" OptionListId="" PrintCode="" DataEntryOrder="0" />
<Attribute Name="O-LEADLG" Value="72" OptionListId="" PrintCode="D" DataEntryOrder="4" />
<Attribute Name="O-LEADTP" Value="R" OptionListId="1F3" PrintCode="D" DataEntryOrder="3" />
<Attribute Name="O-LOCOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="24" />
<Attribute Name="O-CODE" Value="N18A13" OptionListId="1F1" PrintCode="D" DataEntryOrder="1" />
<Attribute Name="O-CONOPT" Value="N" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="25" />
<Attribute Name="O-FITOPT" Value="Y" OptionListId="YES_NO" PrintCode="3" DataEntryOrder="23" />
<Attribute Name="O-FITTIN" Value="BG" OptionListId="1F5" PrintCode="D" DataEntryOrder="32" />
<Attribute Name="O-FLANGE" Value="" OptionListId="1F2" PrintCode="" DataEntryOrder="0" />
</Attributes>
</Component>
</Components>
</ConfigMaster>')
select * from @ConfigMaster; --before
-蛮力强行,但简单俗气的一句话 ...
UPDATE @ConfigMaster SET ConfigMaster=
REPLACE(REPLACE(REPLACE(CAST(ConfigMaster AS NVARCHAR(MAX)),' PrintCode="D"',' PrintCode="E"'),' PrintCode="3"',' PrintCode="I"'),' PrintCode=""',' PrintCode="I"');
-XQuery,如果对您来说这些限制还可以(假设可能有多个<Component>
):
UPDATE @ConfigMaster SET ConfigMaster=
ConfigMaster.query
(N'
<ConfigMaster>
<Components>
{
for $comp in //ConfigMaster/Components/Component
return
<Component>{$comp/@Name}
<Attributes>
{
for $node in $comp/Attributes/Attribute
return
<Attribute>
{
for $attr in $node/@*
return
if(local-name($attr)!="PrintCode") then
$attr
else
attribute PrintCode {if($attr="D") then "E" else "I"}
}
</Attribute>
}
</Attributes>
</Component>
}
</Components>
</ConfigMaster>
');
-最后是分解/重新组合(假设可能有多个组成部分)
WITH comp AS
(
SELECT comp.value(N'@Name',N'nvarchar(max)') AS ComponentName
,comp.query(N'*') AS Children
FROM @ConfigMaster
CROSS APPLY ConfigMaster.nodes(N'/ConfigMaster/Components/Component') A(comp)
)
SELECT ComponentName AS [ComponentName/@Name]
,(
SELECT attr.value(N'@Name',N'nvarchar(max)') AS [@Name]
,attr.value(N'@Value',N'nvarchar(max)') AS [@Value]
,attr.value(N'@OptionListId',N'nvarchar(max)') AS [@OptionListId]
,CASE attr.value(N'@PrintCode',N'nvarchar(max)') WHEN 'D' THEN 'E' ELSE 'I' END AS [@PrintCode]
,attr.value(N'@DataEntryOrder',N'nvarchar(max)') AS [@DataEntryOrde]
FROM Children.nodes(N'Attributes/Attribute') A(attr)
FOR XML PATH('Attribute'),ROOT('Attributes'),TYPE
)
FROM comp
FOR XML PATH('Components'),ROOT('ConfigMaster')