Information ausblenden
Willkommen im Forum für alle Datenbanken! Registriere Dich kostenlos und diskutiere über DBs wie Mysql, MariaDB, Oracle, Sql-Server, Postgres, Access uvm

Baumstruktur duplizieren

Dieses Thema im Forum "Microsoft SQL Server" wurde erstellt von Tuesday, 27 August 2013.

  1. Tuesday

    Tuesday Benutzer

    Guten Tag,

    ich stehe vor dem Problem eine geschachtelte Baum-Struktur von einer Tabelle in eine zweite kopieren zu müssen.
    Die IDs werden per auto increment vergeben.

    Es handelt sich um die typische Baumstruktur, die von diversen Funktionen bearbeitet/ausgelesen wird:
    Code:
    ID | ParentID | Name
    1  |  null    | A
    2  |  null    | B
    3  |      1   | AA
    4  |      1   | AB
    5  |  null    | C
    6  |      2   | BA
    7  |      3   | AAA
    Diese Struktur müsste nun 1:1 in eine andere Tabelle kopiert werden. Da in der zweiten Tabelle diverse (unterschiedl.) Bäume vorkommen können, wird dort noch mit einer zusätzlichen ID gearbeitet:


    Code:
    ID | ParentID | Name | ObjectID
    1  |  null    | A    | 815
    2  |  null    | B    | 815 
    3  |      1   | AA   | 815 
    4  |      1   | AB   | 815 
    5  |  null    | C    | 815 
    6  |      2   | BA   | 815 
    7  |      3   | AAA  | 815
     
    8  |  null    | A    | 666
    9  |  null    | B    | 666  
    10 |      8   | AA   | 666  
    11 |      8   | AB   | 666  
    12 |  null    | C    | 666  
    13 |      9   | BA   | 666  
    14 |     10   | AAA  | 666  
    


    Hat jemand eine Idee, wie man den Baum kopieren kann? Ich hänge momentan an dem Problem, dass die IDs automatisch vergeben werden und ich beim Einfügen eines Knotens die ID des root- bzw. vorherigen Knotens nicht kenne.

    Vielen Dank schon einmal für eure Mühe mir bei dem Thema zu helfen.
     
  2. ukulele

    ukulele Datenbank-Guru

    Kannst du das auto increment nicht vorrübergehend deaktivieren?

    Andernfalls würde ich sagen, da du ja möglicherweise sowieso doppelte IDs hast, musst du deine alten IDs und ParentIDs in 2 neue Spalten übertragen. Dann ließe sich das ganze in einem Updatebefehl nachholen:
    Code:
    Quelltabelle
    ID | ParentID | Name
     
    Zieltabelle
    ID | ParentID | Name | ID_alt |ParentID_alt
     
    UPDATE    zieltabelle z1
    SET        z1.ParentID = (    SELECT    z2.ID
                            FROM    zieltabelle z2
                            WHERE    z2.ID_alt = z1.ParentID_alt )
    WHERE    z1.ParentID IS NULL
     
  3. Tuesday

    Tuesday Benutzer

    Hallo ukulele, vielen Dank für deine schnelle Antwort.

    An der Zieltabelle kann ich nichts ändern. Dort ist die Struktur vorgegeben und das AI kann ich erst recht nicht abschalten.

    Wieso denn doppelte IDs? In der Quelltabelle ist die Struktur hinterlegt, die dann in die Zieltabelle kopiert und an ein Object (ObjectID) rangehängt wird.
    Das Ziel ist später, dass man ein Objekt öffnet und zu diesem Objekt dann eine Verzeichnisstruktur angezeigt wird. Der Baum in der Quelltabelle ist ein Template, welches über einen Trigger als "Erstbefüllung" dem Objekt zugeordnet werden soll.
     
  4. ukulele

    ukulele Datenbank-Guru

    Kann es nicht sein das eine ID aus der Quelltabelle in der Zieltabelle schon existiert?
     
  5. Tuesday

    Tuesday Benutzer

    Theoretisch ja, praktisch nein.

    Quelltabelle ist 9 Zeilen groß. Die Zieltabelle hat ca. 500.000 Einträge.

    Sollte noch niemand vorher per Hand Einträge in der Zieltabelle eingetragen haben (Manuell ein Verzeichnis angelegt), wäre es theoretisch beim allerersten getriggerten Anlegen möglich, dass die IDs identisch sind. Spätestens beim zweiten mal passt es ja nicht mehr. (So wie in meinem ursprünglichen Bsp).
     
  6. ukulele

    ukulele Datenbank-Guru

    Ok dennoch bleibt, wenn ich das richtig verstehe, das Problem das die Datensatz ID beim Insert automatisch vergeben wird und du sie im nachhinein nicht mehr "kennst". In dem Fall würde ich versuchen, die Information, welche Datensätze welchen Vorgänger in der Quelltabelle hatten, erstmal mit zu übergeben ohne gleich die neue ID kennen zu müssen. Wenn ich die nachträglich setzen kann, das Objekt also erstmal ohne Vorgänger in die Zieltabelle schreibe, kann ich im nachhinein die ParentIDs neu erzeugen.

    Am liebsten sind mir da natürlich zusätzliche Spalten aber theoretisch kann man die ID auch mit einem Trennzeichen in den Namen setzen und später wieder raus löschen. Das alles geht aber nur, wenn dir der Trigger da nicht zwischenfunkt oder die Spalte ParentID eine NOT NULL Bedingung hat.
     
  7. akretschmer

    akretschmer Datenbank-Guru

    ich hätt ja eine Idee, wäre aber wieder pg-spezifisch ... writeable CTE.
     
  8. ukulele

    ukulele Datenbank-Guru

    Sry das kenn ich wieder nicht :)

    Natürlich könnte ich auch ne Schleife machen und nach jedem Eintrag die ID wieder auslesen und verwenden, sicher gibt es auch noch andere Wege...
     
  9. Tuesday

    Tuesday Benutzer

    Oder mit @@IDENTITY und Variablen die ID's merken und hoffen, dass niemand gleichzeitig den Trigger erneut auslöst.
     
  10. Tuesday

    Tuesday Benutzer

    Noch eine Idee:

    Wäre es evtl. Hilfreich, wenn man in der Quelltabelle zusätzlich die Tiefe mit speichert?

    Code:
    ID | ParentID | Name | Level
    1  |  null    | A    | 0
    2  |  null    | B    | 0
    3  |     1    | AA   | 1
    4  |     1    | AB   | 1
    5  |  null    | C    | 0
    6  |     2    | BA   | 1
    7  |     3    | AAA  | 2
     
  11. akretschmer

    akretschmer Datenbank-Guru


    Nö, die ist berechenbar ...
     
  12. ukulele

    ukulele Datenbank-Guru

    Code:
    CREATE TABLE tmp_alt(    ID INT IDENTITY PRIMARY KEY,
                            ParentID INT NULL,
                            Name VARCHAR(100) NULL )
     
    INSERT INTO tmp_alt(ParentID,Name) VALUES (NULL,'Ordner 1');
    INSERT INTO tmp_alt(ParentID,Name) VALUES (1,'Ordner 2');
    INSERT INTO tmp_alt(ParentID,Name) VALUES (1,'Ordner 3');
    INSERT INTO tmp_alt(ParentID,Name) VALUES (2,'Unterordner 2a')
    INSERT INTO tmp_alt(ParentID,Name) VALUES (2,'Unterordner 2b')
    INSERT INTO tmp_alt(ParentID,Name) VALUES (3,'Unterordner 3a')
    INSERT INTO tmp_alt(ParentID,Name) VALUES (3,'Unterordner 3b')
    INSERT INTO tmp_alt(ParentID,Name) VALUES (3,'Unterordner 3c')
     
    SELECT    *
    FROM    tmp_alt
     
    1    NULL    Ordner 1
    2    1    Ordner 2
    3    1    Ordner 3
    4    2    Unterordner 2a
    5    2    Unterordner 2b
    6    3    Unterordner 3a
    7    3    Unterordner 3b
    8    3    Unterordner 3c
     
    CREATE TABLE tmp_neu(    ID INT IDENTITY PRIMARY KEY,
                            ParentID INT NULL,
                            Name VARCHAR(100) NULL )
     
    INSERT INTO tmp_neu(ParentID,Name) VALUES (NULL,'Ordner 1');
    INSERT INTO tmp_neu(ParentID,Name) VALUES (1,'Ordner 2');
    INSERT INTO tmp_neu(ParentID,Name) VALUES (1,'Ordner 3');
    INSERT INTO tmp_neu(ParentID,Name) VALUES (2,'Unterordner 2a')
    INSERT INTO tmp_neu(ParentID,Name) VALUES (2,'Unterordner 2b')
    INSERT INTO tmp_neu(ParentID,Name) VALUES (3,'Unterordner 3a')
    INSERT INTO tmp_neu(ParentID,Name) VALUES (3,'Unterordner 3b')
    INSERT INTO tmp_neu(ParentID,Name) VALUES (3,'Unterordner 3c')
     
    SELECT    *
    FROM    tmp_neu
     
    1    NULL    Ordner 1
    2    1    Ordner 2
    3    1    Ordner 3
    4    2    Unterordner 2a
    5    2    Unterordner 2b
    6    3    Unterordner 3a
    7    3    Unterordner 3b
    8    3    Unterordner 3c
     
    INSERT INTO tmp_neu(Name)
    SELECT    isnull(cast(ID AS VARCHAR(10)),'0') + '$' +
            isnull(cast(ParentID AS VARCHAR(10)),'0') + '#' +
            Name
    FROM    tmp_alt
     
    SELECT    *
    FROM    tmp_neu
     
    1    NULL    Ordner 1
    2    1    Ordner 2
    3    1    Ordner 3
    4    2    Unterordner 2a
    5    2    Unterordner 2b
    6    3    Unterordner 3a
    7    3    Unterordner 3b
    8    3    Unterordner 3c
    9    NULL    1$0#Ordner 1
    10    NULL    2$1#Ordner 2
    11    NULL    3$1#Ordner 3
    12    NULL    4$2#Unterordner 2a
    13    NULL    5$2#Unterordner 2b
    14    NULL    6$3#Unterordner 3a
    15    NULL    7$3#Unterordner 3b
    16    NULL    8$3#Unterordner 3c
     
    UPDATE    tmp_neu
    SET        ParentID = (    SELECT    t2.ID--,
                                    --cast(left(t2.Name,charindex('$',t2.Name)-1) AS INT),
                                    --cast(right(left(t2.Name,charindex('#',t2.Name)-1),len(left(t2.Name,charindex('#',t2.Name)-1))-charindex('$',t2.Name)) AS INT)
                            FROM    tmp_neu t2
                            WHERE    t2.Name LIKE '%$%'
                            AND        t2.Name LIKE '%#%'
                            AND        cast(left(t2.Name,charindex('$',t2.Name)-1) AS INT) =
                                    cast(right(left(tmp_neu.Name,charindex('#',tmp_neu.Name)-1),len(left(tmp_neu.Name,charindex('#',tmp_neu.Name)-1))-charindex('$',tmp_neu.Name)) AS INT) )
    WHERE    ParentID IS NULL
    AND        Name LIKE '%$%'
    AND        Name LIKE '%#%'
     
    SELECT    *
    FROM    tmp_neu
     
    1    NULL    Ordner 1
    2    1    Ordner 2
    3    1    Ordner 3
    4    2    Unterordner 2a
    5    2    Unterordner 2b
    6    3    Unterordner 3a
    7    3    Unterordner 3b
    8    3    Unterordner 3c
    9    NULL    1$0#Ordner 1
    10    9    2$1#Ordner 2
    11    9    3$1#Ordner 3
    12    10    4$2#Unterordner 2a
    13    10    5$2#Unterordner 2b
    14    11    6$3#Unterordner 3a
    15    11    7$3#Unterordner 3b
    16    11    8$3#Unterordner 3c
     
    UPDATE    tmp_neu
    SET        Name = right(Name,len(Name)-charindex('#',Name))
    WHERE    Name LIKE '%$%'
    AND        Name LIKE '%#%'
     
    SELECT    *
    FROM    tmp_neu
     
    1    NULL    Ordner 1
    2    1    Ordner 2
    3    1    Ordner 3
    4    2    Unterordner 2a
    5    2    Unterordner 2b
    6    3    Unterordner 3a
    7    3    Unterordner 3b
    8    3    Unterordner 3c
    9    NULL    Ordner 1
    10    9    Ordner 2
    11    9    Ordner 3
    12    10    Unterordner 2a
    13    10    Unterordner 2b
    14    11    Unterordner 3a
    15    11    Unterordner 3b
    16    11    Unterordner 3c
    Das ist natürlich ein ziemliches gebastel mit dem Namen und ganz wichtig ist es schonmal Trennzeichen zu nehmen die sonst nicht vorkommen. Aber prinzipiell geht es, wenn auch wesentlich ekliger als mit zusätzlichen Spalten.
     
  13. Tuesday

    Tuesday Benutzer

    Schonmal eine ziemlich coole Lösung. Vielen Dank.

    Leider ist mir beim Ausprobieren eine etwas nervige Constraint aufgefallen:
    Es muss immer ein Root Element geben. Dieses muss die ParentID NULL und den Namen NULL haben. Kindelemente mit einem Namen MÜSSEN auch eine ParentID haben.

    Mein Bsp war also falsch. :-(


    Code:
    ID | ParentID | Name
    1  |  null    | null
    2  |     1    | A
    3  |     1    | B
    4  |     2    | AA
    5  |     2    | AB
    6  |     1    | C
    7  |     3    | BA
    8  |     4    | AAA
    Bei der ersten Einfügerunde wird ja für jede Zeile erst mal NULL als ParentID gesetzt. Das knallt leider.
     
  14. ukulele

    ukulele Datenbank-Guru

    Hab ich mir schon fast gedacht :) Musst du eventuell mit einem Platzhalter wie z.B. 999999 als ParentID arbeiten und meine Statements etwas anpassen. Auserdem musst du den ersten Eintrag, also das Root Element aus der alten Tabelle eventuell manuell übergeben und dann irgendwo in der Struktur verankern, es sei denn, es kann mehrere Root Elemente geben.
     
  15. Tuesday

    Tuesday Benutzer

    Joa. Das habe ich soweit umgebaut.
    Auch das zweite Update habe ich angepasst, da es die Fehlermeldung
    Code:
    Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
    gab.

    Nachdem das jetzt eigentlich durchlaufen sollte, gibt es nun an einer ganz anderen Stelle einen Fehler.
    Es gibt noch eine dritte Tabelle, die per Trigger (Update/Insert/Del in der Verzeichnistabelle) mit einer Menschenlesbaren (Pfad, Level, ..) Form des Baums befüllt wird.
    Der Trigger ruft wohl eine Prozedur auf, die den Baum rekursiv traversiert und sich damit den Pfad zusammenbastelt. Jedenfalls kommt diese Prozedur nicht mit der temporären Form (ParentID Platzhalter) in der Verzeichnistabelle klar und muss wegen einer Dauerschleife beendet werden.
    Das Update (Schritt: Einfügen der ParentIDs) wird dann abgebrochen.

    Leider übersteigt das meine Kenntnisse jetzt ein wenig und einsehen, geschweige denn ändern, kann ich besagte Prozeduren auch nicht ;-)


    Dieser Lösungsansatz mit den Inserts/Updates ist sehr vielversprechend, aber leider wird da zu heftig auf der eigentlichen Tabelle rumgeschrieben.

    Könnte man das evtl. mit der Nutzung von einem/mehrere Cursor + Fetch lösen?

    Bzw. wie sieht das denn mit CTEs aus? (MS SQL 2008 R2) Im Internet findet man ja einiges zu dem Thema.. Einfügen.. Anzeigen.. etc.. Aber nichts was mein Problem betrifft :(
     
Die Seite wird geladen...

Diese Seite empfehlen

  1. Diese Seite verwendet Cookies. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies.
    Information ausblenden