Круговые пути в ограничениях FOREIGN KEY
трудны для решения, и ваша проблема является примером. Если вы можете избежать их, сделайте это. Вот один из способов переделать ваши столы:
Сначала добавьте UNIQUE KEY
в таблицу message
на (thread_id, message_id)
(или сделайте его Первичным ключом, если Doctrine может это сделать. Для MySQL это будет означать, что message(id)
не будет автоматически увеличено) но произведенный ORM. Вы можете не захотеть этого, если планируете иметь приложения, которые обращаются к базе данных напрямую или через другие ORM).
Затем переместите last_message_id
в новую таблицу, которая имеет отношение 1 к 1 с message
, хотя составная (thread_id, message_id)
. В этой таблице thread_id
будет уникальным, поэтому у каждого потока будет только одно последнее сообщение.
Я напишу код SQL здесь. Эта страница поможет вам с кодом Doctrine, который может иметь несколько иную структуру: Составные первичные и внешние ключи
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;
И новая таблица, для хранения последнего сообщения для каждой темы:
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;
Другая возможность состоит в том, чтобы иметь столбец thread(last_message_id)
NULL
и соответствующим образом изменять ограничения FK (как предложение @ Eric). Это менее суетно на этапе проектирования, и вам приходится иметь дело с одним столом меньше. В этом подходе вы должны быть осторожны с порядком вставок и удалений - как показывает ваш пример.
Как третий вариант, подумали ли вы, действительно ли вам нужен столбец thread(last_message_id)
в вашей таблице? Не может ли это быть вычисленное (из двух таблиц) значение, и вы пропустите всю проблему? Если бы это было best_message_id
, я бы это понял, но последнее сообщение - это только последняя строка в другой таблице, упорядоченная по времени. Вы можете найти это с помощью запроса, и вам не нужно сохранять его (снова) в базе данных, если нет причин для повышения производительности.