So führen Sie ein UPSERT durch, damit ich sowohl neue als auch alte Werte im Update-Teil verwenden kann


75

Dummes, aber einfaches Beispiel: Angenommen, ich habe eine Tabelle 'Artikel', in der ich die Gesamtsummen der empfangenen Artikel aufbewahre.

Item_Name              Items_In_Stock

Der Artikelname ist hier der Primärschlüssel. Wie erreiche ich Folgendes, wenn ich Artikel A in Menge X erhalte?

Wenn der Artikel nicht vorhanden ist, füge ich einen neuen Wert für Artikel A ein und setze die Artikel auf Lager auf X. Wenn ein Datensatz vorhanden ist, in dem Artikel auf Lager Y waren, lautet der neue Wert für Artikel auf Lager (X + Y).

INSERT INTO `item`
(`item_name`, items_in_stock)
VALUES( 'A', 27)
ON DUPLICATE KEY UPDATE
`new_items_count` = 27 + (SELECT items_in_stock where item_name = 'A' )

Mein Problem ist, dass ich mehrere Spalten in meiner tatsächlichen Tabelle habe. Ist es eine gute Idee, mehrere select-Anweisungen in den Update-Teil zu schreiben?

Natürlich kann ich das im Code machen, aber gibt es einen besseren Weg?


Warum sollten Sie in Ihrem Beispiel eine Unterabfrage verwenden? Du hättest es einfach benutzen können ON DUPLICATE KEY UPDATE new_items_count = new_items_count + 27. Da Sie keine anderen Spalten veröffentlicht haben, ist es schwierig, Ihre Frage zu beantworten, da das erwartete Verhalten unbekannt ist. Was machst du mit anderen Spalten? Einige Zahlen aktualisieren oder?
Michael JV

Toll. Das beantwortet die Frage. Ich wusste nicht, dass ich Spaltennamen anstelle von Unterabfragen verwenden kann. Wenn Sie es als Antwort posten, werde ich es als Antwort akzeptieren.
WPFAbsoluteNewBie

Es gibt eine Ruby-Bibliothek, die eine MySQL-Funktion definiert: github.com/seamusabshere/upsert
Seamus Abshere

was ist new_items_counthier Ist es eine andere Spalte auf Ihrem Tisch?
Asgs

Antworten:


150

Wie in meinem Kommentar erwähnt, müssen Sie die Unterauswahl nicht vornehmen, um auf die Zeile zu verweisen, die dazu führt, dass ON DUPLICATE KEY ausgelöst wird. In Ihrem Beispiel können Sie also Folgendes verwenden:

INSERT INTO `item`
(`item_name`, items_in_stock)
VALUES( 'A', 27)
ON DUPLICATE KEY UPDATE
`new_items_count` = `new_items_count` + 27

Denken Sie daran, dass die meisten Dinge wirklich einfach sind. Wenn Sie sich dabei ertappen, etwas zu komplizieren, das einfach sein sollte, dann tun Sie es höchstwahrscheinlich falsch :)


Sie retten den Tag. Arbeiten Sie auch mit PDO
Ruwantha

Schöne saubere und prägnante Antwort
MikeT

2
Hinweis: Es sieht so aus, als würde die ON DUPLICATE KEY UPDATE-Klausel den Auto-Inkrement-Wert erhöhen, wenn das PK-Feld automatisch inkrementiert wird, unabhängig davon, welcher Effekt (INSERT oder UPDATE) ausgelöst wurde (bei Verwendung der InnoDB-Engine). Siehe: MySQL Referenzhandbuch
Alejandro

Um Codeduplizierungen zu vermeiden, sollten Sienew_items_count = new_items_count + VALUES(items_in_stock)
Paul Spiegel

1
Kann dies mit REPLACEAbfrage erfolgen? Auf diese Weise ist es viel einfacher
Shankar Thyagarajan

13

Anhand dieses Beispiels können Sie sich ein Bild machen:

Angenommen, Sie möchten benutzerbezogene Daten für sieben Tage hinzufügen

Es sollte einen eindeutigen Wert für Benutzer-ID und Tag haben

UNIQUE KEY `seven_day` (`userid`,`day`)

Hier ist die Tabelle

CREATE TABLE `table_name` (
  `userid` char(4) NOT NULL,
  `day` char(3) NOT NULL,
  `open` char(5) NOT NULL,
  `close` char(5) NOT NULL,
  UNIQUE KEY `seven_day` (`userid`,`day`)
);

Und Ihre Anfrage wird sein

INSERT INTO table_name (userid,day,open,close) 
    VALUES ('val1', 'val2','val3','val4') 
        ON DUPLICATE KEY UPDATE open='val3', close='val4';

Beispiel:

<?php
//If your data is
$data= array(
        'sat'=>array("userid"=>"1001", "open"=>"01.01", "close"=>"11.01"),
        'sun'=>array("userid"=>"1001", "open"=>"02.01", "close"=>"22.01"),
        'sat'=>array("userid"=>"1001", "open"=>"03.01", "close"=>"33.01"),
        'mon'=>array("userid"=>"1002", "open"=>"08.01", "close"=>"08.01"),
        'mon'=>array("userid"=>"1002", "open"=>"07.01", "close"=>"07.01")
    );


//If you query this in a loop
//$conn = mysql_connect("localhost","root","");
//mysql_select_db("test", $conn);

foreach($data as $day=>$info) {
    $sql = "INSERT INTO table_name (userid,day,open,close) 
                VALUES ('$info[userid]', '$day','$info[open]','$info[close]') 
            ON DUPLICATE KEY UPDATE open='$info[open]', close='$info[close]'";
    mysql_query($sql);
}
?>

Ihre Daten werden in der Tabelle aufgeführt:

+--------+-----+-------+-------+
| userid | day | open  | close |
+--------+-----+-------+-------+
| 1001   | sat | 03.01 | 33.01 |
| 1001   | sun | 02.01 | 22.01 |
| 1002   | mon | 07.01 | 07.01 |
+--------+-----+-------+-------+

6
Verketten Sie niemals Daten direkt in einer Abfrage. Verwenden Sie vorbereitete / parametrisierte Abfragen mit PDO oder ähnlichem. In diesem Beispiel wäre es angebracht gewesen, zumindest Daten zu maskieren.
Brad

Beachten Sie, dass dieser Code die mysql_*Funktionen verwendet, die in PHP 7 entfernt wurden. Wenn Sie über einen Ersatz nachdenken , sehen Sie sich dieses interessante Rätsel zwischen PDO und MySQLi an.
i336_

11

Obwohl Michaels Antwort die richtige ist, müssen Sie ein bisschen mehr wissen, um das Upsert programmgesteuert durchzuführen:

Erstellen Sie zunächst Ihre Tabelle und geben Sie an, für welche Spalten ein eindeutiger Index erstellt werden soll:

CREATE TABLE IF NOT EXISTS Cell (
  cellId BIGINT UNSIGNED,
  attributeId BIGINT UNSIGNED,
  entityRowId BIGINT UNSIGNED,
  value DECIMAL(25,5),
  UNIQUE KEY `id_ce` (`cellId`,`entityRowId`)
)

Fügen Sie dann einige Werte ein:

INSERT INTO Cell VALUES( 1, 6, 199, 1.0 );

Versuchen Sie dasselbe noch einmal, und Sie erhalten einen doppelten Schlüsselfehler, weil cellIdund entityRowIddasselbe sind:

INSERT INTO Cell VALUES( 1, 6, 199, 1.0 );

Doppelter Eintrag '1-199' für Schlüssel 'id_ce'

Deshalb verwenden wir den Befehl upsert:

INSERT INTO Cell ( cellId, attributeId, entityRowId, value)
VALUES( 1, 6, 199, 300.0 )
ON DUPLICATE KEY UPDATE `value` = `value` + VALUES(`value`)

Dieser Befehl nimmt den 1.0bereits vorhandenen Wert als Wert und führt a aus value = value + 300.0.

Selbst nach Ausführung des obigen Befehls befindet sich nur eine Zeile in der Tabelle, und der Wert ist 301.0.


Sollte der Endwert nicht sein 301.0und nicht 302.0?
Alejandro

2
Dies ist möglicherweise nicht die akzeptierte Antwort, aber die zusätzlichen Details haben mich sehr beeindruckt und positiv bewertet.
HPWD

1

Dies ist die Syntax für einen Upsert

INSERT INTO `{TABLE}` (`{PKCOLUMN}`, `{COLUMN}`) VALUES (:value)
ON DUPLICATE KEY UPDATE `{COLUMN}` = :value_dup';

1

Wenn Sie Wert für PK - Spalte haben, oder einen eindeutigen Index für eine Spalte , die erfüllt unicity, können Sie verwenden INSERT IGNORE, INSERT INTO ... ON DUPLICATEoderREPLACE

Beispiel mit INSERT IGNORE

INSERT IGNORE INTO Table1
    (ID, serverID, channelID, channelROLE)
VALUES
    (....);

Beispiel mit INSERT INTO .. ON DUPLICATE KEY UPDATE

SET @id = 1,
    @serverId = 123545,
    @channelId = 512580,
    @channelRole = 'john';
INSERT INTO Table1
    (ID, serverID, channelID, channelROLE)
VALUES
    (@id, @serverId, @channelId, @channelRole)
ON DUPLICATE KEY UPDATE
    serverId = @serverId,
    channelId = @channelId,
    channelRole = @channelRole;

Beispiel mit Replace

REPLACE INTO table1
    (ID, serverID, channelID, channelROLE)
VALUES
    (...);

0

Beispiel für Upsert

INSERT INTO table1 (col1, col2, col3)
VALUES ($1, $2, $3)
ON CONFLICT (col1)
DO
UPDATE
SET col2 = $2, col3 = $3
WHERE col1 = $1
RETURNING col1
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.