borisbaer: Datenbank-Werte mit übergebenen Form-Werten vergleichen

Ich sitze seit Stunden an einem Problem, das ich einfach nicht lösen kann: Ich möchte, dass beim Schreiben in die DB überprüft wird, ob eine Reihe mit einer bestimmten ID bereits vorhanden ist und wenn ja, soll diese aktualisiert werden, ansonsten soll eine neue Reihe erstellt werden.

Das Formular übergibt mir die ID des Inputs sowie dessen Wert (von 0 bis 2). Bei drei Inputs sieht das so aus:

Array
(
    [3] => 0
    [7] => 1
    [5] => 2
)

Wichtig ist, dass die IDs in keiner chronologischen Abfolge auftreten müssen. In die Datenbank sollen nur Reihen geschrieben werden, die nicht den Wert 0 haben. In obigem Beispiel wären das die [7] und die [5]. Wenn ich dann die DB-Tabelle auslese, erscheint richtigerweise Folgendes:

$dbTable = Array
(
    [0] => Array
        (
            [id] => 7
            [value] => 1
        )

    [1] => Array
        (
            [id] => 5
            [value] => 1
        )
)

Bei der nächsten Formular-Übergabe soll das Skript schauen, ob in der DB eine entsprechende ID schon vorhanden ist. Wenn ja, soll der Wert aktualisiert werden, wenn nicht, soll eine neue Reihe erstellt werden. Hingekriegt habe ich nur, dass die richtigen IDs bei Bedarf aktualisiert werden, aber beim Erstellen neuer Reihen schießt mir ein doppelter foreach-Loop quer, denn es werden gleich mehrere Reihen erstellt. Hier der Code-Auszug:

foreach ( $_POST['input'] as $id => $value ) {

	foreach ( $dbTable as $key => $row ) {

		if ( $row['id'] === ( int ) $id ) {

			$ReleasesModel -> update();

		}

	}

}

Ich bitte um Hilfe! 😞

akzeptierte Antworten

    1. Vorausgesetzt, Deine „ID“ ist zwingend unique:

      https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html

      Die ID wird es mehrmals in der Tabelle geben. Es ist quasi die Objekt-ID und sie wird für die jeweiligen Benutzer-ID manipuliert. Ich habe das der Einfachheit halber aber bei meinem ursprünglichen Beitrag nicht mit einbezogen.

      1. Moin,

        Die ID wird es mehrmals in der Tabelle geben. Es ist quasi die Objekt-ID und sie wird für die jeweiligen Benutzer-ID manipuliert. Ich habe das der Einfachheit halber aber bei meinem ursprünglichen Beitrag nicht mit einbezogen.

        Aber die Kombination aus Benutzer-ID und Objekt-ID ist eindeutig, oder? Das klappt dann trotzdem, im INSERT-Query müssen halt alle drei Felder genannt werden.

        Gruß
        Tobias

        1. Moin,

          Die ID wird es mehrmals in der Tabelle geben. Es ist quasi die Objekt-ID und sie wird für die jeweiligen Benutzer-ID manipuliert. Ich habe das der Einfachheit halber aber bei meinem ursprünglichen Beitrag nicht mit einbezogen.

          Aber die Kombination aus Benutzer-ID und Objekt-ID ist eindeutig, oder? Das klappt dann trotzdem, im INSERT-Query müssen halt alle drei Felder genannt werden.

          Jepp. Und der genannte „UNIQUE Constraint“ muss sich dann über die Spalten erstecken, die in jeglicher Kombination maximal einmal auftauchen sollen.

          @@borisbaer

          ALTER TABLE `tab` (
              UNIQUE KEY ( `UserID`, `ObjectID` )
          )
          

          Die Benennung eines Feldes mit „ID“, welches keine echte ID ist, ist höchst unglücklich. Das wird zu Fehlern führen, weil es im sachlichen Zusammenhang einfach mal keine ID ist, aber jeder wegen des Bezeichners denkt, es sei eine solche - und also sei jede einzigartig…

          „Ich lösche mal fix einen Datensatz:“

          DELETE FROM `tab` WHERE ID=121;
          346967 Zeilen gelöscht.
          

          „Oh.“

          1. Jepp. Und der genannte „UNIQUE Constraint“ muss sich dann über die Spalten erstecken, die in jeglicher Kombination maximal einmal auftauchen sollen.

            Gut zu wissen, danke!

            ALTER TABLE `tab` (
                UNIQUE KEY ( `UserID`, `ObjectID` )
            )
            

            Dieser Befehl hat leider nicht funktioniert, dafür aber dieser hier …

            ALTER TABLE `table` ADD UNIQUE ( `UserID`, `ObjectID` )
            

            Die Benennung eines Feldes mit „ID“, welches keine echte ID ist, ist höchst unglücklich. Das wird zu Fehlern führen, weil es im sachlichen Zusammenhang einfach mal keine ID ist, aber jeder wegen des Bezeichners denkt, es sei eine solche - und also sei jede einzigartig…

            Danke für den Hinweis, dann werde ich die Benennung überdenken!

            „Ich lösche mal fix einen Datensatz:“

            DELETE FROM `tab` WHERE ID=121;
            346967 Zeilen gelöscht.
            

            „Oh.“

            🤯

  1. Moin,

    Ich sitze seit Stunden an einem Problem, das ich einfach nicht lösen kann: Ich möchte, dass beim Schreiben in die DB überprüft wird, ob eine Reihe mit einer bestimmten ID bereits vorhanden ist und wenn ja, soll diese aktualisiert werden, ansonsten soll eine neue Reihe erstellt werden.

    Das klingt – vorausgesetzt du verwendest MySQL/MariaDB[1] und auf den IDs liegt ein Unique-Index o.ä. – nach einem Fall für INSERT … ON DUPLICATE KEY, etwa so:

    INSERT INTO tabelle (id, value) VALUES (:id, :value) as new
      ON DUPLICATE KEY UPDATE value = new.value;
    

    Dein doppeltes foreach brauchst du dann natürlich nicht und :id/:value sind Platzhalter für prepared Statements. Zu klären wäre natürlich noch was passieren soll wenn ein Wert 0 ist (dann müsste der Datensatz ggf. gelöscht werden).

    Gruß
    Tobias


    1. das ist kein SQL-Standard deswegen nur MySQL/MariaDB - andere DBMS haben aber wohl ähnliche Lösungen dafür, Postgresql z.B. INSERT … ON CONFLICT ↩︎

    1. Das klingt – vorausgesetzt du verwendest MySQL/MariaDB[^1] und auf den IDs liegt ein Unique-Index o.ä. – nach einem Fall für INSERT … ON DUPLICATE KEY, etwa so:

      INSERT INTO tabelle (id, value) VALUES (:id, :value) as new
        ON DUPLICATE KEY UPDATE value = new.value;
      

      Ja, MariaDB wird verwendet.

      Vielen Dank, das funktioniert so tatsächlich. Aber das as new und dann value = new.value will er irgendwie nicht schlucken. Liegt vielleicht daran, dass ich mit bindValue arbeite, aber wenn ich ganz normal value = :value am Ende als Syntax habe, geht der Befehl.

      Dein doppeltes foreach brauchst du dann natürlich nicht und :id/:value sind Platzhalter für prepared Statements. Zu klären wäre natürlich noch was passieren soll wenn ein Wert 0 ist (dann müsste der Datensatz ggf. gelöscht werden).

      Mich würde dennoch interessieren, ob und wie man das auch mit PHP-Schleifen bewerkstelligen könnte. Aber ich konnte mir einfach keine abstrakte Logik dazu ausdenken, die funktioniert.

      Gruß
      Boris

      1. Moin,

        Vielen Dank, das funktioniert so tatsächlich. Aber das as new und dann value = new.value will er irgendwie nicht schlucken. Liegt vielleicht daran, dass ich mit bindValue arbeite, aber wenn ich ganz normal value = :value am Ende als Syntax habe, geht der Befehl.

        Nein, das liegt daran dass du unter MariaDB noch mit VALUES(value) arbeiten musst, siehe Handbuch – bei MySQL ist der Syntax seit Version 8.0.20 als veraltet eingestuft.

        Mich würde dennoch interessieren, ob und wie man das auch mit PHP-Schleifen bewerkstelligen könnte. Aber ich konnte mir einfach keine abstrakte Logik dazu ausdenken, die funktioniert.

        Du bräuchtest vor dem inneren foreach noch eine Variable (z.B. $gefunden = false;) welche du innerhalb des if-Blocks auf true setzt. Wenn $gefunden dann nach der inneren foreach-Schleife immer noch false ist, gibt es den Datensatz noch nicht und du müsstest ein INSERT ausführen.

        Gruß
        Tobias

        1. Hallo Tobias,

          der Syntax

          autsch, gute Besserung. Es heißt im Deutschen die Syntax.
          Wahrscheinlich angelehnt an die Satzlehre.

          Einen schönen Tag noch
           Martin

          --
          Wer andern eine Bratwurst brät,
          braucht wohl ein Bratwurstbratgerät.
        2. Nein, das liegt daran dass du unter MariaDB noch mit VALUES(value) arbeiten musst, siehe Handbuch – bei MySQL ist der Syntax seit Version 8.0.20 als veraltet eingestuft.

          Ja, mein Erklärungsversuch mit bindValue ergibt keinen Sinn.

          Du bräuchtest vor dem inneren foreach noch eine Variable (z.B. $gefunden = false;) welche du innerhalb des if-Blocks auf true setzt. Wenn $gefunden dann nach der inneren foreach-Schleife immer noch false ist, gibt es den Datensatz noch nicht und du müsstest ein INSERT ausführen.

          Boah, na klar. Mann, vielen Dank, Tobias. Die Lösung ist so einfach und ich habe da sonst was für Verrenkungen angestellt. Das merke ich mir auf jeden Fall fürs nächste Mal. 🙏

          So sieht das nun aus:

          $isNew = true;
          
          foreach ( $releases as $row )
          
          	if ( $row['id'] === $id )
          
          		$isNew = false;
          
          if ( $isNew === true && $value > 0 )
          
          	$Releases -> create();
          
          else $Releases -> update();
          
          header( 'Location:' . $_SERVER['HTTP_REFERER'] );
          

          Muss nur noch was rein, damit die Reihe wieder gelöscht wird, wenn der aktualisierte Wert 0 entspricht. Aber das geht ja wieder leicht.

          Grüße
          Boris