2017-07-15 3 views
1

ユーザ間の一致を記録するテーブルを作成したい。すべてのマッチは1v1なので、常に2人のユーザーが試合に出ています。そのうちの1人は勝者でなければならず、もう1人は敗者でなければなりません。postgresqlテーブル制約が不正な挿入を拒否しない

CREATE TABLE IF NOT EXISTS matches (
    match_id bigserial PRIMARY KEY, 
    user_1_id bigint NOT NULL REFERENCES users(user_id), 
    user_2_id bigint NOT NULL REFERENCES users(user_id), 
    winner_id bigint REFERENCES users(user_id), 
    loser_id bigint REFERENCES users(user_id), 
    tied boolean, 
    CONSTRAINT check_winner_loser_tied CHECK (
     (user_1_id != user_2_id) AND 
     (
      (winner_id = user_1_id AND loser_id = user_2_id AND tied = FALSE) OR 
      (winner_id = user_2_id AND loser_id = user_1_id AND tied = FALSE) OR 
      (tied = TRUE AND winner_id = NULL AND loser_id = NULL) 
     ) 
    ) 
); 

あなたが上見ることができるように、私はしかし、私はまだ、テーブルに例を無効なデータを挿入することができるよ、私は上記の条件を強制するためにテーブル制約を追加しました:

INSERT INTO matches (user_1_id, user_2_id, winner_id, loser_id, tied) 
VALUES (1, 2, 1, 2, TRUE); -- can't be winner & loser if it's a tie! 

さらに:

INSERT INTO matches (user_1_id, user_2_id, winner_id, loser_id, tied) 
VALUES (1, 2, NULL, NULL, FALSE); -- must be a winner & loser if no tie! 

私は間違っていますか?

さらに詳しい情報:SELECT version();PostgreSQL 9.6.3 on x86_64-pc-linux-gnu, compiled by gcc (Debian 4.9.2-10) 4.9.2, 64-bitを返します。

+1

で、この新しいものは 'winner_id = NULL'本当であってはなりません、winner_idではなくNULL' IS'てみてください。 – dnoeth

+0

@dnoethありがとう、それは第1のケース(勝者と敗者との同値=真)を解決するのに役立ちました!ただし、2番目のケースは引き続き可能です(tie = false、null winner&null loser)。 – kfedorov91

+0

2番目のケースで失敗する理由はわかりませんが、データモデルは少し複雑です。 'winner_id'、' loser_id'、 'tied'を3つの値(0,1,2)の単純な列に置き換えてみませんか?0 =タイ、1 =第1ユーザが勝ち、第2 =ユーザが勝った。 – dnoeth

答えて

1

あなたの制約が1間違い含まれています。あなたはSQLで

​​3210

すべての比較

(value = NULL) 

リターンNULLを使用する必要があるとき、あなたは

(winner_id = user_1_id AND loser_id = user_2_id AND tied = FALSE) OR 
(winner_id = user_2_id AND loser_id = user_1_id AND tied = FALSE) OR 
(tied = TRUE AND winner_id = NULL AND loser_id = NULL) 

使用している取扱NULLSを、三=演算子のための状態ロジック。

2状態ロジックのみを使用する場合は、IS NOT DISTINCT FROM比較述部を使用します。

は代替dbfiddleでhere


をすべてをチェックしてください:あなたは命名semantinc を使用し、それらを小さくするかどうかをチェックし、より有益です。あなたは、例えば、使用したい:

CONSTRAINT check_users_different 
    CHECK (user_1_id <> user_2_id), 
CONSTRAINT check_when_tied_no_winner_and_no_loser 
    CHECK (CASE WHEN tied 
       THEN winner_id IS NULL AND loser_id IS NULL 
       ELSE true 
      END), 
CONSTRAINT check_when_not_tied_winner_not_null 
    CHECK (CASE WHEN not tied 
       THEN winner_id IS NOT NULL 
       ELSE true 
      END), 
CONSTRAINT check_when_not_tied_loser_not_null 
    CHECK (CASE WHEN not tied 
       THEN loser_id IS NOT NULL 
       ELSE true 
      END), 
CONSTRAINT check_when_not_tied_one_user_wins_the_other_loses 
    CHECK (CASE WHEN not tied 
       THEN (user_1_id = winner_id AND user_2_id = loser_id) OR 
        (user_1_id = loser_id AND user_2_id = winner_id) 
       ELSE true 
      END) 

(私が知っている:これは、はるかに冗長であり、CASE WHENだけの論理和または論理積を使用することによって単純化することができ、私はそれはそれはそれが明確になることを次のように書いて見つけます。 )

そして、あなたが取得したい、より有益なエラー:

INSERT INTO matches (user_1_id, user_2_id, winner_id, loser_id, tied) 
VALUES (1, 2, 1, 2, TRUE); -- can't be winner & loser if it's a tie! 
 
ERROR: new row for relation "matches" violates check constraint "check_when_tied_no_winner_and_no_loser" 
DETAIL: Failing row contains (1, 1, 2, 1, 2, t). 
INSERT INTO matches (user_1_id, user_2_id, winner_id, loser_id, tied) 
VALUES (1, 2, NULL, NULL, FALSE); -- must be a winner & loser if no tie! 
 
ERROR: new row for relation "matches" violates check constraint "check_when_not_tied_loser_not_null" 
DETAIL: Failing row contains (2, 1, 2, null, null, f). 

チェックdbfiddle here

+0

Btw、NULLとの比較はNULLではなく「不明」を返します。 – dnoeth

+1

@dnoeth:[* Three-Value Logic *](https://en.wikipedia.org/wiki/Three-valued_logic)の 'unknown'はSQLでは' NULL'で表されます。 * dbfiddle [ここ](http://dbfiddle.uk/?rdbms=postgres_9.6&fiddle=c121f3dcb5885db6c7efb2e704aafb7c)*を確認してください。だから、今日の終わりには同等です;-) – joanolo

関連する問題