• Jetzt anmelden. Es dauert nur 2 Minuten und ist kostenlos!

Rows affected ist nicht eindeutig

Werbung:
Danke für den Tipp. Ich hab' mir die Sache angeschaut, komme aber nicht zurecht. Vielleicht hast Du noch einen Tipp auf Lager:

Tabelle visitors
visitor_id (Autoincrement, Primarykey, NotNULL)
session_id (varchar255, unique, NotNULL)
customer_id (int11, NULL)
created (timestamp, Standard->CurrentTimestamp)
lastchange (datetime, NULL)

Mein SQL-Statement
INSERT INTO visitors (session_id, customer_id, lastchange)
VALUES (session_id = 'cvke54vdesqvve5cisvk0rlvd5', customer_id = 7, lastchange = NOW())
ON DUPLICATE KEY UPDATE customer_id = 7, lastchange = NOW()

Das Statement läuft ohne Mecker durch,
Meldung: [1 Zeile(n) eingefügt. ID der eingefügten Zeile: 16 ( die Abfrage dauerte 0.0556 sek.]

Aber das Ergebnis, gleichgültig, ob die session-id in der Tabelle besteht oder ob sie nicht besteht:
es wird ein neuer DS angelegt mit der Session_id 0 und der customer_id NULL,
(created hat natürlich den aktuellen Zeitstempel und die visitor_id ist auch hochgezählt)

Also ist ganz klar an meinem Statement was falsch, aber was?

Edit:
Dieses Statement läuft durch und macht brav das gewünschte Update:
UPDATE visitors SET customer_id = 7, lastchange = NOW()
WHERE session_id = 'cvke54vdesqvve5cisvk0rlvd5'

############################
Hat sich alles erledigt, ist ja schon peinlich, hatte ich aber ein Brett vorm Kopf
Das Statement muß natürlich nicht so lauten:
INSERT INTO visitors (session_id, customer_id, lastchange)
VALUES (session_id = 'cvke54vdesqvve5cisvk0rlvd5', customer_id = 7, lastchange = NOW())
ON DUPLICATE KEY UPDATE customer_id = 7, lastchange = NOW()

sondern so (ohne Wiederholung der Feldnamen)
INSERT INTO visitors (session_id, customer_id, lastchange)
VALUES ( 'cvke54vdesqvve5cisvk0rlvd5', 7, NOW())
ON DUPLICATE KEY UPDATE customer_id = 7, lastchange = NOW()
 
Zuletzt bearbeitet:
Tja, zu früh gefreut. Nochmal ganz von vorne.
Bitte nur die Fakten in diesem Beitrag als Grundlage der Problembeschreibung nehmen.


Tabelle visitors
visitor_id = Autoincrement, Primary Key
session_id = varchar, Unique
customer_id = int(11)
created = timestamp, Standard current timestamp
lastchange = datetime

SQL-Statement
INSERT INTO visitors (session_id, lastchange)
VALUES ( '" . session_id() . "', NOW())
ON DUPLICATE KEY UPDATE lastchange = NOW()

Das Statement wird prepared und dann per exec ausgeführt. Es gibt 3 mögliche Rückgabewerte:
1 = Datensatz angefügt, d. h. die Session-Id gab es noch nicht
2 = Feld lastchange wurde aktualisiert, d. h. die Session-Id gab es, aber die Zeit wurde aktualisiert
0 = es wurde nix gemacht, d. h. die Session-Id gab es schon und die Zeit noch stimmte noch (das kann vorkommen, wenn das Statement binnen einer Sekunde zweimal ausgeführt wird).

Jetzt kommt der Haken an der Sache: um einen Fehler zu provozieren, habe ich in dem SQL-Statement "NOW()" durch "X" ersetzt; damit versuche ich also verbotenerweise einen String in ein Zeitfeld einzufügen.
Die Rückgabe ist auch bei diesem fehlerhaften Statement 0.
Wie kann ich aber einen unmißverständlichen Rückgabewert bekommen? Denn 0 muß ja offensichtlich nicht heißen, daß meine Daten in Ordnung sind.
 
Werbung:
Um einen Fehler zu provozieren, bau einen Schreibfehler im Wort INSERT ein. MySQL ist flexibel genug um deinen Versuch nicht als Fehler zu werten.
 
Wie ermittelst du denn derzeit den Rückgabewert? Noch per mysql_affected_rows? Oder was gibt da 0, 1 oder 2 zurück?
 
Werbung:
Danke für Dein Interesse.
So sieht's aus:
PHP:
$sql = "INSERT INTO visitors (session_id, lastchange)";
$sql .= " VALUES ('" . session_id() . "', NOW())";
$sql .= " ON DUPLICATE KEY UPDATE lastchange = NOW()";
    
$insert = $this->db->prepare($sql);
$rows_affected = $this->db->exec($sql);

Inzwischen bin ich auf error_info und error_code gestoßen, bringt aber auch nix:
PHP:
var_dump($insert->errorInfo()); // Ausgabe: array(3) {   [0]=>   string(0) ""   [1]=>   NULL   [2]=>   NULL } 
var_dump($insert->errorCode()); // Ausgabe: NULL
Dabei ist es gleichgültig, was $rows_affected zurückliefert, der error ist immer leer.
Ich wäre dankbar, wenn mir einer sagen könnte, wo ich weitersuchen kann, oder ob es einfach keine vernünftige Rückgabe gibt. In diesem Fall müßte ich halt mit einem zusätzlichen SELECT feststellen, was in der Tabelle los ist.
 
In diesem Fall müßte ich halt mit einem zusätzlichen SELECT feststellen, was in der Tabelle los ist.
was auch sicher die vernünftigste Lösung wäre. Zumal es hier nur um eine minimale Abfrage geht, auf ein Feld, dass hoffentlich ein Index ist. Diese Abfrage dauert nur wenige Millisekunden, ich würde mich nicht darauf verlassen, dass du wirklich etwas spart mit einem komplexeren INSERT

Code:
SELECT session_id FROM visitors WHERE session_id = '...' LIMIT 1
 
Joa, müsste man testen. Aber bevor du/wir noch Jahre an der Sache hängen, vielleicht erst mal so implementieren und bei akutem Optimierungsbedarf noch mal drüber nachdenken. Die SELECT- und die UPDATE-Query müssten aber in einer Transaktion stehen.

Ich probiere da nachher aber noch etwas rum, interessiert mich jetzt auch. Faule Anfrage: Könntest du vielleicht das CREATE-Statement für die Tabelle posten, achtelpetit?
 
Werbung:
Danke, daß Du Dir meinen Kopf zerbrichst.
Das Create-Statement:
PHP:
CREATE TABLE `minishop`.`visitors` (
`visitor_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`session_id` VARCHAR( 255 ) NOT NULL ,
`customer_id` INT( 11 ) NOT NULL ,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
`lastchange` DATETIME NOT NULL ,
UNIQUE (
`session_id`
)
) ENGINE = InnoDB;
Wenn Transaktion, dann ja InnoDB

Edit:
Die ganze Sache ist nur eine Übungsaufgabe, die ich mir selbst gestellt habe; also habe ich keinen Zeitdruck. Aber eben weil ich lernen möchte, will ich solchen Dingen auf den Grund gehen und mich nicht mit einer "Hauptsache-es-läuft-Lösung" zufrieden geben.
 
Also, es ist tatsächlich (siehe threadi) schlicht und ergreifend kein Fehler, für lastchange 'X' einzutragen. Das DBMS interpretiert den Wert automatisch als '0000-00-00 00:00:00'.

Wenn du diesen Fall speziell abfangen möchtest, müsstest du vor dem Absenden der Query prüfen, ob die Angabe für lastchange korrekt ist.



Hat nichts damit zu tun, aber hier mein Demo-Code, falls er später noch gebraucht wird.

PHP:
<?php

namespace demo;

use \PDO, \PDOStatement;

function insert(PDOStatement $sth, $sessionId, $customerId)
{
    $sth->execute(array(
        ':sessionId' => $sessionId,
        ':customerId' => $customerId
    ));

    printf("\tINSERT ('%s', '%s') --> %s\n",
           $sessionId, $customerId, $sth->rowCount());
}

$pdo = new PDO('mysql:dbname=test;host=localhost', 'user', 'password',
               array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"));

$pdo->query("DROP TABLE IF EXISTS `visitors`");

$pdo->query("
    CREATE TABLE IF NOT EXISTS `visitors` (
        `visitor_id`  INT            NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        `session_id`  VARCHAR( 255 ) NOT NULL ,
        `customer_id` INT( 11 )      NOT NULL ,
        `created`     TIMESTAMP      NOT NULL DEFAULT CURRENT_TIMESTAMP ,
        `lastchange`  DATETIME       NOT NULL ,
        UNIQUE (
            `session_id`
        )
    ) ENGINE = InnoDB
    ");

$insertStatement = $pdo->prepare("
    INSERT INTO `test`.`visitors`
        (
            `visitor_id` ,
            `session_id` ,
            `customer_id` ,
            `created` ,
            `lastchange`
        )
    VALUES
        (
            NULL ,
            :sessionId ,
            :customerId ,
            CURRENT_TIMESTAMP ,
            NOW( )
        )
    ON DUPLICATE KEY UPDATE
        `lastchange` = NOW( )
    ");



header('Content-Type: text/plain; charset=UTF-8');

echo "Insert two rows\n";
insert($insertStatement, 'session1', 1);
insert($insertStatement, 'session2', 1);

echo "Update second row in same second\n";
insert($insertStatement, 'session2', 1);

sleep(2);
echo "Sleeping...\n";

insert($insertStatement, 'session2', 1);
 
lastchange sollte NULL sein, dann wird automatisch das update Datum eingetragen.

Aber das Problem des OP ist so nicht lösbar, wenn ein Datensatz sich nicht ändert, weil lastchange den gleichen Wert wie vorher hatte, dann wird ON .. UPDATE nicht greifen. D.h. - ich wiederhole mich - es geht nur über ein vorheriges SELECT, was aber in dem Szenario keinerlei Nachteil darstellt.

Der einzige alternative Weg wäre das Skript solange anhalten, dass der Timestamp sich auf jeden Fall geändert hat, was aber absolut Unsinnig wäre.
 
Werbung:
Danke für die Mühe, besonders an Mermshaus.
Sicher, Workarounds sind denkbar. Etwa, zunächst auf einen falschen Wert updaten (vielleicht 0) und dann mit einem zweiten Update auf den richtigen Wert.
1 Sekunde warten ist auch möglich, aber es stimmt schon, das wäre 'ne blöde Methode.
Eine Schleife, die so lange updatet, bis endlich eine 1 zurückgegeben wird.
Aber das ist alles Murks.

Überprüfung der Eingabedaten fällt hier eigentlich aus, denn NOW() ist ja eine Funktion von MySQL.

In diesem speziellen Fall hier wäre ein Fehler nicht tragisch, aber wie macht man das, wenn es um richtig wichtige Daten geht?
 
Wenn es vorkommen kann, dass ein Wert innerhalb einer Sekunde zweimal eingetragen wird, dann gibt es keine Lösung, da der Timestamp nur Sekunden genau ist.
 
Vermutlich liegt's an mir, aber ich verstehe das Problem nicht. Könnte noch mal jemand versuchen, es mir zu erklären?
 
Werbung:
Vermutlich liegt's an mir, aber ich verstehe das Problem nicht. Könnte noch mal jemand versuchen, es mir zu erklären?
Er hat eine Session ID, die möchte er in der Tabelle speichern. Da er nicht weiß, ob es diese ID schon in der Tabelle gibt, muss er eigentlich mit einem Select vorher eine Abfrage starten, dies fand achtelpetit aber zu umständlich und hätte es gerne in einer Abfrage.

Das würde theoretisch mit ON UPDATE gehen wenn der Timestamp immer erneuert wird. Das Problem ist, dass wenn der Aufruf innerhalb einer Sekunde zweimal erfolgt, kann er nicht erkennen, ob ein neuer Eintrag gemacht wurde oder nicht.

Wobei wenn ich das mir jetzt so überlege, es ist doch sowieso egal, da der Timestamp ja nur Sekunden genau ist, d.h. die Abfrage von dir erfüllt zu 100% ihren Zweck.
 
Werbung:
Ja, genau. Wenn das Update keine Änderung am Datensatz herbeiführt, ist eben kein Datensatz „affected“.

Was mich eher erstaunt, ist, dass im „ON DUPLICATE KEY UPDATE“-Fall tatsächlich zwei Rows beeinflusst werden. Das ist aber definiertes Verhalten in MySQL.

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row and 2 if an existing row is updated.

- MySQL :: MySQL 5.5 Reference Manual :: 12.2.5.3 INSERT ... ON DUPLICATE KEY UPDATE Syntax

Edit: Letzten Post nicht gesehen, beziehe mich hier nur auf struppi.

@achtelpetit:

Jetzt kommt der Haken an der Sache: um einen Fehler zu provozieren, habe ich in dem SQL-Statement "NOW()" durch "X" ersetzt; damit versuche ich also verbotenerweise einen String in ein Zeitfeld einzufügen.
Die Rückgabe ist auch bei diesem fehlerhaften Statement 0.
Wie kann ich aber einen unmißverständlichen Rückgabewert bekommen? Denn 0 muß ja offensichtlich nicht heißen, daß meine Daten in Ordnung sind.

Wie gesagt, 'X' ist kein ungültiger Wert, sondern wird zu '0000-00-00 00:00:00'.

Du könntest sonst noch mal versuchen, mein Beispiel so umbauen, dass das Problem offensichtlich wird, denn so sehe ich da nicht zwangsläufig falsches oder mehrdeutiges Verhalten.

PHP:
<?php

namespace demo;

use \PDO, \PDOStatement;

function insert(PDOStatement $sth, $sessionId, $customerId, $time = null)
{
    if ($time === null) {
        $time = date('Y-m-d H:i:s');
    }

    $sth->execute(array(
        ':sessionId' => $sessionId,
        ':customerId' => $customerId,
        ':time' => $time
    ));

    printf("\tINSERT ('%s', '%s', '%s') --> %s\n",
           $sessionId, $customerId, $time, $sth->rowCount());
}

$pdo = new PDO('mysql:dbname=test;host=localhost', 'user', 'pass',
               array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"));

$pdo->query("DROP TABLE IF EXISTS `visitors`");

$pdo->query("
    CREATE TABLE IF NOT EXISTS `visitors` (
        `visitor_id`  INT            NOT NULL AUTO_INCREMENT PRIMARY KEY ,
        `session_id`  VARCHAR( 255 ) NOT NULL ,
        `customer_id` INT( 11 )      NOT NULL ,
        `created`     TIMESTAMP      NOT NULL DEFAULT CURRENT_TIMESTAMP ,
        `lastchange`  DATETIME       NOT NULL ,
        UNIQUE (
            `session_id`
        )
    ) ENGINE = InnoDB
    ");

$insertStatement = $pdo->prepare("
    INSERT INTO `test`.`visitors`
        (
            `visitor_id` ,
            `session_id` ,
            `customer_id` ,
            `created` ,
            `lastchange`
        )
    VALUES
        (
            NULL ,
            :sessionId ,
            :customerId ,
            CURRENT_TIMESTAMP ,
            :time
        )
    ON DUPLICATE KEY UPDATE
        `customer_id` = :customerId,
        `lastchange` = :time
    ");



header('Content-Type: text/plain; charset=UTF-8');

echo "Insert two rows\n";
insert($insertStatement, 'session1', 1);
insert($insertStatement, 'session1', 1, 'X');

exit;

echo "Update second row in same second\n";
insert($insertStatement, 'session2', 1);

echo "Update second row in same second with new alue\n";
insert($insertStatement, 'session2', 2);

sleep(2);
echo "Sleeping...\n";

#insert($insertStatement, 'session2', 1);

sleep(2);
echo "Sleeping...\n";

#insert($insertStatement, 'session2', 1, 'X');
 
Zuletzt bearbeitet:
Wie gesagt, 'X' ist kein ungültiger Wert, sondern wird zu '0000-00-00 00:00:00'.
Na ja, in MySQL wird ein ungültiges Datum eben zu 0, aber dadurch wird's ja nicht zu einem gültigen Datum (Anbeginn aller Zeiten, das wär 0).
Es ist blöd, wenn ich keine Info darüber bekomme, daß ein Fehler aufgetreten ist. Ich muß die formale Datenintegrität in einem separaten Arbeitsschritt prüfen und evtl. Fehler nachträglich zu Fuß korrigieren. Lieber wär mir halt, wenn MySQL sagte: Nö, das ist kein Datum, sowas füge ich nicht ein.
 
Zurück
Oben