Rekursive Baumstruktur in Tabelle speichern.



  • Hi, ich möchte im prinzip Texte in einer Datenbank speichern. Diese Text bzw. nächst kleineren Einheiten wie Sätze und Wörter sind aber nicht statisch sondern werden editiert. Im Prinzip habe ich also eine Baumstruktur. Als Root den Text oder nennen wir es Buch. Dieses ist eine Liste von Sätzen. Sätze wiederum beinhalten Wörter aber wiederum auch andere Sätze oder nennen wir es besser Satzfragmente. Jetzt grübel ich wie man das am besten in der Datenbank abbildet. Natürlich könnte man das Buch in seine Wörter aufgegliedert mit der Buch id und einem Index in einer BookIndices Tabelle Speichern. Wenn nun aber ein Satzfragment editiert wurde wären alle in der Db hinterlegten Bücher oder Satzfragmente falsch. Man müsste genauso alle Bücher editieren. Jetzt war meine Idee das irgendwie rekursiv über Satzfragmente zu lösen. Es gibt da ja u.A. einen "WITH RECURSIVE" SQL Befehl. Aber mir fehlt irgendwie die Idee wie ich das in einer Tabelle repräsentieren könnte so dass auch die Reihenfolge noch erhalten bleibt. Hat da jemand Tipps für?


  • Mod

    Entweder verstehe ich dich nicht (will ich nicht ausschließen, denn ich bin etwas verwirrt nach dem Lesen) oder aber sind deine vermeintlichen Probleme nicht hausgemacht durch deine Idee mit dem Baum, und keine Probleme mit der Datenhaltung? Ich verstehe auch nicht den logischen Sprung von "editierbarer Text" -> "Baumstruktur" und noch weniger zu einer rekursiven Baumstruktur. Was spricht denn gegen das bewährte Modell "Ein Text ist eine Folge von Zeichen"?



  • Also mal abgesehen davon ob das Sinn macht, die Tabellenstruktur sollte ja halbwegs einfach sein. Zumindest wenn wir mal Leerzeichen/Satzzeichen vergessen und so tun als ob das z.B. auch Wörter wären.

    Tabelle Fragment:
        Fragment_ID     integer NOT NULL
        Fragment_Text   string NULL
      PRIMARY KEY: Fragment_ID
    
    Tabelle SubFragments:
        Fragment_ID     integer NOT NULL
        Position        integer NOT NULL
        SubFragment_ID  integer NOT NULL
      PRIMARY KEY: Fragment_ID, Position
    


  • Und wie würde eine Abfrage dazu aussehen damit dies auch rekursiv aufgelöst wird?



  • Google mal nach Recursive Common Table Expressions



  • Das habe ich schon. Aber die Frage ist wie man die Daten abfragen kann so dass sich recursiv die richtige Reihenfolge ergibt. Mit der Position ist das alleine nicht möglich. Ich habe schon versucht einen Index durchzuschleifen:

    Pseudo-SQL:

    DECLARE @number INT;
    SELECT @number = 1;
    WITH RECURSIVE cte(Number, Fragment_ID, Position, SubFragment_ID)
    (
        SELECT @number AS Number, Fragment_ID, Position, SubFragment_ID WHERE ...
    UNION ALL
        SELECT parent.Number + 1, child.Fragment_ID, child.Position, child.SubFragment_ID FROM SubFragments AS child, cte AS parent WHERE child.Fragment_ID = parent.SubFragment_ID
    )
    SELECT * FROM child ORDER BY Number ASC, Position ASC;
    

    Das war aber wenig erfolgreich.



  • Wenn du das in SQL sortieren willst, dann wirst du vermutlich einen String als Sort-Key brauchen, wo du bei der Rekursion immer die Position Werte der neuen Zeilen an die der Parent-Zeile anhängst. Natürlich mit Fixbreite so dass das als String dann auch richtig sortiert wird.

    Ich denke Sortieren in der Applikation wäre einfacher.



  • Also anstatt eines INT durchzuschleifen ist die Lösung wohl einen String zu verwenden und die Ids zu verketten.

    WITH RECURSIVE cte(Number, Fragment_ID, Position, SubFragment_ID)
    (
        SELECT Fragment_ID AS Number, ...
    UNION ALL
        SELECT Number || '|' || child.Fragment_ID, ...
    )
    SELECT * FROM child ORDER BY Number;
    

    Mit dieser...

    SELECT Number || '|' || child.Fragment_ID, ...
    

    ...Syntax sind aber immer die Number = 1. Ich vermute hier wird mit einem INT gearbeitet anstatt mit einem VARCHAR.
    Wie könnte ich denn die Verwendung eines VARCHAR erzwingen. Die Ursprüngliche Number ist ja eine INT Spalte.

    Nehme ich stattdessen Concat

    SELECT CONCAT(`Number`, ':', t1.`Id`),
    

    bekomme ich immer eine

    SQL Fehler (1265): Data truncated for column 'Number' at row 1
    

    Fehlermeldung.

    Noch jemand eine Idee? Denke ich habe es jetzt fast.

    Und warum gibt der CAST hier eine Fehlermeldung:

    SELECT CAST(`Fragment_ID` AS VARCHAR(100)),
    SQL Fehler (1064): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'VARCHAR(100)),
    

    SQL ist auch so eine völlig kaputte Sprache. Suche seit Stunden nach einer Lösung den INT als String interpretieren zu können. Wie konnte sich so sein Schrott durchsetzen...



  • Den Fehler bekommst du weil MySQL vermutlich keinen VARCHAR Datentyp kennt.
    Versuch's mal mit

    LPAD(CONVERT(`Position`, CHAR), 8, '0')
    

    Davon abgesehen sieht deine CTE auch nicht sinnvoll aus. Du versuchst z.B. die Fragment_ID Spalte zu konkatenieren - dafür wäre aber die Position Spalte gedacht. Das child.Fragment_ID im rekursiven Teil verstehe ich auch nicht ganz. Wo kommt child her?



  • MySQL kennt sich also selber nicht :D. Die String-Spalten sind vom Typ VARCHAR, aber es ist wirklich so, dass VARCHAR(100) nicht erkannt wird, aber CHAR(100). Der || Operator funktioniert auch nicht obwohl überall im Internet Beispiele mit diesem zu finden sind.

    EDIT: Es funktioniert nun. Das mit der Fragment_ID war ein Denkfehler. Natürlich hätte ich die Position verketten sollen. Super, danke euch.



  • @Enumerator sagte in Rekursive Baumstruktur in Tabelle speichern.:

    MySQL kennt sich also selber nicht 😃

    Doch, schon... Siehe Dokumentation: https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast

    CAST(expr AS type [ARRAY])
    (...)
    CHAR[(N)] [charset_info]

    Produces a string with the VARCHAR data type. except that when the expression expr is empty (zero length), the result type is CHAR(0). If the optional length N is given, CHAR(N) causes the cast to use no more than N characters of the argument. No padding occurs for values shorter than N characters. If the optional length N is not given, MySQL calculates the maximum length from the expression. If the supplied or calculated length is greater than an internal threshold, the result type is TEXT. If the length is still too long, the result type is LONGTEXT.

    VARCHAR ist dagegen nicht in der Liste der typen, in die du casten kannst.



  • @Enumerator

    es gibt keine rekursive Baumstruktur. Man kann jedoch einen Baum rekursiv durchlaufen um alle Äste zu erfassen, und damit kommen wir zum Punkt: Zur Wurzel kommt man über die Parent-Node. Das heißt, auf Dein Tabellendesign bezogen, daß es ein Feld geben muß welches den Parent für den betreffenden Record ausweist. Nur der Root hat keinen Parent.
    Für einen rekursiven Durchlauf drehst Du die Parent-Relation um in eine Children-Relation. Das kann bspw. mit einer Abfrage erfolgen "zeige mir alle Rekords die mich als Parent haben" und das machst Du dann mit jedem Kindknoten, also rekursiv.

    MFG


Log in to reply