KraKi: mysql_affected_rows - erfolgreich-sinnloses UPDATE zählt nicht.

Hi,

ich habe gerade ein wenig in meiner Datenbank herumgedocktert und bin nun an einen "Punkt" wo ich folgendes tue:

Wert aus Formular holen
Werte in UPDATE-Klausel eintragen und ausführen
-wenn erfolgreich... ok; wenn nicht:
Werte in INSERT-Klausel eintragen und ausführen
-wenn erfolgreich... ok; wenn nicht: Fehler.

Nun zum Problem, es ist in der Anwendung ungemein häufig der Fall, dass nichts geändert wird an den Daten, die mittels UPDATE aktualisiert werden, also die aktualisierten Daten waren schon aktuell.

In der PHP-Doku (php.net) steht dazu:

Anmerkung:  Benutzen Sie UPDATE wird MySQL keine Spalten aktualisieren,
bei denen der neue dem alten Wert entpspricht. Das kann dazu führen,
dass mysql_affected_rows()  nicht die tatsächliche Anzahl der
betroffenen Zeilen liefert, sondern nur die, die wörtlich durch die
Anfrage betroffen sind.

Ergo erhalte ich völlig zu Recht aus mysql_affected_rows() eine null, wenn die Anfrage zwar gültig war und der Datensatz auch gefunden wurde, aber es nichts zu aktualisieren gab. Das Blöde ist nur, _dann_ will ich ja kein INSERT machen, sondern die Zeile unberührt lassen (oder meinetwegen mit den neuen-alten Daten überschreiben).

Die Frage ist also, wie stelle ich am geschicktesten fest, ob das UPDATE deshalb erfolglos war, weil die Daten gleich geblieben sind, oder weil es den Datensatz einfach nicht gibt.

Hier noch der Code (gekürzt):

  
<?php  
 if (isset($_POST['input'])) {  
  $html = $html."\n\n\n\n\n<!--\n\n";  
  $input = $_POST['input'];  
  escape_mysql($input); //eigene Funktion, die den ganzen array mysql-escaped.  
  $input['id'] = (int)$input['id'];  
  foreach ($input as $key => $value) {  
   if ($key != 'id') {  
    if (!isset($value['ignore'])) {  
     // (...) ein paar Änderungen/Prüfungen der $input-Werte auf Plausibilität etc.  
     $sql = "UPDATE `".$Tabelle."` SET (...) WHERE `id` = '".$key."' LIMIT 1 ;";  
     $html = $html."\n$sql";  
     if (mysql_query($sql)) { if (mysql_affected_rows() > 0) { $Update_erfolg = TRUE; } } else { $Update_erfolg = FALSE; }  
     if ($Update_erfolg) {  
      $html = $html."\nUPDATE ERFOLG!!!\n\n\n";  
     } else {  
      $html = $html."\nUPDATE war nix:\n".mysql_error();  
      $sql = "INSERT INTO `".$Tabelle."` ( (...) ) VALUES ( (...) ) ; ";  
      $html = $html."\n$sql";  
      if (mysql_query($sql)) { if (mysql_affected_rows() > 0) { $Insert_erfolg = TRUE; } } else { $Insert_erfolg = FALSE; }  
      if ($Insert_erfolg == TRUE) {  
       $html = $html."\nINSERT erfolgt\n\n\n";  
      } else {  
       $html = $html."\nINSERT missglückt \n".mysql_error()."\n\n\n\n";  
      }  
     }  
    }  
   }  
  }  
  $html = $html."\n\n\n\n\n-->\n\n";  
 }  
 echo ($html);  
?>  

  1. hi,

    Ergo erhalte ich völlig zu Recht aus mysql_affected_rows() eine null, wenn die Anfrage zwar gültig war und der Datensatz auch gefunden wurde, aber es nichts zu aktualisieren gab. Das Blöde ist nur, _dann_ will ich ja kein INSERT machen, sondern die Zeile unberührt lassen (oder meinetwegen mit den neuen-alten Daten überschreiben).

    Also andersherum betrachtet, willst du nur dann ein Insert machen, wenn es keinen "passenden" Datensatz zum Updaten gibt.

    Die Frage ist also, wie stelle ich am geschicktesten fest, ob das UPDATE deshalb erfolglos war, weil die Daten gleich geblieben sind, oder weil es den Datensatz einfach nicht gibt.

    Am besten sagst du dir einfach, "das ist mir völlig Wurscht, interessiert mich nicht - soll sich die DB doch selbst drum kümmern."

    gruß,
    wahsaga

    --
    /voodoo.css:
    #GeorgeWBush { position:absolute; bottom:-6ft; }
    1. Also andersherum betrachtet, willst du nur dann ein Insert machen, wenn es keinen "passenden" Datensatz zum Updaten gibt.

      Ganz genau!

      Am besten sagst du dir einfach, "das ist mir völlig Wurscht, interessiert mich nicht - soll sich die DB doch selbst drum kümmern."

      Ich bin schon ganz begeistert von der Funktion (?), allerdings geht mir jetzt (wenn ich das richtig verstanden habe) mein auto_increasement flöten. Also wenn die id nicht existiert wird sie neu angelegt. Das ist aber nicht gewünscht, ich möchte dass die id (und der ganze Datensatz) überschrieben werden, wenn er existiert und wenn nicht, dass dann ein neuer erstellt wird allerdings mit einer neuen (dem auto_increasement folgenden) id.

      Ich wüsste natürlich auch noch Möglichkeiten, so könnte ich z.B. die alten Werte in dem Ausgangsformular zusätzlich nocheinmal "hidden" mitschicken (oder einen Hash davon) und so in php noch feststellen, ob es verändert worden ist, aber das Formular ist schon relativ groß und ich würde es ungern noch aufblasen (wobei das mit einem Hash ja noch ginge).
      Oder aber ich führe erst noch ein SELECT aus und prüfe so ob der Datensatz existiert, aber das wäre ja ein zusätzlicher Query und wie man im Code sieht ist das eine foreach-Anweisung, es würden alse gegebenenfalls SEHR viel mehr Queries.
      Daher bin ich mit diesen Lösungen nicht soo zufrieden.

    2. Moin!

      Am besten sagst du dir einfach, "das ist mir völlig Wurscht, interessiert mich nicht - soll sich die DB doch selbst drum kümmern."

      Richtige Idee, aber REPLACE ist einfach das falsche Statement dafür.

      Besser: INSERT ... ON DUPLICATE KEY UPDATE ...

      http://dev.mysql.com/doc/refman/4.1/en/insert-on-duplicate.html

      Zerbeißt existierende auto_increment-Spalten nicht (wegen DELETE-INSERT-Abfolge bei REPLACE), und ist auch sonst deutlich handlungsflexibler, weil das UPDATE absolut nichts mit dem INSERT zu tun haben muß, bzw. komplett andere Dinge tun kann, beispielsweise statt dem INSERT eines neuen Datensatzes einen Zähler im bestehenden Datensatz hochzählen.

      Und atomar ist das Statement auch noch (das aber genauso, wie REPLACE) - also tausendmal besser, als die jetzige Lösung von KraKi, bei der es ja durchaus sein kann, dass zwei Prozesse parallel feststellen, dass UPDATE erfolglos war, und dann beide gemeinsam zum INSERT schreiten.

      - Sven Rautenberg

      --
      "Love your nation - respect the others."
      1. *grummel* ich hatte diesen Beitrag doch gestern beantwortet...

        Moin!

        Moin!

        Und atomar ist das Statement auch noch (das aber genauso, wie REPLACE) - also tausendmal besser, als die jetzige Lösung von KraKi, bei der es ja durchaus sein kann, dass zwei Prozesse parallel feststellen, dass UPDATE erfolglos war, und dann beide gemeinsam zum INSERT schreiten.

        Nein, da hast du dich verlesen. In dem oben stehenden Code fehlen zwar zwei else-Anweisungen (ist mir später aufgefallen), aber es gibt niemals zwei INSERTs. Das Problem ist ja, dass es ein erfolgreiches UPDATE+INSERT gibt, aber niemals UPDATE+INSERT+INSERT und auch nicht INSERT+INSERT. Wie gesagt, da musst du dich verlesen haben.

        Am besten sagst du dir einfach, "das ist mir völlig Wurscht, interessiert mich nicht - soll sich die DB doch selbst drum kümmern."
        Besser: INSERT ... ON DUPLICATE KEY UPDATE ...

        http://dev.mysql.com/doc/refman/4.1/en/insert-on-duplicate.html

        Zerbeißt existierende auto_increment-Spalten nicht (wegen DELETE-INSERT-Abfolge bei REPLACE), und ist auch sonst deutlich handlungsflexibler, weil das UPDATE absolut nichts mit dem INSERT zu tun haben muß, bzw. komplett andere Dinge tun kann, beispielsweise statt dem INSERT eines neuen Datensatzes einen Zähler im bestehenden Datensatz hochzählen.

        Tja, leider scheint auch diese Technik nicht für meine Problem geeignet, denn wie ich ein wenig weiter unten erklärte möchte ich, wenn es die id nicht gibt gerne den INSERT durchführen. Dann allerdings _ohne_ id-Angabe, also dem auto-increasement folgend. Das ist hier auch nicht möglich, ich mache einen INSERT und der wird ausgeführt, es sei denn der Datensatz mit der angegebenen id existiert, dann wird er aktualisiert.

        Naja, wie auch immer, mir ist gestern eine Idee gekommen, wie ich das Problem möglichweise schon in PHP lösen kann (ich denke ich kann VORHER vorhersehen, ob ein Datensatz existiert oder nicht). Trotzdem vielein Dank für die Antworten euch beiden, ich habe wieder 'was gelernt, was ich bestimmt mal gebrauchen kann (ich sollte endlich mal (my)SQL erlernen).

        Schönen Mittwoch noch.

        1. Moin!

          Und atomar ist das Statement auch noch (das aber genauso, wie REPLACE) - also tausendmal besser, als die jetzige Lösung von KraKi, bei der es ja durchaus sein kann, dass zwei Prozesse parallel feststellen, dass UPDATE erfolglos war, und dann beide gemeinsam zum INSERT schreiten.

          Nein, da hast du dich verlesen. In dem oben stehenden Code fehlen zwar zwei else-Anweisungen (ist mir später aufgefallen), aber es gibt niemals zwei INSERTs. Das Problem ist ja, dass es ein erfolgreiches UPDATE+INSERT gibt, aber niemals UPDATE+INSERT+INSERT und auch nicht INSERT+INSERT. Wie gesagt, da musst du dich verlesen haben.

          Du hast meinen Einwurf nicht verstanden.

          Wenn dein EINER PHP-Code ein UPDATE plus ggf. INSERT macht - was passiert dann, wenn dieser eine Code parallel von zwei Browsern aufgerufen wird?

          Genau, es werden mindestens zwei UPDATES gemacht, und je nachdem, wie jeweils die Datenbank reagiert, eventuell auch noch zwei INSERTS.

          Hängt nur davon ab, zu welchen Zeitpunkten das jeweils geschieht.

          Und deshalb gibts eben sowas wie Transaktionen, mit denen man mehrere SQL-Statements zu einem zusammenfassen kann, damit zwei parallele Prozesse sich nicht gegenseitig ins Gehege kommen.

          - Sven Rautenberg

          --
          "Love your nation - respect the others."