次の列があります:standard BOOLEAN NOT NULL
1つの行をTrueに、その他の行をすべてFalseに強制したいと思います。この制約に応じて、FKやその他のものはありません。私はplpgsqlでそれを達成できることを知っていますが、これは大槌のようです。 CHECK
またはUNIQUE
制約のようなものを好みます。シンプルなほど良い。
1つの行はTrueでなければなりませんが、すべてFalseにすることはできません(したがって、最初に挿入された行はTrueである必要があります)。
行を更新する必要があります。つまり、すべての行が最初にFalseに設定され、その後に1つの行がTrueに設定される可能性があるため、更新が完了するまで制約を確認するのを待つ必要があります。
products.tax_rate_id
とtax_rate.id
の間にはFKがありますが、デフォルトまたは標準の税率とは何の関係もありません。これは、ユーザーが新しい製品を簡単に作成できるように選択できます。
重要な場合はPostgreSQL 9.5。
表は税率です。税率の1つがデフォルトです(デフォルトはPostgresコマンドであるため、standard
)。新しい商品が追加されると、標準の税率が商品に適用されます。 standard
がない場合、データベースは推測またはあらゆる種類の不要なチェックを実行する必要があります。単純な解決策は、standard
があることを確認することだと私は思った。
上記の「デフォルト」とは、プレゼンテーション層(UI)を意味します。デフォルトの税率を変更するためのユーザーオプションがあります。 GUI /ユーザーがtax_rate_idをNULLに設定しないようにするために追加のチェックを追加するか、デフォルトの税率を設定する必要があります。
必要なのはstandard = true
を含む単一の列だけなので、他のすべての行では標準をNULLに設定します。次に、NULL値はそれに違反しないため、単純なUNIQUE
制約が機能します。
CREATE TABLE taxrate (
taxrate int PRIMARY KEY
, standard bool DEFAULT true
, CONSTRAINT standard_true_or_null CHECK (standard) -- yes, that's the whole constraint
, CONSTRAINT standard_only_1_true UNIQUE (standard)
);
DEFAULT
は、入力した最初の行がデフォルトになることを示すオプションのリマインダーです。 enforcing何もしていません。複数の行をstandard = true
に設定することはできませんが、すべての行をNULLに設定することはできます。単一のテーブルにconstraintsのみを使用してこれを防ぐための明確な方法はありません。 CHECK
制約は、他の行を考慮しません(ダーティトリックなし)。
関連:
更新するには:
BEGIN;
UPDATE taxrate SET standard = NULL WHERE standard;
UPDATE taxrate SET standard = TRUE WHERE taxrate = 2;
COMMIT;
次のようなコマンドを許可するには(ステートメントの最後でのみ制約が満たされる場合):
WITH kingdead AS (
UPDATE taxrate
SET standard = NULL
WHERE standard
)
UPDATE taxrate
SET standard = TRUE
WHERE taxrate = 1;
.. UNIQUE
制約はDEFERRABLE
でなければなりません。見る:
dbfiddle ここ
次のような単一行を持つ2番目のテーブルがあります。
これをスーパーユーザーとして作成します。
CREATE TABLE taxrate (
taxrate int PRIMARY KEY
);
CREATE TABLE taxrate_standard (
taxrate int PRIMARY KEY REFERENCES taxrate
);
CREATE UNIQUE INDEX taxrate_standard_singleton ON taxrate_standard ((true)); -- singleton
REVOKE DELETE ON TABLE taxrate_standard FROM public; -- can't delete
INSERT INTO taxrate (taxrate) VALUES (42);
INSERT INTO taxrate_standard (taxrate) VALUES (42);
現在は常に標準を指す単一の行があります(この単純なケースでは、標準レートも直接表します)。スーパーユーザーだけがそれを壊すことができました。トリガーBEFORE DELETE
を使用して、これを禁止することもできます。
dbfiddle ここ
関連:
VIEW
を追加して、variant 1と同じように表示できます。
CREATE VIEW taxrate_combined AS
SELECT t.*, (ts.taxrate = t.taxrate) AS standard
FROM taxrate t
LEFT JOIN taxrate_standard ts USING (taxrate);
標準レートだけが必要なクエリでは、(のみ)taxrate_standard.taxrate
を直接使用します。
後で追加しました:
products.tax_rate_id
とtax_rate.id
の間にはFKがあります
バリアント2の貧乏人の実装は、標準税を指すproducts
(または同様のテーブル)に行を追加するだけです。割合; 「標準税率」と呼ぶかもしれないダミーの製品-セットアップで許可されている場合。
FK制約は、参照整合性を適用します。それを完了するには、行にtax_rate_id IS NOT NULL
を適用します(それが一般的に列に当てはまらない場合)。そして、その削除を禁止します。どちらもトリガーに入れることができます。余分なテーブルはありませんが、エレガントさが低く、信頼性が低くなります。
フィルターされたインデックス を使用できます
create table test ( id int primary key, foo bool );
CREATE UNIQUE INDEX only_one_row_with_column_true_uix ON test (foo) WHERE (foo); --> where foo is true
insert into test values (1, false); insert into test values (2, true); insert into test values (3, false); insert into test values (4, false); insert into test values (5, true);
エラー:重複するキー値が一意の制約「only_one_row_with_column_true_uix」に違反しています 詳細:キー(foo)=(t)はすでに存在します。
dbfiddle ここ
しかし、言ったように、最初の行はtrueでなければならないので、CHECK制約を使用できますが、関数を使用しても、最初の行を後で削除できます。
create function check_one_true(new_foo bool) returns int as $$ begin return ( select count(*) + (case new_foo when true then 1 else 0 end) from test where foo = true ); end $$ language plpgsql stable;
alter table test add constraint ck_one_true check(check_one_true(foo) = 1);
insert into test values (1, true); insert into test values (2, false); insert into test values (3, false); insert into test values (4, false);
insert into test values (5, true);
エラー:リレーション「test」の新しい行がチェック制約「ck_one_true」に違反しています 詳細:失敗した行には(5、t)が含まれています。
select * from test;
id | foo -:| :- 1 | t 2 | f 3 | f 4 | f
delete from test where id = 1;
dbfiddle ここ
これを解決するには、BEFORE DELETEトリガーを追加して、最初の行(fooがtrue)が削除されないようにします。
create function dont_delete_foo_true() returns trigger as $x$ begin if old.foo then raise exception 'Can''t delete row where foo is true.'; end if; return old; end; $x$ language plpgsql;
create trigger trg_test_delete before delete on test for each row execute procedure dont_delete_foo_true();
delete from test where id = 1;
エラー:fooがtrueの行は削除できません。
dbfiddle ここ