MySQL: определение уникальности по нескольким таблицам

Я начинаю разрабатывать приложение, в котором пользователь может создавать ставки. Учитывая следующую схему

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". Если это возможно, я также хочу избежать создания дополнительной таблицы. Является ли это проблемой для решения в коде, а не в СУБД?

Ответ 1

Переместите "BetName" в таблицу "play_in".

TABLE plays_in
  BetName
  PlayerID
  PRIMARY KEY(BetName, PlayerID)
  FOREIGN KEY PlayerID

Затем отбросьте таблицу "Ставка".

Ответ 2

Эта модель не поддерживается стандартным 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-м подходом.

Ответ 3

Изменить: Как было предложено 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//

См. также этот ответ.