2012-03-30 5 views
2

Ich habe ein Problem mit gegenseitigen Einschränkungen. Ich möchte zwei Tabellen haben, die jeweils eine Einschränkung für die andere haben.SQL-Problem, gegenseitige Einschränkungen, "eine Fremdschlüsseleinschränkung schlägt fehl"

Ich bin mit Doctrine2 arbeiten (aber es ist nicht mit dem Problem), hier ist meine vereinfachte Code:

SQL:

CREATE TABLE IF NOT EXISTS `thread` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `last_message_id` int(11) DEFAULT NULL, 
    `subject` varchar(255) NOT NULL 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `UNIQ_C023F2BBBA0E79C3` (`last_message_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

ALTER TABLE `thread` 
ADD CONSTRAINT `FK_C023F2BBBA0E79C3` FOREIGN KEY (`last_message_id`) REFERENCES `message` (`id`); 

CREATE TABLE IF NOT EXISTS `message` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `user_id` int(11) DEFAULT NULL, 
    `thread_id` int(11) DEFAULT NULL, 
    `body` longtext NOT NULL 
    PRIMARY KEY (`id`), 
    KEY `IDX_9E4E8B5FA76ED395` (`user_id`), 
    KEY `IDX_9E4E8B5FE2904019` (`thread_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

ALTER TABLE `message` 
ADD CONSTRAINT `FK_9E4E8B5FE2904019` FOREIGN KEY (`thread_id`) REFERENCES `thread` (`id`) ON DELETE CASCADE; 

Doctrine2 Mapping (die den SQL-Code oben generiert):

<?php 

class Thread 
{ 
    /* @ORM\OneToOne() */ 
    private $lastMessage; 
} 

class Message 
{ 
    /* @ORM\ManyToOne() */ 
    private $thread; 
} 

und wenn ich versuche, entweder einen Thread oder eine Nachricht zu löschen, erhalte ich (logisch) den Fehler: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails ('thread', CONSTRAINT 'FK_C023F2BBBA0E79C3' FOREIGN KEY ('last_message_id') REFERENCES 'message' ('id'))

Also, gibt es eine Möglichkeit, diesen Fehler zu vermeiden? Oder sollte ich gegenseitige Zwänge vergessen? Alles?

Ich möchte hinzufügen, dass ich die last_message_id behalten möchte, weil ich die Threads mit Infos zu ihrer letzten Nachricht anzeigen möchte, und eine (paginierte) Abfrage ohne diesen Verweis auf die letzte Nachricht zu erstellen war ein totaler Albtraum ...

Danke!

+0

können Sie Ihre Tabelle Schema oder Diagramm, Code in diesem Fall ist nicht wirklich hilfreich. – Robert

+0

Sie müssen den Wert 'lastMessage' in der Tabelle' thread' ändern, bevor Sie die aktuelle letzte Nachricht löschen. Wenn keine Nachrichten mehr vorhanden sind, d. H. Der Thread leer ist, sollte 'lastMessage' auf NULL gesetzt werden. – mellamokb

+0

Ihr SQL-Code enthält nicht die 'FOREIGN KEY'-Einschränkungen (nicht, dass sie benötigt werden, das Problem ist klar, nur der Vollständigkeit halber). –

Antwort

2

Die beste Lösung, die ich mir vorstellen kann, würde sein, eine ON DELETE CASCADE Einschränkung der FK in der Tabelle Thread hinzuzufügen. Wenn Sie den Thread löschen, werden die zugehörigen Nachrichten automatisch gelöscht.

In ähnlicher Weise müssten Sie eine ON DELETE SET NULL-Einschränkung für die Nachrichtentabelle FK hinzufügen. Wenn Sie also die letzte Nachricht in einem Thread löschten, würde die Last_Message_ID in der Thread-Tabelle auf NULL gesetzt.

Oder Sie könnten nur logische (Soft-) Löschungen anstelle von harten Löschungen, die das Problem auch lösen würde.

ETA:

Nun, da Sie die Einschränkungen gebucht haben, ist dies diejenige, die Sie ändern müssen, wäre:

ALTER TABLE `thread` 
ADD CONSTRAINT `FK_C023F2BBBA0E79C3` FOREIGN KEY (`last_message_id`) 
REFERENCES `message` (`id`) ON DELETE SET NULL; 
+0

Vielen Dank! Es hat funktioniert :) – Nanocom

0

Wenn Sie gegenseitige Einschränkungen haben (dh jede Nachricht hat einen Thread und jeder Thread hat eine Nachricht) Warum können Sie das nicht in einer Tabelle kombinieren? Scheint sinnvoller zu sein

+2

Nicht möglich, da ein Thread mehrere Nachrichten hat, aber nur eine letzte Nachricht – Nanocom

2

Kreisbahnen in FOREIGN KEY Zwänge nur schwer zu behandeln und Ihr Problem ist ein Beispiel. Wenn Sie sie vermeiden können, tun Sie das. Hier ist eine Möglichkeit, Ihre Tabellen neu zu gestalten:

zunächst ein UNIQUE KEY in Tabelle hinzufügen message auf (thread_id, message_id) (oder macht es die Primärschlüssel, wenn Lehre kann das tut das würde bedeuten - für MySQL- dass message(id) wäre nicht auto-. Inkrementiert, aber vom ORM erzeugt, möglicherweise nicht, wenn Sie Anwendungen haben möchten, die direkt oder über andere ORMs auf die Datenbank zugreifen.

Dann verschieben Sie die last_message_id in eine neue Tabelle, die eine 1-zu-1-Beziehung mit message hat, obwohl die Verbindung (thread_id, message_id). In dieser Tabelle wäre die thread_id Unique, also hat jeder Thread genau eine letzte Nachricht.

Ich werde den SQL-Code hier schreiben. Auf dieser Seite werden Sie mit der Lehre Code helfen, die etwas andere Struktur kann entstehen: Compound Primary and Foreign Keys

CREATE TABLE IF NOT EXISTS `thread` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    ---`last_message_id` int(11) DEFAULT NULL, --- REMOVED: last_message 
    `subject` varchar(255) NOT NULL 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 


CREATE TABLE IF NOT EXISTS `message` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `user_id` int(11) DEFAULT NULL, 
    `thread_id` int(11) NOT NULL,     --- why was it NULL ? 
    `body` longtext NOT NULL 
    PRIMARY KEY (`id`), 
    KEY `IDX_9E4E8B5FA76ED395` (`user_id`), 
    ---KEY `IDX_9E4E8B5FE2904019` (`thread_id`), --- REMOVED, not needed any more 
               --- because we have a this key 
    UNIQUE KEY (thread_id, id)     --- ADDED, needed for the FK below 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

ALTER TABLE `message` 
ADD CONSTRAINT `FK_9E4E8B5FE2904019` 
    FOREIGN KEY (`thread_id`) 
    REFERENCES `thread` (`id`) 
    ON DELETE CASCADE; 

Und die neue Tabelle, die letzte Nachricht für jeden Thread zu speichern:

CREATE TABLE IF NOT EXISTS `thread_last_message` (
    `message_id` int(11) NOT NULL, 
    `thread_id` int(11) NOT NULL,    
    PRIMARY KEY (`thread_id`), 
    KEY (`thread_id`, message_id`), 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

ALTER TABLE `thread_last_message`    --- which just means 
ADD CONSTRAINT `FK_something`     --- that every 
    FOREIGN KEY (`thread_id`, `message_id`)  --- thread's last message 
    REFERENCES `message` (`thread_id`, `id`)  --- is a message 
    ON DELETE CASCADE; 

Eine weitere Möglichkeit ist es, die thread(last_message_id) Spalte NULL zu haben und die FK Beschränkungen passend zu ändern (als @ Erics Vorschlag). Dies ist weniger aufwändig in der Designphase und Sie haben einen Tisch weniger zu bewältigen. Sie müssen bei dieser Vorgehensweise auf die Reihenfolge der Einfügungen und Löschungen achten - wie Ihr Beispiel zeigt.


Als dritte Option, haben Sie gedacht, wenn Sie wirklich eine thread(last_message_id) Spalte in der Tabelle müssen? Könnte dies nicht ein berechneter (aus den beiden Tabellen) Wert sein und Sie überspringen das ganze Problem? Wenn es ein best_message_id wäre, würde ich das verstehen, aber die letzte Nachricht ist nur die letzte Zeile in einer anderen Tabelle, geordnet nach Zeit. Sie können das bei einer Abfrage finden und müssen es (wieder) nicht in der Datenbank speichern, es sei denn, es gibt Leistungsgründe.

+0

Vielen Dank für die lange Antwort. Für die dritte Option habe ich bereits versucht, eine Abfrage zu erstellen, die Threads und ihre letzte Nachricht abrufen kann, aber ich habe Probleme, weil ich die Abfrage paginieren muss ... – Nanocom

0

Diese Lösung erfordert keine Änderung des Schemas, die Sie übrigens rückgängig machen müssen.

Wenn Sie einen Thread entfernen mögen, die Nachrichten auf diesem Thread auch nicht sinnvoll, so:

-- break one end of the mutual constraint 
update thread set last_message_id = NULL where id = <thread_id_to_delete>; 
delete from message where thread_id = <thread_id_to_delete> 
delete from threads where id = <thread_id_to_delete> 

(Haftungsausschluss: Ich habe nicht genau diesen Code testen, aber ein ähnlicher)