web-dev-qa-db-ja.com

複数のタイプで1対0または1の関係を強制するにはどうすればよいですか?

サッカーリーグを表すデータベース構造を作成していますが、解決方法がわからない部分があります。つまり、次のとおりです。

  • メンバーおよび彼は審判管理またはplayer(ただし、これらのタイプの1つのみ)。

状況を例示するために、次の図を含めます。

Visualization

タイプの属性は次のとおりです。

  • memberid(主キー)、namesurnameなど。

  • refereeid(主キー)、practiceleagueleagueへの外部キー)

  • managementid(主キー)、practiceclub_idclubへの外部キー)

  • playerid(主キー)、numberclub_idclubへの外部キー)

質問

このスキームでは、メンバー審判およびプレーヤーになることができます。同時に、メンバーは審判またはプレーヤーのみになることができるというルールを適用したいのですが、どうすればこの目標を達成できますか?

2
Gro

タイプを定義する属性をMEMBERに追加できます。例:

CREATE TABLE MEMBER
( MEMBER_ID INT NOT NULL PRIMARY KEY -- ID is to vague IMO
, MEMBER_TYPE CHAR(1) NOT NULL
, ...
, CONSTRAINT <name> CHECK (MEMBER_TYPE IN ('R','M','P'))
) ;

一部のDBMSはSELECTCHECK CONSTRAINTSをサポートしていると聞きました。そのため、タイプを検証するCHECK制約を各サブテーブルに追加できます。

それをサポートしていないDBMSの場合、MEMBERに一意の制約を追加できます。

ALTER TABLE MEMBER ADD CONSTRAINT <name> UNIQUE (MEMBER_ID, MEMBER_TYPE);

mEMBER_TYPE属性をサブテーブルに「継承」します。

ALTER TABLE REFEREE ADD COLUMN MEMBER_TYPE CHAR(1) DEFAULT 'R' NOT NULL;
ALTER TABLE REFEREE ADD CONSTRAINT <name> CHECK (MEMBER_TYPE = 'R');
ALTER TABLE REFEREE ADD CONSTRAINT <name> 
     FOREIGN KEY (MEMBER_ID, MEMBER_TYPE)
     REFERENCES MEMBER (MEMBER_ID, MEMBER_TYPE);

もう1つのアイデアは、挿入または更新する前に、メンバーが正しいタイプであることを検証する検証トリガーをサブテーブルに追加することです。

3
Lennart

この背後にある「ビジネスロジック」は、審判でもあるプレーヤー(またはマネージャー)がチームの不正行為を助ける可能性があるという利益相反であると思います。各メンバーを本当に1つのタイプにしたい場合は、メンバーテーブルに属性「memberType」を含めることができます。この属性は、「R」、「M」、または「P」の値を保持します(アイデアがわかります) 。

遅かれ早かれ、3つの「タイプ」テーブル(プレーヤーが参照またはマネージャーになる時期を考慮)のそれぞれに「StartDate」と「EndDate」が必要になり、移動の量に応じて、おそらく監査テーブルも必要になります。時間の経過とともに役割(およびクラブ)間で。しかし、あなたの質問に答えることから始めましょう:あなたのメンバーテーブルに 'memberType'を追加してください。アプリケーションやチェック制約により、ビジネスロジックを適用できます。

0
Cliff

SELECTを含むCHECK句を受け入れないDBの場合、別の異なる可能性があります。「審判」、「管理」、および「プレーヤー」テーブルにBEFOREINSERTまたはUPDATEトリガーを追加します。トリガーは、他の2つのテーブルに別のエントリが存在しないことを確認する必要があります。

これは例です(私はPostgreSQLを使用しています)。これは、定義した基本的なスキーマです。

CREATE schema football ;

CREATE TABLE football.member
(
    member_id integer PRIMARY KEY,
    name text,
    surname text
) ;

CREATE TABLE football.referee
(
    member_id integer PRIMARY KEY REFERENCES football.member(member_id),
    practice text,
    league text
) ;

CREATE TABLE football.management
(
    member_id integer PRIMARY KEY REFERENCES football.member(member_id),
    practice text,
    club_id integer /* REFERENCES club(club_id) */
) ;

CREATE TABLE football.player
(
    member_id integer PRIMARY KEY REFERENCES football.member(member_id),
    number integer,
    club_id integer /* REFERENCES club(club_id) */
) ;

これを変更せずに、「審判」、「管理」、「プレーヤー」の各テーブルに1つずつ、合計3つのトリガー関数を作成できます。

CREATE OR REPLACE FUNCTION football.check_referee_only() RETURNS trigger AS
$BODY$
BEGIN
    -- Check that referee is not a manager
    if EXISTS (SELECT * FROM football.management x 
                WHERE x.member_id = new.member_id) THEN
        RAISE EXCEPTION 'Member % is already a Manager. Cannot also be a Referee', new.member_id;
    END IF;

    -- Check that referee is not a player
    if EXISTS (SELECT * FROM football.player     x 
                WHERE x.member_id = new.member_id) THEN
        RAISE EXCEPTION 'Member % is already a Player. Cannot also be a Referee', new.member_id;
    END IF;

    -- Nothing wrong... just return
    RETURN new ;
END ;$BODY$
LANGUAGE plpgsql VOLATILE ;

-- You should also define equivalent two functions 
-- for the other two roles (player and manager) 

ここで、右側の各テーブルでINSERTまたはUPDATEが発生しようとしているときに起動する3つのトリガーを作成します。

CREATE TRIGGER trg_check_referee_only 
   BEFORE INSERT OR UPDATE OF member_id
   ON football.referee FOR EACH ROW
   EXECUTE PROCEDURE football.check_referee_only();

-- You should also define equivalent two triggers 
-- for the other two roles (player and manager) 

そして今、それが機能することを確認します:

-- We create one member
INSERT INTO football.member(member_id, name, surname) VALUES (1, 'John', 'Doe') ;

-- We make it a player...
INSERT INTO football.player(member_id, number, club_id) VALUES (1, 12, 12345) ;

-- If we try to make it a referee as well => an exception will occur
INSERT INTO football.referee(member_id, practice, league) VALUES (1, 'some practice', 'some league') ;

最後のステートメントを実行すると、次のエラーが発生します。

ERROR: Member 1 is already a Player. Cannot also be a Referee
SQL state: P0001
Context: PL/pgSQL function football.check_referee_only() line 10 at RAISE

データ表現を最初から設計している場合、これは最善の解決策ではないことにおそらく同意します。代わりに「member_type」列も使用するでしょう。

しかしながら。データ構造がすでに配置されている場合、それを変更することは実用的ではなく(たとえば、多くのコードを変更する必要があるため)、ルールを保証する必要がある場合は、これがその方法になります。また、他の履歴条件(@Cliffで指摘されている)を考慮し、x.member_id = new.member_idだけをチェックするのではなく、start_dateとend_date、または他の条件を考慮するように後で拡張することもできます。

このアプローチには別の利点もあります。後で4番目のテーブル(「ファン」と呼びましょう)を作成でき、「ファン」が「審判」になることを禁止しますが、「マネージャー」になることは禁止します。 "。今回は、「member_type」という列は、誰かが「ファン」であり「マネージャー」であるという事実を適切に表したものではありません。いずれの場合も、適切と思われる組み合わせを保証するようにトリガーを設計できます。

主な欠点:トリガーには特定の実行時ペナルティがあり、慎重にテストする必要があり、特定のデータベースに大きく依存します(たとえば、Oracleにとって良いことはSQLサーバーにとっては良くない場合があります)。

0
joanolo