2015-05-15 2 views
10

Ich habe eine Tabelle von Werten in SQL Server 2008, aus der ich den Wert in eine XML-Spalte innerhalb einer übereinstimmenden Zeile einer anderen Tabelle einfügen möchte. Die XML-Spalte enthält möglicherweise nicht alle Tags, die zu dem Element führen, das eingefügt werden soll.Wie fügt man ein Element in die XML-Spalte ein, ohne zu wissen, ob der Baum bereits existiert?

Ich kann dies durch mehrere update/xml.modify Anweisungen erreichen, um sicherzustellen, dass die Tags vor dem Einfügen des Elements vorhanden sind, aber das scheint wirklich ineffizient und was, wenn ich ein Element 5 oder 10 Tags tief einfügen wollte?

Hier ist ein erstelltes example in SQL fiddle

Das Setup ist, dass ich habe 2 Tabellen (vereinfachte/hier aus bis zu einem verständlichen Szenario zu machen)

CREATE TABLE tableColors (id nvarchar(100), color nvarchar(100)) 
CREATE TABLE xmlTable (id nvarchar(100), xmlCol xml)` 

Ich brauche das Element <root><colors><color>tableColors.color</color></colors></root> in XMLTABLE einzufügen, wo die id stimmt überein und das Element existiert noch nicht. Das xmlCol kann viel mehr Elemente enthalten oder sogar leer sein. Das Farb-Tag ist 0 oder viele und das Farb-Tag ist 0 oder 1.

Die abschließende Anweisung, das Element an der richtigen Stelle einzufügen, ist sinnvoll, funktioniert aber nicht, wenn die übergeordneten Tags nicht bereits vorhanden sind.

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 

Also, ich brauche /root/colors existiert, um sicherzustellen, bevor Sie dieses Update-Anweisung ausgeführt wird. Bitte sagen Sie mir, dass ich etwas vermisse und ich muss nicht explizit eine Einfügung von root machen (wenn leer) und dann Farben in root einfügen.

Um weiter zu erklären, hier ist ein vor und nach dem das neue Element in/root/Farben des Einsetzens:

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> 

Auch hier ist eine vollständige example in SQL fiddle wo ich erreichen, was ich will, aber es muss eine sein besserer Weg. Was vermisse ich?

+0

Es sieht so aus, als wäre Brians Antwort so nah wie XML. d. h. mehrere if-Anweisungen innerhalb eines einzelnen Updates - ein einzelnes Update ist großartig, aber für komplexere Bäume wäre dies ein Albtraum. Ich weiß, ich sagte, ich würde keine Antwort akzeptieren, die dynamisches SQL verwendet, aber ich dachte, ich könnte Brians Antwort in eine gespeicherte Prozedur konvertieren, die dynamisches SQL verwendet, um die Wiederholung zu eliminieren und diese bis zu anderen Bäumen und Spalten zu öffnen. http://sqlfiddle.com/#!6/d8e66/1 Immer noch in der Hoffnung, jemand wird eine noch einfachere XML-Anweisung vorbringen :) –

Antwort

1

Sie können die Verschachtelung in Ihre Insert-Anweisung enthalten und tun es mit diesem nur ein Update wie:

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 
+0

Danke Brian, das entfernt zumindest alle zusätzlichen Updates! Sie haben vielleicht die beste SQL-XML-basierte Lösung, die allerdings schnell unüberschaubar wird, wenn Sie mehr als 5 Ebenen tief einfügen müssen. Die Notwendigkeit, wenn - dann - sonst überhaupt für das scheint wie ein großes Versehen von MS. Ich hatte nicht zwei Lose von wenn - dann - sonst, eine für die Beilage, die andere für die Wenn. Tolles Denken! Hier ist Ihr Code in SQL Fiddle http://sqlfiddle.com/#!6/03812/8 –

0

Es wäre besser, eine XSL-Transformation zu verwenden, um das XML zu manipulieren. Hier ist ein altes Blog (SQL Server 2005) über die Integration Transformation SQL CLR verwendet (es gibt viele andere Informationen da draußen):

http://blogs.msdn.com/b/mrorke/archive/2005/06/28/433471.aspx

Sobald Sie diese Abfrage integriert haben, könnte in etwa so aussehen:

aussehen könnte
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 

Und das Stylesheet nicht leere Instanzen XMLCOL zum handel somthing wie: dies que

<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> 
+0

Schätzen Sie die Mühe, die Sie dorthin gegangen sind, und ich bin sicher, dass eine CLR-Lösung möglich ist. Es scheint mir nur, dass das Erstellen eines XSLT und CLR dafür viel mehr Arbeit ist als nur mehrere Insert-Anweisungen. Vor allem, weil dies immer noch kein generischer Ansatz für die Zusammenführung wird (nicht dass ich darum gebeten hätte, nur dass der zusätzliche Aufwand dies auf ein anderes Niveau bringen müsste). Die zusätzlichen Dateien für die Bereitstellung, Installation und Verwaltung von externem SQL sind ein Problem für mich. –

0

Verwenden ry:

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) 
+0

Danke dafür, ich hatte darüber nachgedacht, auch replace-Anweisungen zu verwenden, dachte aber, es gäbe eine einfache XML-Abfrage-Lösung, die solche Dinge vermeiden könnte. Hier ist es in SQL Geige http://sqlfiddle.com/#!6/03812/16 Ein paar Anmerkungen zu Ihrer Antwort, es ist kurz vor der Arbeit, aber die else-Anweisung müsste auch ein Ersatz sein "", ' ...', muss vermieden werden, ein Duplikat einzufügen, und ich denke, es könnte stark vereinfacht werden. Siehe http://sqlfiddle.com/#!/03812/19 für meine Vereinfachungen. –

+0

@ScottC Ich ändere meine Abfrage in eine Aktualisierungsabfrage, jetzt aktualisiert sie Ihren xmlCol;). –

0

Diese Antwort nur baut auf Brians die einzigen die einzige zu sein scheint Derzeit ist eine auf XML-Abfragen basierende Lösung möglich. Für mich - zumindest für den Moment - reicht die einzelne Aussage, aber wenn Sie einen Baum tiefer in eine XML-Struktur einfügen müssen, würde es wirklich schmerzhaft werden und die Automatisierung macht einen gewissen Sinn.

Siehe mein Beispiel in SQL Fiddle. Hier habe ich eine gespeicherte Prozedur erstellt, die den XML-Baum und die Spalte als Parameter verwendet, um eine dynamische SQL-Abfrage zu erstellen und auszuführen. Der Vorteil besteht darin, dass Sie mit demselben SP verschiedene Bäume aus verschiedenen Spalten einfügen oder die Baumstruktur problemlos ändern können, ohne befürchten zu müssen, dass Sie eine der if-Anweisungen verpasst haben. Natürlich gelten die normalen Probleme von dynamischem SQL und sollten in Betracht gezogen werden - z. Fehlen von Ausführungsplänen usw. Sie könnten dies sogar noch allgemeiner machen, indem Sie auch die Klauseln der Tabelle name/join als Parameter verwenden.

Hoffentlich hilft dies jemand in der Zukunft, ändern Sie die Tabellennamen nach Ihren Bedürfnissen. Hier ist der Code (falls SQL Fiddle vergisst).

-- @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)[email protected] 
    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 
Verwandte Themen