テーブルがあり、デフォルト値なしで新しい列を追加する必要があります。
ALTER TABLE integrations.billables
DROP CONSTRAINT IF EXISTS cc_at_least_one_mapping_needed_billables,
ADD CONSTRAINT cc_at_least_one_mapping_needed_billables
CHECK ((("qb_id" IS NOT NULL) :: INTEGER +
("xero_id" IS NOT NULL) :: INTEGER +
("freshbooks_id" IS NOT NULL) :: INTEGER +
("unleashed_id" IS NOT NULL) :: INTEGER +
("csv_data" IS NOT NULL) :: INTEGER +
("myob_id" IS NOT NULL) :: INTEGER) > 0);
ALTER TABLE integrations.billables
DROP COLUMN IF EXISTS myob_id,
ADD COLUMN myob_id varchar(255);
すでにそこにある値ではなく、次の値の制約を追加するにはどうすればよいですか? (そうしないと、エラーチェック制約「」が一部の行に違反します)。
これは私の前の質問に関連しています: エラー:チェック制約が一部の行に違反しています
すべての既存の行を古いものとしてマークします。
ALTER TABLE integrations.billables
ADD COLUMN is_old BOOLEAN NOT NULL DEFAULT false;
UPDATE integrations.billables SET is_old = true;
そして、古い行を無視するように制約を設定します。
ALTER TABLE integrations.billables
ADD CONSTRAINT cc_at_least_one_mapping_needed_billables
CHECK (
NOT(("qb_id", "xero_id", "freshbooks_id", "unleashed_id", "csv_data", "myob_id") IS NULL)
OR is_old
);
(はい、そのIS NULL
チェックは機能します。 ここ を参照してください。)
このメカニズムの利点:
欠点:
boolean
列またはその他のフープジャンプを追加する必要があります。is_old
フラグをtrue
に変更するだけなので、これは悪用される可能性があります。 (ただし、これには対処できます。以下を参照してください。)これは、エンドユーザーがデータベースに直接アクセスできず、開発者がデータに奇妙なことをしないように信頼できる場合、心配する必要はありません。誰かがフラグを変更することを心配しているare場合は、トリガーを設定して、挿入または更新によってis_old
がtrue
に設定されないようにすることができます。
CREATE FUNCTION throw_error_on_illegal_old()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.is_old THEN
-- Need to make sure we don't try to access
-- OLD in an INSERT
IF TG_OP = 'INSERT' THEN
RAISE 'Cannot create new with is_old = true';
ELSE
IF NOT OLD.is_old THEN
RAISE 'Cannot change is_old from false to true';
END IF;
END IF;
END IF;
-- If we get here, all tests passed
RETURN NEW;
END
$$
;
CREATE TRIGGER billables_prohibit_marking_row_old
BEFORE INSERT OR UPDATE ON integrations.billables
FOR EACH ROW EXECUTE PROCEDURE throw_error_on_illegal_old()
;
データベーススキーマを変更できる人が来てトリガーや何かを削除することは誰もないことを信頼する必要がありますが、そうする場合は制約も削除する可能性があります。
これが SQLFiddleデモ です。 「should skip」行は出力に含まれていないことに注意してください(希望どおり)。
serial
列または整数列にnextval
が自動的に入力される場合(これにより、決してその列の明示的な値)、その列の値が特定の値より大きいかどうかをさらに確認できます。
(
(("qb_id" IS NOT NULL) :: INTEGER +
("xero_id" IS NOT NULL) :: INTEGER +
("freshbooks_id" IS NOT NULL) :: INTEGER +
("unleashed_id" IS NOT NULL) :: INTEGER +
("csv_data" IS NOT NULL) :: INTEGER +
("myob_id" IS NOT NULL) :: INTEGER) > 0
OR
YourSerialColumn <= value
)
ここでvalue
は、currval
制約の変更/再作成時の列/対応するシーケンス。
このようにIS NOT NULL
チェックは、YourSerialColumn
値がvalue
より大きい行にのみ適用されます。
注
これは David Spilletの提案 のバリエーションと見なすことができます。主な違いは、このソリューションでは構造の変更(パーティション化)が必要ないという事実にあります。ただし、どちらのオプションも、テーブル内の適切な列の存在に依存しています。そのような列がなく、この問題を解決するためにを追加できる場合は、パーティション化のアイデアを採用することが、これら2つの方法のより良いオプションである可能性があります。
制約をNOT VALID
として追加するだけです
制約に
NOT VALID
のマークが付けられている場合、テーブル内のすべての行が制約を満たすことを確認するために時間がかかる可能性のある初期チェックがスキップされます。制約は引き続き後続の挿入または更新に対して適用されます(つまり、[...]であり、新しい行が指定されたチェック制約と一致しない限り、制約は失敗します)
これはバージョン9.1までのPostgresでは不可能でした。 9.2以降では、チェック制約をNOT VALID
(MS SQL ServerのWITH NOCHECK
と同等)として定義できます。詳細は http://www.postgresql.org/docs/9.2/static/sql-altertable.html を参照してください。
私は、この種の事態を回避することが可能な場合、一般的に不満です。適切なパーティションキー(たとえば、入力フィールドの日付)がある場合の妥協点は、おそらくテーブルをパーティション化して、新しい行を含むパーティションにNOT NULL
制約のみを適用できることです。どちらの場合も、テーブルのレコードの特定のサブセットでNULL値が予期される可能性があることを将来のDBA /開発者/その他が知るように、設計の選択が十分に文書化されていることを確認してください。
CHECK
制約ははるかに簡単です。
ALTER TABLE billables
ADD CONSTRAINT cc_at_least_one_mapping_needed_billables
CHECK (qb_id IS NOT NULL OR
xero_id IS NOT NULL OR
freshbooks_id IS NOT NULL OR
unleashed_id IS NOT NULL OR
csv_data IS NOT NULL OR
myob_id IS NOT NULL) NOT VALID;
または単に:
CONSTRAINT cc_at_least_one_mapping_needed_billables
CHECK (NOT (qb_id,xero_id,freshbooks_id,unleashed_id,csv_data,myob_id) IS NULL) NOT VALID;
なぜそれが機能するのですか?
私はすでに NOT VALID
句を追加しました @ a_horseが言及しました 。このように、制約は新しく追加された行にのみ適用されます。また、可能なダンプ/復元サイクルを考慮する必要があります。詳細:
そして、あなたはそれを単一のコマンドですべて行うことができ、これは最速であり、可能な同時トランザクションが何か間違ったことをするのを防ぎます:
ALTER TABLE integrations.billables
DROP CONSTRAINT IF EXISTS cc_at_least_one_mapping_needed_billables
, ADD COLUMN myob_id varchar(255)
, ADD CONSTRAINT cc_at_least_one_mapping_needed_billables
CHECK (NOT (qb_id,xero_id, freshbooks_id,unleashed_id, csv_data, myob_id) IS NULL)
NOT VALID;
補足1:新しいmyob_id
がなくても、同じ列セットにCHECK
制約がすでにある場合、既存のすべての行がmyob_id
とともに新しいCHECK
制約も渡すため、問題はありません。
補足2:一部のRDBMSでは、パフォーマンスを最適化するためにvarchar(255)
を使用することが理にかなっています。長さ修飾子が実際に長さを最大255に制限する必要がある場合にのみ意味があるので、これはPostgresと255には無関係です。