我在SQL Server 2008中有一个值表,我希望将值插入到另一个表的匹配行中的XML列中。 xml列可能包含也可能没有所有标记通向我要插入的元素。
我可以通过多个更新/ xml.modify
语句来实现这一点,以确保在插入元素之前存在标记,但这看起来效率非常低,如果我想插入5或10个元素标签,该怎么办?
这是一个创建的example in SQL fiddle
设置是我有2个表(在这里简化/组合以制作可理解的场景)
CREATE TABLE tableColors (id nvarchar(100), color nvarchar(100))
CREATE TABLE xmlTable (id nvarchar(100), xmlCol xml)`
我需要将元素<root><colors><color>tableColors.color</color></colors></root>
插入到xmlTable中,其中id匹配且元素尚不存在。 xmlCol可以包含更多元素,甚至可以是空白。颜色标签为0或许多,颜色标签为0或1。
将元素插入正确位置的最终语句是有意义的,但如果父标记尚不存在则无效。
UPDATE xmlTable
SET xmlCol.modify(' insert <color>{sql:column("color")}</color> as first into (/root/colors)[1] ')
FROM xmlTable
INNER JOIN tableColors ON xmlTable.id = tableColors.id
WHERE xmlCol.exist('/root/colors/color[(text()[1]) = sql:column("color")]') = 0
因此,在运行此更新语句之前,我需要确保/root/colors
存在。请告诉我,我遗漏了一些东西,我不必明确地插入root(如果为空),然后将颜色插入root。
为了进一步说明,这里是将新元素插入/ root / colors:
之前和之后New Element XML before XML after
<color>blue</color> -blank- <root><colors><color>blue</color></colors></root>
<color>green</color> <root><vegitation>yes</vegitation></root> <root><vegitation>yes</vegitation><colors><color>green</color></colors></root>
<color>white</color> <root><colors><color>brown</color></colors></root> <root><colors><color>brown</color><color>white</color></colors></root>
同样,这里是一个完整的example in SQL fiddle,我实现了我想要的,但必须有一个更好的方法。我错过了什么?
答案 0 :(得分:1)
您可以在insert语句中包含嵌套结构,只需使用以下一个更新来执行此操作:
UPDATE #xmlTable
SET xmlCol.modify('
insert if (count(/root)=0) then <root><colors><color>{sql:column("color")}</color></colors></root>
else (if (count(/root/colors)=0) then <colors><color>{sql:column("color")}</color></colors>
else <color>{sql:column("color")}</color>) as first into
(if (count(/root)=0) then (/) else (if (count(/root/colors)=0) then (/root) else (/root/colors)))[1]')
FROM #xmlTable
INNER JOIN #tableColors
ON #xmlTable.id = #tableColors.id
WHERE xmlCol.exist('/root/colors/color[(text()[1])=sql:column("color")]') = 0
答案 1 :(得分:0)
最好使用XSL转换来操作XML。这是一个关于使用SQL CLR集成转换的旧博客(SQL Server 2005)(还有很多其他信息):
http://blogs.msdn.com/b/mrorke/archive/2005/06/28/433471.aspx
一旦集成了这个,您的查询就会显示如下:
update @xmlTable
set xmlCol = case
when cast(xmlCol as varchar(max)) = ''
then '<root><colors><color>' + color + '</color></colors></root>'
else {run xslt transformation, passing in @tableColors.color as XSLT parameter colorForUpdate}
end
from @xmlTable x
inner join @tableColors y on x.id = y.id
用于处理xmlCol的非空实例的样式表可能看起来像:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="colorForUpdate"/>
<xsl:template match="/root[not(colors)]">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
<colors>
<color>
<xsl:value-of select="$colorForUpdate"/>
</color>
</colors>
</xsl:copy>
</xsl:template>
<xsl:template match="/root/colors">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
<color>
<xsl:value-of select="$colorForUpdate"/>
</color>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
答案 2 :(得分:0)
使用此查询:
UPDATE xmlTable
SET xmlTable.xmlCol = (
SELECT
CAST(REPLACE(x.myXML, '&myColor;', c.color) AS XML)
FROM (
SELECT *,
CASE
WHEN xmlTable.xmlCol.exist('root') = 0 THEN
'<root><colors><color>&myColor;</color></colors></root>'
WHEN xmlTable.xmlCol.exist('root/colors') = 0 THEN
REPLACE(CONVERT(nvarchar(max), xmlTable.xmlCol), '<root>','<root><colors><color>&myColor;</color></colors>')
ELSE
CONVERT(nvarchar(max), xmlTable.xmlCol)
END AS myXML
FROM
xmlTable) x
JOIN
tableColors c
ON x.id = c.id
WHERE
xmlTable.id = x.id)
答案 3 :(得分:0)
这只是建立在Brian的答案之上,这似乎是目前唯一可用的基于XML查询的解决方案。对我来说 - 至少现在 - 单个语句就足够了,但是如果你需要在xml结构中插入一个更深的树,那么它会变得非常痛苦并且自动化会产生某种程度的意义。
请参阅我的示例in SQL Fiddle。在这里,我创建了一个存储过程,它将xml树和列作为参数来创建和执行动态SQL查询。好处是您可以使用相同的SP从不同的列插入不同的树或轻松更改树,而不必担心您错过了其中一个if语句。当然,动态SQL的正常问题是适用的,应该加以考虑 - 例如缺少执行计划等。通过将表名/连接子句作为参数,您可以轻松地使其更加通用。
希望这有助于将来改变表名以满足您的需求。这是代码(如果SQL Fiddle忘记了)。
-- @xmlTree should be of the format '/root/colors/color', column is where to get the data from in the tableFacts table
CREATE PROCEDURE sp_insertXML(@xmlTree nvarchar(max), @column sysname)
AS
BEGIN
DECLARE @insert nvarchar(max), @if nvarchar(max), @val nvarchar(max), @into nvarchar(max)
DECLARE @xmlColumn nvarchar(max)='sql:column("' + @column +'")'
DECLARE @parentTree nvarchar(max)=@xmlTree
DECLARE @endTree nvarchar(max)
DECLARE @closeTags nvarchar(max)=''
DECLARE @thisTag nvarchar(max)
WHILE (LEN(@parentTree)>0)
BEGIN
-- Set each parameter
SET @thisTag = RIGHT(@parentTree,CHARINDEX('/',REVERSE(@parentTree))-1)
SET @endTree = @thisTag + COALESCE('/' + @endTree, '')
SET @closeTags = @closeTags + '</' + @thisTag + '>'
SET @parentTree = LEFT(@parentTree,LEN(@parentTree)-LEN(@thisTag)-1)
-- Set the insert and into statements
SET @if = 'if (count(' + @parentTree + '/' + @thisTag + ')=0) then '
SET @val = '<' + REPLACE(@endTree,'/','><') + '>{' + @xmlColumn + '}' + @closeTags
SET @insert = COALESCE(@if + @val + '
else ' + @insert, @val)
SET @into = CASE WHEN @into IS NULL THEN '' ELSE 'if (count(' + @parentTree + '/' + @thisTag + ')=0) then ' END + ' (' +
CASE @parentTree WHEN '' THEN '/' ELSE @parentTree END + ')' + COALESCE(' else ' + @into,'')
END
DECLARE @sql nvarchar(max) = 'UPDATE xmlTable
SET xmlCol.modify('' insert ' + @insert + ' into (' + @into + ')[1]'')
FROM xmlTable
INNER JOIN tableFacts
ON xmlTable.id = tableFacts.id
WHERE xmlCol.exist(''' + @xmlTree + '[(text()[1])=' + @xmlColumn + ']'') = 0
AND ISNULL(' + QUOTENAME(@column) + ','''') <> '''''
EXEC sp_executesql @sql
END