Alex: Datensatz ergänzen oder löschen

Hallo,

ich habe eine mySQL-Tabelle ("benutzer") in denen registrierte Benutzer meiner Webseite stehen:

Tabelle "benutzer":

  • id  + name     +
    +-----+----------+
    +  1  + olaf     +
    +  2  + ute      +
    +  4  + hans     +

Der Benutzer kann nun Optionen auswählen, die in einer weiteren Tabelle gespeichert werden.

Tabelle "optionen":

  • id  + option   +
    +-----+----------+
    +  1  + 1        +
    +  2  + 0        +
    +  4  + 1        +

Diese Tabellen sind soweit von meinem CMS vorgegeben und diese Tabellen sollen unangetastet bleiben.

Ich möchte nun in einer weiteren Tabelle die Namen (benutzer.name) alle Benutzer eintragen, welche als option (optionen.option) eine 1 (true) eingetragen haben. Hintergrund ist, dass dort Statistikdaten der Benutzer eingetragen werden sollen, die diese Option angehakt (option = 1) haben.

Den select-Befehl kriege ich soweit hin:

"SELECT benutzer.name FROM benutzer INNER JOIN optionen ON (benutzer.id = optionen.id) WHERE (optionen.option = 1)"

Die dritte Tabelle ist derzeit wie folgt aufgebaut:

Tabelle "statistik":

  • name  + statistik  +
    +-------+------------+
  • olaf  + 20         +
  • hans  + 30         +

Als eindeutige ID soll hier der Name verwendet werden, da dieser auch der Schlüssel für die Abfrage der Statistikwerte aus einer externen API ist. Grundsätzlich kann ich bei _dieser_ Tabelle aber auch Änderungen an der Struktur vornehmen, da diese unabhängig vom CMS ist und ausschließlich für die Statistikwerte genutzt wird.

An der Stelle diese Tabelle zu aktualisieren hänge ich nun fest.

Es sollen alle Benutzer, die nicht bereits vorhanden sind (Schlüssel = name) und welche die v.g. Bedingungen erfüllen in "statistik" eingefügt werden. Datensätze bereits vorhandener Benutzer sollen unangetastet bleiben. Es soll auch kein weiterer Datensatz zu diesen Nutzern eingefügt werden (1 Datensatz je Nutzer). Benutzer die in der Tabelle "statistik" vorhanden sind, aber nicht mehr über den v.g. SELECT-Befehl geliefert werden, sollen gelöscht werden.

Jetzt frage ich mich, ob ich dies mit einem Befehl umsetzen kann:

INSERT/REPLACE/UPDATE = fällt m.E. weg, weil keine Löschung der Nutzer die Option nicht mehr gewählt haben oder nicht mehr in der Benutzertabelle vorhanden sind

DELETE = macht m.E. nur i.V.m. einem weiteren Befehl Sinn. D.h. im Anschluss an INSERT/REPLACE/UPDATE die überflüssigen Datensätze löschen.

Gibt es einen SQL-Befehl, der gelieferte Datensätze aktualisiert und gleichzeitig nicht gelieferte DS löscht? Wenn nein, ist dann die Kombination INSERT/DELETE die richtige? Ist mein Ansatz überhaupt der Richtige? Wie würdet ihr die Sache angehen?

Gruß
Alex

  1. Tabelle "benutzer":

    • id  + name     +
      +-----+----------+
      +  1  + olaf     +
      +  2  + ute      +
      +  4  + hans     +

    Der Benutzer kann nun Optionen auswählen, die in einer weiteren Tabelle gespeichert werden.

    Tabelle "optionen":

    • id  + option   +
      +-----+----------+
      +  1  + 1        +
      +  2  + 0        +
      +  4  + 1        +

    Die id der Tabellen benutzer und optionen ist die identische ID der Benutzer.

  2. Tach!

    Gibt es einen SQL-Befehl, der gelieferte Datensätze aktualisiert und gleichzeitig nicht gelieferte DS löscht? Wenn nein, ist dann die Kombination INSERT/DELETE die richtige?

    Jein. Mir ist kein einfaches Statement bekannt, dass für diesen Spezialfall genommen werden kann. Aber du kannst dir eine Stored Procedure erstellen, wenn du keine zwei einzelnen Statements (gegebenenfalls zuzüglich Transaktionssteuerung) ausführen möchtest.

    INSERT mit ON DUPLICATE KEY UPDATE und danach ein DELETE wird es wohl werden müssen.

    dedlfix.

    1. Hallo dedlfix,

      INSERT mit ON DUPLICATE KEY UPDATE und danach ein DELETE wird es wohl werden müssen.

      Auf dem ersten Blick erscheint mir "ON DUPLICATE KEY UPDATE" sehr passend. Vielen Dank dafür.

      ****

      Vlt. hat ja wer noch einen Tipp, wie ich das am im Weiteren angehe. Für mich stellen sich zwei fragen.

      (1) Wenn ich das Ergebnis meiner Abfrage in $ergebnis gespeichert habe:

        
      $abfrage = SELECT benutzer.name FROM benutzer INNER JOIN optionen ON  (benutzer.id = optionen.id) WHERE (optionen.option = 1);  
      $ergebnis = mysql_query($abfrage);  
      
      

      Kann ich es von dort direkt für das Updpate nutzen? Bin bisher immer den Weg darüber gegangen, dass ich mir das $ergebnis mit mysql_fetch_assoc als Array habe liefern lassen, um es dann über eine foreach-Schleife in eine Tabelle einzufügen. Da ich die Daten hier aber nicht verändere, sollte es doch ohne den Umweg über das Array möglich sein, die Abfrage direkt für das Update zu nutzen, oder?

      (2) Ich will ja alle Datensätze außer die gelieferten löschen. Ich wüsste mir hier jetzt nur mit DELETE ... WHERE NOT ... zu helfen. Bräuchte ich dann hier spätestens wieder ein Array oder kann ich auch hier (falls es zu (1) etwas gab), direkt mit dem SQL-Ergebnis weiterarbeiten?

      1. Tach!

        (1) Wenn ich das Ergebnis meiner Abfrage in $ergebnis gespeichert habe:

        $abfrage = SELECT benutzer.name FROM benutzer INNER JOIN optionen ON  (benutzer.id = optionen.id) WHERE (optionen.option = 1);
        $ergebnis = mysql_query($abfrage);

        
        >   
        > Kann ich es von dort direkt für das Updpate nutzen?  
          
        Das ist ein Verweis auf eine bereits im Hintergrund abgefragte Ergebnismenge. Die einzelnen Ergebnisse bekommst du nur über die Fetch-Funktionen. Und du kannst dir aus den Werten nur ein neues Statement zusammenstellen. Wenn du auf mysqli umsteigst, kannst du zwar mit mysqli\_fetch\_all() ein fertiges Array bekommen, aber auch das müsstest du durchackern. Einfacher geht es mit einem INSERT ... SELECT ... ON DUPLICATE KEY UPDATE ...  
          
        
        > (2) Ich will ja alle Datensätze außer die gelieferten löschen. Ich wüsste mir hier jetzt nur mit DELETE ... WHERE NOT ... zu helfen. Bräuchte ich dann hier spätestens wieder ein Array oder kann ich auch hier (falls es zu (1) etwas gab), direkt mit dem SQL-Ergebnis weiterarbeiten?  
          
        Du kannst WHERE name NOT IN (...) nehmen. In die Klammern kommt dann ein SELECT, dass dir die Namen liefert (falls du die und nicht die IDs als Primärschlüssel verwenden willst. Warum eigentlich (nicht)?).  
          
          
        dedlfix.
        
        1. Tach nochmal :)

          Das ist ein Verweis auf eine bereits im Hintergrund abgefragte Ergebnismenge. Die einzelnen Ergebnisse bekommst du nur über die Fetch-Funktionen. Und du kannst dir aus den Werten nur ein neues Statement zusammenstellen. Wenn du auf mysqli umsteigst, kannst du zwar mit mysqli_fetch_all() ein fertiges Array bekommen, aber auch das müsstest du durchackern. Einfacher geht es mit einem INSERT ... SELECT ... ON DUPLICATE KEY UPDATE ...

          Hhhm, das ganze in einer Abfrage zu kombinieren erscheint sehr praktisch. Zumindest habe ich jetzt wieder was zum Einlesen/Recherchieren.

          Auch wenn ich dann ja spätestens beim Löschen eine zweite identische SELECT-Abfrage einbaue und hier dann am Ende eine Zwischenspeicherung in einem Array doch sauberer/performanter wäre, als ein zweites Select einzusetzen? Ich meine praktisch wird das wahrscheinlich vernachlässigbar sein, zumal wir von ca. 100 Datensätzen (Benutzern) sprechen, aber hinterfragen wollte ich das trotzdem :)

          Du kannst WHERE name NOT IN (...) nehmen. In die Klammern kommt dann ein SELECT, dass dir die Namen liefert (falls du die und nicht die IDs als Primärschlüssel verwenden willst. Warum eigentlich (nicht)?).

          An diese Kombination habe ich noch garnicht gedacht. Wenn ich das ans Laufen kriege wäre das wirklich eine großartige Vereinfachung. Vielen Dank :)

          Die ID nicht als Primärschlüssel zu nehmen, favorisiere ich deshalb, weil die Statistiktabelle quasi alle Informationen enthalten soll und somit auch Standalone verwendbar ist (wenn das CMS mal nicht mehr ist oder gegen ein anderes ausgetauscht wird). Außerdem bekomme ich die Statistikwerte aus einer API wo der name das eindeutige Zuordnungskriterium ist. Im Hinblick auf eine saubere Normalisierung werde ich diesen Ansatz aber nochmal überdenken :)

          1. Tach!

            Auch wenn ich dann ja spätestens beim Löschen eine zweite identische SELECT-Abfrage einbaue und hier dann am Ende eine Zwischenspeicherung in einem Array doch sauberer/performanter wäre, als ein zweites Select einzusetzen? Ich meine praktisch wird das wahrscheinlich vernachlässigbar sein, zumal wir von ca. 100 Datensätzen (Benutzern) sprechen, aber hinterfragen wollte ich das trotzdem :)

            Bei 100 musst du dir keine großartigen Gedanken machen. Das geht selbst ohne Indexe flott. Ansonsten aber scheint es mir sinnvoller, die Abfrage als Subselect nochmal laufen zu lassen, als erst das Ergebnis zu einem anderen Server, zumindest aber zu einem anderen Prozess zu transportieren, es dort in eine neue Query umzuformen und so wieder zurückzusenden. Ein DBMS ist darauf ausgelegt und optimiert, mit Datenmengen umzugehen. Lass es das DBMS ausführen, das ist sein Job.

            Die ID nicht als Primärschlüssel zu nehmen, favorisiere ich deshalb, weil die Statistiktabelle quasi alle Informationen enthalten soll und somit auch Standalone verwendbar ist (wenn das CMS mal nicht mehr ist oder gegen ein anderes ausgetauscht wird). Außerdem bekomme ich die Statistikwerte aus einer API wo der name das eindeutige Zuordnungskriterium ist. Im Hinblick auf eine saubere Normalisierung werde ich diesen Ansatz aber nochmal überdenken :)

            Solange die Namen eindeutig sind, ist das kein Problem, diese zu verwenden. Aber wenn sich mal der Name ändert (wenn man das kann) sind auch die Statistikdaten weg, weil der Nutzer dann neu aussieht. Im ungünstigsten Fall hat sich ein neuer Nutzer mit dem alten freigewordenen Namen angemeldet. Da musst du mal alle möglichen Szenarien ermitteln und überlegen, was dann passiert.

            Und man darf auch die Prinzipien der Normalisierung ignorieren, wenn man es begründen kann. Zum Beispiel kannst du den Namen als einfaches Datum ablegen und den Benutzer-PK auch als PK in der Statistiktabelle verwenden. Die API-Daten lassen sich dann immer noch über den Namen zuordnen (wenn die nicht auseinanderlaufen).

            dedlfix.

            1. Auch wenn ich dann ja spätestens beim Löschen eine zweite identische SELECT-Abfrage einbaue und hier dann am Ende eine Zwischenspeicherung in einem Array doch sauberer/performanter wäre, als ein zweites Select einzusetzen? Ich meine praktisch wird das wahrscheinlich vernachlässigbar sein, zumal wir von ca. 100 Datensätzen (Benutzern) sprechen, aber hinterfragen wollte ich das trotzdem :)

              Bei 100 musst du dir keine großartigen Gedanken machen. Das geht selbst ohne Indexe flott. Ansonsten aber scheint es mir sinnvoller, die Abfrage als Subselect nochmal laufen zu lassen, als erst das Ergebnis zu einem anderen Server, zumindest aber zu einem anderen Prozess zu transportieren, es dort in eine neue Query umzuformen und so wieder zurückzusenden. Ein DBMS ist darauf ausgelegt und optimiert, mit Datenmengen umzugehen. Lass es das DBMS ausführen, das ist sein Job.

              Ok, vielen Dank, dass du das nochmal so ausgeführt hast.

              Die ID nicht als Primärschlüssel zu nehmen, favorisiere ich deshalb, weil die Statistiktabelle quasi alle Informationen enthalten soll und somit auch Standalone verwendbar ist (wenn das CMS mal nicht mehr ist oder gegen ein anderes ausgetauscht wird). Außerdem bekomme ich die Statistikwerte aus einer API wo der name das eindeutige Zuordnungskriterium ist. Im Hinblick auf eine saubere Normalisierung werde ich diesen Ansatz aber nochmal überdenken :)

              Solange die Namen eindeutig sind, ist das kein Problem, diese zu verwenden. Aber wenn sich mal der Name ändert (wenn man das kann) sind auch die Statistikdaten weg, weil der Nutzer dann neu aussieht. Im ungünstigsten Fall hat sich ein neuer Nutzer mit dem alten freigewordenen Namen angemeldet. Da musst du mal alle möglichen Szenarien ermitteln und überlegen, was dann passiert.

              Genau so ist es gewünscht. Ein Nutzer selbst kann zwar seinen Nutzernamen nicht ändern, sehr wohl aber die Admins. Und in diesem (Ausnahme-)Fall soll dann tatsächlich die Statistik zu dem bisherigen Namen nicht auf den neuen Namen übertragen werden. Wenn sich ein neuer Nutzer mit dem freigewordenen Namen anmeldet ist dies durchaus möglich, aber auch unproblematisch. Ich führe das jetzt mal nicht weiter aus - es sei denn, ich habe jetzt die Neugier von jmd. geweckt, um was für eine Statistik es hier geht (davon gehe ich jetzt aber erstmal nicht aus).

              Und man darf auch die Prinzipien der Normalisierung ignorieren, wenn man es begründen kann. Zum Beispiel kannst du den Namen als einfaches Datum ablegen und den Benutzer-PK auch als PK in der Statistiktabelle verwenden. Die API-Daten lassen sich dann immer noch über den Namen zuordnen (wenn die nicht auseinanderlaufen).

              Ehrlich gesagt, habe ich die Normalisierung hier erstmal der Funktion untergeordnet. Ich werde das aber nun wirklich (ganz ehrlich) auch nochmal mit in die Umsetzung mit einbeziehen. Ich erkenne hier auch durchaus Vorteile.

              Ich denke, ich bin nun auf einem guten Weg bzw. habe schonmal einen guten Plan in der Hand mit dem ich mich jetzt auf dem Weg machen kann*. Vielen lieben Dank dafür dedlfix.

              FG
              Alex

              *sollte ich mich unterwegs doch nochmal verlaufen, werde ich mich sicher nochmal melden :)

  3. Hello Alex,

    könntest Du die Aufgabe bitte nochmal im Klartext ohne "Statements" fomulieren?

    Ich habe das bisher so verstanden:

    Ein User versucht Daten einzugeben.
    Diesen User kann es schon geben -> Spezialbeahndlung 2)
    oder diesen User gibt es bidher nicht -> Spezialbehandlung 1)

    1. Neuanlage eines Users
    2. Update der Daten eines bestehenden Users

    Das Ganze soll im konkurrierenden Umfeld stattfinden.

    Habe ich das jetzt falsch verstanden?

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://restaurant-zur-kleinen-kapelle.de
    1. Hello Tom,

      könntest Du die Aufgabe bitte nochmal im Klartext ohne "Statements" fomulieren?

      Sehr gerne :)

      Ich habe das bisher so verstanden:

      Ein User versucht Daten einzugeben.
      Diesen User kann es schon geben -> Spezialbeahndlung 2)
      oder diesen User gibt es bidher nicht -> Spezialbehandlung 1)

      Richtig. Konkretisiert:

      Ein neuer Benutzer kann sich registrieren (neuer Datensatz in Tabellen "benutzer" und "optionen"*) oder ein bestehender User seinen Account löschen (Datensatz in Tabellen "benutzer" und "optionen" gelöscht**) oder seinen Optionen ändern (Datensatz in Tabelle "optionen" anpassen***). Admins können zusätzlich den Benutzernamen "name" in der Tabelle "benutzer" ändern****. Alle diese Optionen liefert mein CMS.

      Alle diese möglichen Änderungen sollen nun auch Einfluss auf meine (nicht zum CMS gehörende) Tabelle "statistik" haben.

      * = der Benutzer soll auch in "statistik" angelegt werden
      ** = der DS des Benutzers soll auch in "optionen" gelöscht werden
      *** = der DS des Benutzers soll in "statistik" gelöscht werden, wenn er die Option von 1 zu 0 ändert bzw. dort angelegt werden, wenn er die Option von 0 zu 1 ändert
      ***** = siehe *; der bisherige DS kann dann gelöscht werden (nachrangig)

      All dies soll nicht live geschehen, sondern ich wollte dies über einen Cronjob stündlich aktualisieren.

      Ich hoffe so ist es etwas klarer!?

      1. Hello,

        Alle diese möglichen Änderungen sollen nun auch Einfluss auf meine (nicht zum CMS gehörende) Tabelle "statistik" haben.

        Dann solltest Du die Aktionen in der Datenbank der Reihe nach abfragen und zwar mit der höchstwertigsten Aktion als erster.

        Wenn update mit den erforderlichen Credintial möglich, dann Update...

        Sonst biete Neuantrag an, unter der Bedinung, dass auch jetzt noch kein Update möglich ist, ...

        Du musst also zu jedem Änserungszeitpunkt der Datenbank alle Sicherheitslücken abfragen, denn im Web zählt jede Anfrage einzeln!. Da gibt es "Erinnerung" nur die gemeinsame Datenbasis!

        Liebe Grüße aus dem schönen Oberharz

        Tom vom Berg

        --
         ☻_
        /▌
        / \ Nur selber lernen macht schlau
        http://restaurant-zur-kleinen-kapelle.de
        1. Hallo nochmal und vielen Dank für deine Antwort.

          Alle diese möglichen Änderungen sollen nun auch Einfluss auf meine (nicht zum CMS gehörende) Tabelle "statistik" haben.

          Dann solltest Du die Aktionen in der Datenbank der Reihe nach abfragen und zwar mit der höchstwertigsten Aktion als erster.

          Naja, jetzt mal "nichttechnisch" gesprochen. Ich will ein Extrakt aus zwei Tabellen mit einer anderen Tabelle abgleichen. Da dachte ich es wäre jetzt das einfachste mich mit Bleistift und Radiergummi dranzusetzen und die beiden Listen in einem Rutsch abzugleichen anstatt die Tabelle zweimal mit Bleistift und einmal mit Radiergummi durchzugehen. Dabei geht es mir weniger darum, ob das ein oder andere performanter ist (weil es sicher bei der Datenmenge kaum messbar ist), sondern vielmehr darum, den Code übersichtlich zu halten und Redundanz möglichst zu verhindern. Und Last but not least auch noch was zu lernen :-)

          Du musst also zu jedem Änserungszeitpunkt der Datenbank alle Sicherheitslücken abfragen, denn im Web zählt jede Anfrage einzeln!. Da gibt es "Erinnerung" nur die gemeinsame Datenbasis!

          Ich nehme das jetzt mal als allgemeinen Hinweis mit. Bezogen auf das hiesige Beispiel halte ich das jedoch für unproblematisch. Wenn jmd. beispielsweise gerade einen Haken bei den Optionen setzt und dann doch nicht auftaucht, weil das Update schon gelaufen ist, muss dieser halt bis zum nächsten Update warten. Oder wenn jmd. upgedatet wird und anschließend den Haken rausnimmt, wird er halt erst im nächsten Durchlauf gelöscht. Wenn das alles gerade in den Sekundenbruchteilen der Abfrage geschieht ist das doch - im Ergebnis - nicht anders zu werten, als wenn das in der Zeit zwischen zwei Abfragen/Updates geschieht.

  4. Vielen Dank mir wurde geholfen.

    Ich arbeite im Script nun das Insert und Delete nacheinander ab und so konnte ich das gewünschte Ergebnis erzielen.

    Der Thread darf aus meiner Sicht archiviert werden :-)

    1. Tach!

      Ich arbeite im Script nun das Insert und Delete nacheinander ab und so konnte ich das gewünschte Ergebnis erzielen.
      Der Thread darf aus meiner Sicht archiviert werden :-)

      Das macht er von selbst, wenn man ihn lang genug ruhen lässt. Und falls du ein Folgeproblem innerhalb der nächsten Tage hast, kannst du es hier einfügen, dann geht der Zusammenhang nicht verloren.

      dedlfix.