Ответ 1
Переместите "BetName" в таблицу "play_in".
TABLE plays_in
BetName
PlayerID
PRIMARY KEY(BetName, PlayerID)
FOREIGN KEY PlayerID
Затем отбросьте таблицу "Ставка".
Я начинаю разрабатывать приложение, в котором пользователь может создавать ставки. Учитывая следующую схему
TABLE Player
PlayerID PRIMARY KEY
PlayerName
(...)
TABLE Bet
BetID PRIMARY KEY
BetName
(...)
TABLE plays_in
BetID
PlayerID
PRIMARY KEY(BetID, PlayerID)
FOREIGN KEY BetID
FOREIGN KEY PlayerID
Можно ли каким-либо образом определить уникальность для (BetName, PlayerID), чтобы ставка могла иметь одно и то же имя несколько раз, но только один раз для игрока? Это означает, что определенный игрок может участвовать только один раз в ставке с именем "MyFirstBet"? Я не хочу определять имя ставки в качестве первичного ключа или уникального, потому что любой другой игрок, который не будет играть эту ставку с игроком выше, должен иметь возможность одновременно называть свою ставку "MyFirstBet". Если это возможно, я также хочу избежать создания дополнительной таблицы. Является ли это проблемой для решения в коде, а не в СУБД?
Переместите "BetName" в таблицу "play_in".
TABLE plays_in
BetName
PlayerID
PRIMARY KEY(BetName, PlayerID)
FOREIGN KEY PlayerID
Затем отбросьте таблицу "Ставка".
Эта модель не поддерживается стандартным SQL - эта проблема заключается в том, что BetName не является ключом и не рассматривает (в настоящее время) часть ключа кандидата.
Одним из способов решения этой проблемы, поддерживающей отношения, и обеспечения ссылочной целостности, является добавление столбца BetName в PlaysIn, а затем наличие PlayIn FK (BetId, BetName), так что есть FK над Ключом-кандидатом, а не только Суррогатная ПК. Затем добавьте PlaysIn UX (BetName, PlayerId), чтобы обеспечить уникальное имя/плеер. В принципе, он ограничивает отношения с помощью суррогата и соответствующего составного ключа. Это нехорошо, потому что нет "дублированных данных" (вне Compound PK), используемых для RI.
TABLE Bet
PK BetID
BetName
TABLE PlaysIn
BetID
PlayerID
BetName -- must set
PK (BetID, PlayerID)
FK Bet(BetID, BetName)
UX (PlayerID, BetName)
Другой подход, который я рекомендую, хотя он и меняет отношения, заключается в том, чтобы переместить PlayerId из PlaysIn и сохранить его с помощью ставки. Затем PlaysIn → Bet → Player. UX также можно было бы повысить до ПК, а BetID можно было бы отбросить, что сделало бы его похожим на выше.
TABLE Player
PK PlayerID
PlayerName
TABLE Bet
-- Note: If PlaysIn needs PlayerID as well, use PK(PlayerId, BetName)
-- and adjust the FK in PlaysIn
PK BetID
PlayerID
BetName
UX (PlayerID, BetName)
TABLE PlaysIn
PK PlayID -- If you're gonna use Surrogates, be consistent
BetID
FK (BetID) -- Access to Player via Bet
-- other things for a "Play"
Конечно, TRIGGERS могут "делать все", но не представлены непосредственно в отношениях. Код также может быть дотошным при вставках/обновлениях - если вы доверяете DAL.
Я бы рассмотрел возможность изменения модели в соответствии с 2-м подходом.
Изменить: Как было предложено LoztInSpact, это решение действительно не работает. Проблема заключается в транзакциях, а триггер не видит немедленных изменений. См. здесь.
Попробуйте триггеры базы данных. Вы можете проверить свое состояние, а затем либо подать сигнал об ошибке, либо изменить вставленные значения на NULL или какое-то специальное значение.
CREATE TRIGGER check_bet_name BEFORE INSERT ON plays_in
FOR EACH ROW BEGIN
DECLARE bet_exists INT DEFAULT 0;
DECLARE msg VARCHAR(255);
SELECT 1 INTO bet_exists
FROM Bet AS b1
WHERE b1.BetID = NEW.BetID
AND EXISTS (SELECT *
FROM plays_in AS p JOIN Bet AS b2 USING (BetID)
WHERE p.PlayerID = NEW.PlayerID AND b2.BetName = b1.BetName
)
LIMIT 1;
IF bet_exists THEN
SET msg = "Bet name already exists...";
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
END IF;
END//
См. также этот ответ.