BloodySword: Rekursives Löschen von Foren inkl. Threads und Beiträgen

Hallo, ich habe folgendes Problem:

Ich habe gerade eine Denkblokade und weiß nicht, wie ich in PHP einen rekursiven Aufruf zum Löschen eines Forums inklusive Unterforen, Threads und Beiträgen hinbekomme.

Ungerne möchte ich die komplette Tabellenstruktur hier posten, aus Sicherheitsgründen. Allerdings biete ich euch die nötigen Informationen wie folgt an:

In der Datenbank besteht das Forum nur aus einer einzigen Tabelle. Jeder Eintrag hat eine Differenzierung zwischen den Typen: 0=Forum; 1=Thread; 2=Post; 3=Trenner. Jeder Eintrag hat auch eine 'parent_id', mit der man die Einträge in einander verschachteln kann, sagen wir mal unterordnen kann.

Stark vereinfacht hier mal eine Struktur eines Forums:

id  parent  type  title
1   0       0     PHP
2   1       0     Fortgeschritten
3   1       0     Anfänger
4   2       1     Datenbank absichern. Wie?
5   4       2     Gutes Passwort ist das A und O
6   3       1     Sessions

Wie muss ich die Abfragen generieren, wenn ich jetzt das komplette PHP-Forum mit dem gesamten Inhalt löschen möchte?

Ich benutzte msqli auf OO-Basis.

Ich hoffe ihr könnt mir helfen. Grüße, BS

  1. Hi!

    Ich habe gerade eine Denkblokade und weiß nicht, wie ich in PHP einen rekursiven Aufruf zum Löschen eines Forums inklusive Unterforen, Threads und Beiträgen hinbekomme.

    Für solche Aufgaben wäre eine Strukturierung nach dem Nested-Sets-Prinzip effizienter gewesen. Aber mal zurückgefragt: Wie fragst du im täglichen Betrieb die Daten ab, dass du sie zusammenhängend gemäß ihrer Parent-Beziehung darstellen kannst?

    Wie muss ich die Abfragen generieren, wenn ich jetzt das komplette PHP-Forum mit dem gesamten Inhalt löschen möchte?

    Man nehme vom zu löschenden Objekt die ID und frage alle Datensätze ab, die diese ID als Parent haben. Von den Fundstücken mache man wieder das selbe, bis jeweils nichts mehr gefunden wird. Am Ende hast du alle IDs, die zu löschen sind.

    Wenn der Datenbestand nicht allzu groß ist, kannst du die Daten in einem Rutsch abfragen und die Auswertung auf dem Client machen. Ansonsten eben viele Statements absetzen.

    Lo!

    1. Von dem Nested-Prinzip verstehe ich nichts. Ich hatte mal bei phpMyAdmin relationen gebildet, die keine Funktion hatten. Musste sowieso alles programmatisch durcharbeiten.

      Abgefragt wird das so:

      Der Benutzer öffnet die Maske, sagen wir ID 0. Das bedeutet, dass dort alle vorhandenen Master-Foren, so nenne ich sie mal gelistet sind. Klickt der Benutzer ein Forum an, so wird diese ID aufgerufen, und die dort einsortierten Einträge gelistet. Foren, Trenner und Threads werden dabei klar differenziert. Wenn der Benutzer nun ein Thread betritt, so wird dort dieser in einer neuen Masge angezeigt und die zugehörigen Antworten darunter. Ganz simpel eben. Natürlich wird auch sowas wie letzter Post usw. angezeigt.

      Wie würde das als Struktugramm oder als vereinfachte Funktion aussehen. Etwa so?

      PSEUDO-CODE

      Funktion DatensätzeLöschen($forum) {
        Alle Einträge abfragen, die diesem Forum untergeordnet sind, wenn es keine gibt, abbrechen.
        Für jedes dieser Einträge die Funktion selbst wieder aufrufen, mit dieser ID
        Anschließend die DELETE-Abfrage ausführen
      }

      Richtig so??

      1. Hi!

        Von dem Nested-Prinzip verstehe ich nichts.

        Nested Sets. Das ist ein ziemlich gutes System, um effizient eine Menge Fragen zu baumstrukturierten Daten beantworten zu können, die in einem flachen System wie einer Datenbank-Tabelle abgelegt werden sollen.

        Abgefragt wird das so:
        Der Benutzer öffnet die Maske, sagen wir ID 0. Das bedeutet, dass dort alle vorhandenen Master-Foren, so nenne ich sie mal gelistet sind. Klickt der Benutzer ein Forum an, so wird diese ID aufgerufen, und die dort einsortierten Einträge gelistet. Foren, Trenner und Threads werden dabei klar differenziert. Wenn der Benutzer nun ein Thread betritt, so wird dort dieser in einer neuen Masge angezeigt und die zugehörigen Antworten darunter. Ganz simpel eben. Natürlich wird auch sowas wie letzter Post usw. angezeigt.

        Mit anderen Worten, du hast quasi nie mehr als zwei Ebenen in einem Request zu berücksichtigen. Die Antworten zu einem Thread sind auch nicht so schön baumstrukturell angeordnet wie hier sondern hängen flach am Ausgangsposting. Da kommt man auch ohne Nested Sets noch einigermaßen gut zurecht, wenn man nicht plötzlich doch über mehr als zwei Ebenen Daten braucht.

        Wie würde das als Struktugramm oder als vereinfachte Funktion aussehen. Etwa so?

        Das las sich erst einmal so, als ob das so richtig wäre.

        Lo!

        1. http://www.klempert.de/nested_sets/artikel/#kap0

          Interessant!

          Bleibt nur zu wissen, wie ich sowas mit SMARTY hinterher auf der Seite darstellen kann?!

    2. Moin!

      Wenn der Datenbestand nicht allzu groß ist, kannst du die Daten in einem Rutsch abfragen und die Auswertung auf dem Client machen. Ansonsten eben viele Statements absetzen.

      Oh weia!

      Aus Performancegründen würde ich exakt umgekehrt argumentieren. Wenn der Datenbestand klein ist, dann tut ein "SELECT ... WHERE parentId = ..." + "DELETE ... WHERE id =..." noch nicht sehr weh.

      Aber es wird für die Datenbank nahezu unmöglich, wenn man tausend (oder mehr) Postings hat, und diese mit tausend SELECT und tausend DELETE zu löschen versucht. Gerade in so einem Fall ist es im Prinzip Pflicht, mit so wenig Querys wie möglich den maximalen Effekt zu erzielen. Im Idealfall also mit nur einem einzigen SELECT (die Eltern-Kind-Beziehung bastelt man sich dann im Speicher zusammen) und mit nur einem einzigen "DELETE ... WHERE id IN (1,2,3,...)".

      Mehr als ein DELETE nur dann, wenn die zu erwartende Liste an IDs so groß wird, dass MySQL an die konfigurierte Grenze der maximalen Querygröße gelangt.

      - Sven Rautenberg

      1. Heiliger Bimbam. Also doch lieber Nested Sets, oder? Ich müsste auch mit deinem Tipp viel zu viel umstricken, da kann ich gleich alles auf Nested Sets umbauen...

        Würdet ihr mir dabei assestieren?

        Meine Eingaben hier entsprechen vom Prinzip her dem, was ich in echt im Code schreibe, allerdings nur etwas abgewandelt, eben aus Angst vor SQL-Attacken.

        $db->real_escape_string() reicht nicht alleine aus...

        1. Hi!

          Heiliger Bimbam. Also doch lieber Nested Sets, oder? Ich müsste auch mit deinem Tipp viel zu viel umstricken, da kann ich gleich alles auf Nested Sets umbauen...
          Würdet ihr mir dabei assestieren?

          Ja, natürlich, nach bestem Wissen und Gewissen. Aber Achtung! Nested Sets sind unschlagbar was das Abfragen anbelangt (zumindest kenne ich kein besseres System), aber dafür hast du einen erhöhten Aufwand beim Einpflegen. Ich nehme an, du hast dir das Prinzip schon angesehen und als wichtigstes Merkmal die Rechts-Links-Werte gesehen. Wenn ich es mir recht überlege dürften Lücken kein Problem darstellen, aber die vorhandenen Werte müssen aufsteigend nummeriert sein. Das heißt, dass du beim Einfügen stets die R-L-Werte der "nachfolgenden" Datensätze anpassen musst. Das ist an sich kein Problem, erfordert aber ein bis zwei zusätzliche Statements und außerdem noch zwei für Table Locking inklusive Freigabe. Um das einigermaßen effizient handhaben zu können, kann und sollte man sich für jede Aufgabenstellung eine eigene Funktion schreiben.

          Bevor du dich entscheidest, den Nested-Sets-Weg zu gehen, solltest du dir überlegen, ob sich der Aufwand lohnt. Mir scheint, dass das Löschen ganzer Foren vielleicht eher selten vorkommen wird. Wenn das so ist, wäre es vielleicht nicht unbedingt sinnvoll für diesen einen Anwendungsfall den Komfort zu haben, dafür aber generell Mehraufwand zu betreiben. Zumal deine Struktur immer noch recht flach ist und nicht so viele Verzweigungen auftreten können wie beispielsweise im hiesigen Forum.

          Meine Eingaben hier entsprechen vom Prinzip her dem, was ich in echt im Code schreibe, allerdings nur etwas abgewandelt, eben aus Angst vor SQL-Attacken.

          Davor brauchst du eigentlich keine Angst zu haben, wenn du das Prinzip Kontextwechsel verstanden hast. Lücken entstehen nur, wenn man nicht beachtet, dass einige Zeichen in bestimmten Kontexten eine Sonderbedeutung haben und deswegen anders notiert werden müssen (Maskieren/Escapen).

          $db->real_escape_string() reicht nicht alleine aus...

          In Richtung SQL-Statement schon. Für andere Kontexte gibt es andere Regeln und Verfahren, aber die solltest du sinnigerweise erst dann anwenden, wenn die Daten diesen Weg gehen, nicht schon irgendwann früher.

          Lo!

          1. Hallo. Höchstens werden mal Posts gelöscht, das kann öfter vorkommen, wenn User ihre eigenen wieder löschen. Aber das bezieht sich nur auf einen einzigen Eintrag, also nur ein DELETE-Befehl.

            Ganze Foren werden nur in besonderen Fällen von Admins gelöscht. Also sollte das die Nested Sets überflüssig machen.

            Eine Baumansicht ist durch Platzgründen nicht möglich; Die Seite wird in einem zentrierten DIV dargestellt, welches eine feste breite hat. Eingeplanter Platz für Werbung und dynamische Inhalte bieten nicht genügend Platz um ganze Threads als Nested-View darzustellen. Also spricht auch das gegen die Nested Sets.

            Eine kleine, einfache Frage am Rande:

            Ich verwende als Template-System die aktuelle Version von SMARTY.

            Wie stellt man komplette Nested-Sets mit Smarty dar? Also mit foreach ist das wohl nicht möglich, ich kann ja nicht unendlich viele davon ineinander verschachteln. Wie man in SMARTY rekursionen durchführt, ist mir auch noch nicht ganz klar. Aber es gibt bei Smarty eine richtig geile Plugin-Schnittstelle. Vielleicht gibts dafür Plugins, die ein Array mit mehreren Ebenen mal eben mit ein vordefiniertes Schema in HTML umwandelt?

            Grüße, BloodySword

            1. Hi!

              Ganze Foren werden nur in besonderen Fällen von Admins gelöscht. Also sollte das die Nested Sets überflüssig machen.

              Sagen wir so: Das Aufwand-Nutzen-Verhältnis steht in deinem Fall insgesamt gesehen ungünstig zueinander.

              Wie stellt man komplette Nested-Sets mit Smarty dar? Also mit foreach ist das wohl nicht möglich, ich kann ja nicht unendlich viele davon ineinander verschachteln. Wie man in SMARTY rekursionen durchführt, ist mir auch noch nicht ganz klar. Aber es gibt bei Smarty eine richtig geile Plugin-Schnittstelle.

              Smarty kenne ich nicht, aber Rekursion wäre die einzig sinnvolle Lösung, die mir bei Baumstrukturen unbekannter Tiefe einfällt. Gibt es nicht bei Smarty die Möglichkeit, eine (benutzerdefinierte) Funktion anzugeben? Das wäre möglicherweise einfacher als ein Plugins zu schreiben.

              Vielleicht gibts dafür Plugins, die ein Array mit mehreren Ebenen mal eben mit ein vordefiniertes Schema in HTML umwandelt?

              Kann auch gut sein. Es sollte doch garantiert andere Anwender geben, die Baumstrukturen und Smarty schon mal miteinader verheiraten wollten und dafür was entwickelt haben.

              Lo!

      2. Hi!

        Wenn der Datenbestand nicht allzu groß ist, kannst du die Daten in einem Rutsch abfragen und die Auswertung auf dem Client machen. Ansonsten eben viele Statements absetzen.

        Aus Performancegründen würde ich exakt umgekehrt argumentieren. Wenn der Datenbestand klein ist, dann tut ein "SELECT ... WHERE parentId = ..." + "DELETE ... WHERE id =..." noch nicht sehr weh.

        Aber es wird für die Datenbank nahezu unmöglich, wenn man tausend (oder mehr) Postings hat, und diese mit tausend SELECT und tausend DELETE zu löschen versucht. Gerade in so einem Fall ist es im Prinzip Pflicht, mit so wenig Querys wie möglich den maximalen Effekt zu erzielen. Im Idealfall also mit nur einem einzigen SELECT (die Eltern-Kind-Beziehung bastelt man sich dann im Speicher zusammen) und mit nur einem einzigen "DELETE ... WHERE id IN (1,2,3,...)".

        Ja, deine Argumente sind nicht von der Hand zu weisen. Der Idealfall ist wohl bei vielen wie bei wenig Daten ein SELECT und ein DELETE. Meine Überlegungen gingen eher davon aus, dass bei einer großen Menge der zur Verfügung gestellte Speicher nicht ausreichen könnte, um sämtliche Datensätze darin abzulegen und zu durchsuchen, inklusive Ablage der Zwischenergebnisse. Dazu müsste aber die Datenmenge schon recht enorm und/oder der Speicher sehr klein sein, denn ID plus Parent-ID sind im Prinzip grad mal zwei Integerwerte. In dem Fall stößt man mit vielen kleinen Schritten nicht an die Speichergrenze, aber an die der Zumutbarkeit. Die Frage dürfte jedoch auch bei ausreichend Speicher sein, was länger dauert: PHP einem großen Datenhaufen beackern zu lassen oder die Aufgabe dem DBMS zu übergeben und sich dafür eine Stored Procedure zu schreiben.

        Lo!

  2. Wenn es nur aus einer einzigen Tabelle besteht und du das komplette Forum löschen möchtest, wozu eine Rekursion. Meine SQL-Kenntnisse sind leicht angestaubt, aber warum nicht einfach 'delete * from <table>' ?

    1. Wenn es nur aus einer einzigen Tabelle besteht und du das komplette Forum löschen möchtest, wozu eine Rekursion. Meine SQL-Kenntnisse sind leicht angestaubt, aber warum nicht einfach 'delete * from <table>' ?

      Ich muss doch auch alle untergeordneten Einträge löschen, da ich sonst Datenmüll übrig lasse. Es würden dann ja alle untergeordneten Einträge bleiben, die einer ungültigen ID zugeordnet sind.

      1. Wahrscheinlich denk' ich in eine völlig verkehrte Richtung oder bin einfach schlecht informiert ;).
        Vllt. hilft dir ja so 'ne Art allgemeines Rekursionschema in Pseudocode:

        function delete ()
        {
          while (this has sub)
            delete (sub);
          delete this;
          return;
        }

        Hab gerade gesehen das du selbst schon eins im andern Strang gemacht hast - das sollte so funktionieren.

        1. Vllt. hilft dir ja so 'ne Art allgemeines Rekursionschema in Pseudocode:

          function delete ()
          {
            while (this has sub)
              delete (sub);
            delete this;
            return;
          }

          Genau daran habe ich auch gedacht, nur so ganz verstehe ich deine Funktion nicht, aber du meinst wohl dasselbe. Ich weiß nur nicht was du mit sub meinst. Subroutine oder Subject?

          1. Eigentlich meine ich damit einfach nur 'etwas' das in der Hirarchie eine Ebene unter dem aktuell von der Funktion bearbeiteten Element liegt.

  3. Hi,

    Ich habe gerade eine Denkblokade und weiß nicht, wie ich in PHP einen rekursiven Aufruf zum Löschen eines Forums inklusive Unterforen, Threads und Beiträgen hinbekomme.
    In der Datenbank besteht das Forum nur aus einer einzigen Tabelle. Jeder Eintrag hat eine Differenzierung zwischen den Typen: 0=Forum; 1=Thread; 2=Post; 3=Trenner. Jeder Eintrag hat auch eine 'parent_id', mit der man die Einträge in einander verschachteln kann, sagen wir mal unterordnen kann.

    Und da liegt m.E. das Problem.

    Ich würde das Datenmodell aufbohren und bei jedem Eintrag diese Informationen mitführen:

    id forumid unterforumid threadid parentid

    Das ist zwar redundant, da sich die Information rein über die parentid auch schon ergibt, macht aber den Zugriff wesentlich einfacher, da z.B. das Löschen eines kompletten z.B.Unterforums einfach ist:
    DELETE FROM yourtable WHERE unterforumid = 4711
    Ein Thread ist komplett greifbar über WHERE threadid = 42
    Ohne Rumhampeleien mit der parentid - die ist dann nur noch dafür zuständig, die Struktur eines Threads zu erzeugen.

    Bei einem Forum-Eintrag sind dann halt unterforumid und threadid null, bei einem unterforum ist threadid null.

    Btw, was ist ein "Trenner", daß der extra in der Datenbank gespeichert wird?

    cu,
    Andreas

    --
    Warum nennt sich Andreas hier MudGuard?
    O o ostern ...
    Fachfragen per Mail sind frech, werden ignoriert. Das Forum existiert.
    1. Mhhh... Vielleicht doch lieber Nested Sets?

      Würdet ihr mich dabei unterstützen? Forum fast fertig, und jetzt das Problem mit der Performance beim Löschen... :/

      Btw, was ist ein "Trenner", daß der extra in der Datenbank gespeichert wird?

      Ein Trenner ist in dem Fall nicht anklickbar, sorgt aber für eine klare Abtrennung zwischen mehreren Foren, eben wie ein Sparator in einer Toolbar.

      Der Trenner kann jedoch eine Beschriftung enthalten, ist aber nicht klickbar.