Baumstruktur duplizieren

Tuesday

Benutzer
Beiträge
11
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.
 
Werbung:

ukulele

Datenbank-Guru
Beiträge
4.582
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
 

Tuesday

Benutzer
Beiträge
11
Hallo ukulele, vielen Dank für deine schnelle Antwort.

Kannst du das auto increment nicht vorrübergehend deaktivieren?
An der Zieltabelle kann ich nichts ändern. Dort ist die Struktur vorgegeben und das AI kann ich erst recht nicht abschalten.

Andernfalls würde ich sagen, da du ja möglicherweise sowieso doppelte IDs hast
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.
 

Tuesday

Benutzer
Beiträge
11
Kann es nicht sein das eine ID aus der Quelltabelle in der Zieltabelle schon existiert?

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).
 

ukulele

Datenbank-Guru
Beiträge
4.582
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.
 

akretschmer

Datenbank-Guru
Beiträge
9.423
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

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

ukulele

Datenbank-Guru
Beiträge
4.582
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...
 

Tuesday

Benutzer
Beiträge
11
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
 

ukulele

Datenbank-Guru
Beiträge
4.582
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.
 

Tuesday

Benutzer
Beiträge
11
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.
 

ukulele

Datenbank-Guru
Beiträge
4.582
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.
 
Werbung:

Tuesday

Benutzer
Beiträge
11
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 :(
 
Oben