sql >> データベース >  >> RDS >> PostgreSQL

PostgreSQLのチェック制約を理解する

    データの管理は大きな課題です。私たちの世界が変わるにつれ、データは広範で、豊富で、集中的であり続けています。したがって、流入に対処するための対策を講じる必要があります。

    すべてのデータを手動で検証する' '24時間体制は単に非現実的です。なんて素晴らしい夢でしょう。しかし、結局のところ、それはそれだけです。夢。悪いデータは悪いデータです。どのようにスライスしたり、さいの目に切ったりしても(しゃれが意図されています)。それは最初から問題であり、さらに多くの問題につながります。

    最新のデータベースは、重労働の多くを処理します 私たちのために。多くは、この特定のデータ領域の管理を支援する組み込みソリューションを提供します。

    テーブルの列に入力されるデータを制御する確実な方法は、データ型を使用することです。合計桁数が4で、10進数の後に2桁ある、10進数の列が必要ですか?

    確実なこと!まったく問題ありません。

    実行可能なオプションであるNUMERIC(4,2)は、ウォッチドッグのようにその列を保護しています。文字のテキスト値はそこに滑り込むことができますか?雪だるまのチャンスではありません。

    PostgreSQLは多数のデータ型を提供します。おそらく、あなたのニーズを満たすためにすでに存在しているのです。そうでない場合は、独自に作成できます。 (参照:PostgreSQL CREATE TYPE)

    しかし、データ型だけでは十分ではありません。最も具体的な要件がカバーされ、そのような広範な構造に準拠していることを保証することはできません。スキーマを設計するときは、通常、コンプライアンスルールとある種の「標準」が必要です。

    同じNUMERIC(4,2)列で、25.25より大きく74.33より小さい値のみが必要だとしますか?値88.22が格納されている場合、データ型に問題はありません。合計4桁、小数点以下2桁までを許可することで、その役割を果たしています。他の場所に責任を負わせてください。

    データベースで許可されているデータを制御することになると、この面でどのように勝ちますか?データの一貫性は最優先事項であり、あらゆるサウンドデータソリューションに不可欠です。収集されたデータをその発信元の開始から制御した(オフの)チャンスでは、一貫性はそれほど問題にならない可能性があります。

    しかし、完璧な世界は、私が読むのが大好きな多くのファンタジー小説の1つにしか存在しません。

    残念ながら、不完全で一貫性のない「ダーティ」なデータはすべて、データベース中心のフィールドに存在する一般的な特性と現実です。

    ただし、これらの問題を軽減するためのチェック制約があるため、すべてが悲惨な状況で失われるわけではありません。これらの特定のルールについては、必要に応じて、一貫性のあるデータのみを処理および保存できるようにする必要があります。データベースでこれらの仕様を義務付けることにより、一貫性のないデータがビジネス目標と今後のソリューションに与える影響を最小限に抑えることができます。

    制約とは何ですか? -高レベルの定義

    このコンテキストでは、制約はデータベーステーブルの列に設定されるルールまたは制限の一種です。この特異性により、受信するデータは、保存される前に設定された要件に準拠している必要があります。上記の要件は、ビジネスルールとして「専門的に」造られたものになる傾向があります(多くの場合、そうです)。 。これは、真実の検証ブールテストに要約されます。データが合格(true)の場合、データは保存されます。そうでない場合、エントリはありません(false)。

    PostgreSQLで利用可能な制約

    これを書いている時点で、PostgreSQLのドキュメントには6つのカテゴリの制約がリストされています。

    それらは:

    • チェック制約
    • 非ヌル制約
    • 固有の制約
    • 主キー
    • 外部キー
    • 除外の制約

    チェック制約

    INTEGER列の簡単な例は、たとえば100より大きい値を許可しないことです。

    learning=> CREATE TABLE no_go(id INTEGER CHECK (id < 100));
    CREATE TABLE
    learning=> INSERT INTO no_go(id) VALUES(101);
    ERROR: new row for relation "no_go" violates check constraint "no_go_id_check"
    DETAIL: Failing row contains (101).

    上記のように、チェック制約に違反する値を挿入しようとすると失敗します。

    チェック制約は、INSERT中に列を監視するだけでなく、UPDATEステートメント(および\ copyやCOPYなどの他のステートメント)も制限に準拠する必要があります。

    no_goテーブルに次の値があるとします。

    learning=> TABLE no_go;
    id 
    ----
    55
    (1 row)

    id列の値をCheck制約に準拠していない値に更新することも失敗します:

    learning=> UPDATE no_go SET id = 155
    learning-> WHERE id = 55;
    ERROR: new row for relation "no_go" violates check constraint "no_go_id_check"
    DETAIL: Failing row contains (155).

    チェック制約は、ターゲット列のデータ型に対して「意味をなす」必要があります。 INTEGER列を制約して、データ型自体ではテキスト値の格納が許可されないため、テキスト値の格納を禁止することは無効です。

    テーブルの作成中にそのタイプのチェック制約を課そうとするこの例を参照してください:

    learning=> CREATE TABLE num_try(id INTEGER CHECK(id IN ('Bubble', 'YoYo', 'Jack-In-The-Box')));
    ERROR: invalid input syntax for integer: "Bubble"

    チェック制約のない生活

    私の心に響くと聞いた古いことわざは、「井戸が乾くまで水を逃さない」です。 。 "

    チェック制約がなければ、私たちは確かにそれらの顕著な利点のために関連付けることができます。あなたがそれらなしでやらなければならないときに最も感謝されます。

    この例を見てください…

    まず、このテーブルとトレイルサーフェスマテリアルを表すデータがあります:

    learning=> SELECT * FROM surface_material;
    surface_id | material 
    ------------+--------------
    101 | Gravel
    202 | Grass
    303 | Dirt
    404 | Turf
    505 | Concrete
    606 | Asphalt
    707 | Clay
    808 | Polyurethane
    (8 rows)

    そして、トレイル名と独自のsurface_idを持つこのテーブル:

    learning=> SELECT * FROM trails;
    id | name | surface_id 
    ----+-----------------+------------
    1 | Dusty Storm | 303
    2 | Runners Trip | 808
    3 | Pea Gravel Pass | 101
    4 | Back 40 Loop | 404
    (4 rows)

    テーブルトレイルには、テーブルsurface_materialの対応する値のsurface_idのみが含まれるようにします。

    はいはい、私は知っています。あなたは私に向かって叫んでいます。

    これは処理できません 外部キー?!? "

    はい、できます。しかし、私はそれを一般的な使用法を示すために使用しており、知っておくべき落とし穴もあります(投稿の後半で説明します)。

    チェック制約がない場合、TRIGGERを使用して、一貫性のない値が格納されるのを防ぐことができます。

    これは大まかな(しかし機能している)例です:

    CREATE OR REPLACE FUNCTION check_me()
    RETURNS TRIGGER AS
    $$
    BEGIN
    IF NEW.surface_id NOT IN (SELECT surface_id FROM surface_material)
    THEN Raise Exception '% is not allowed for surface id', NEW.surface_id;
    END IF;
    RETURN NEW;
    END;
    $$ LANGUAGE PLpgSQL;
    CREATE TRIGGER check_me BEFORE INSERT OR UPDATE ON trails
    FOR EACH ROW EXECUTE PROCEDURE check_me();

    テーブルトレイルに対応するsurface_idがない値を挿入しようとすると、失敗します:

    learning=> INSERT INTO trails(name, surface_id)
    learning-> VALUES ('Tennis Walker', 110);
    ERROR: 110 is not allowed for surface id
    CONTEXT: PL/pgSQL function check_me() line 4 at RAISE

    以下のクエリ結果は、'問題を確認します '値は保存されませんでした:

    learning=> SELECT * FROM trails;
    id | name | surface_id 
    ----+-----------------+------------
    1 | Dusty Storm | 303
    2 | Runners Trip | 808
    3 | Pea Gravel Pass | 101
    4 | Back 40 Loop | 404
    (4 rows)

    それは確かに不要な値を禁止するための多くの作業です。

    チェック制約を使用してこの要件を再実装しましょう。

    実際のチェック制約定義ではサブクエリを使用できないため(これが上記の例を使用した理由です)、値はハードコードされている必要があります。 。

    小さなテーブル、またはここに示すような簡単な例の場合、これは問題ありません。他のシナリオでは、より多くの値を組み込むと、代替ソリューションを探すのに役立つ場合があります。

    learning=> ALTER TABLE trails ADD CONSTRAINT t_check CHECK (surface_id IN (101, 202, 303, 404, 505, 606, 707, 808));
    ALTER TABLE

    ここでは、チェック制約にt_checkという名前を付けましたが、システムに名前を付けさせました。

    注:以前に定義された check_me()FUNCTIONおよび付随する 以下を実行する前に、トリガーがドロップされました(表示されていません)。 挿入。)

    learning=> INSERT INTO trails(name, surface_id)
    VALUES('Tennis Walker', 110);
    ERROR: new row for relation "trails" violates check constraint "t_check"
    DETAIL: Failing row contains (7, Tennis Walker, 110).

    それがどれほど簡単だったか見ていただけませんか!トリガーと機能は必要ありません。

    チェック制約により、このタイプの作業が簡単になります。

    チェック制約定義を巧妙にしたいですか?

    できます。

    敏感な足首と膝を持っている人に少し優しいトレイルをリストしたテーブルが必要だとします。ここでは硬い表面は必要ありません。

    表nice_trailにリストされているハイキングトレイルまたはトラックの表面素材が「砂利」または「汚れ」であることを確認する必要があります。

    このチェック制約は、その要件を問題なく処理します:

    learning=> CREATE TABLE nice_trail(id SERIAL PRIMARY KEY,
    learning(> name TEXT, mat_surface_id INTEGER CONSTRAINT better_surface CHECK(id IN (101, 303))); 
    CREATE TABLE

    それは絶対にうまくいきます。

    しかし、チェックを機能させるために必要な両方のIDを返す関数はどうでしょうか。チェック制約定義でFUNCTIONは許可されていますか?

    はい、組み込むことができます。

    これが実際の例です。

    まず、関数本体と定義:

    CREATE OR REPLACE FUNCTION easy_hike(id INTEGER)
    RETURNS BOOLEAN AS
    $$
    BEGIN
    IF id IN (SELECT surface_id FROM surface_material WHERE material IN ('Gravel', 'Dirt'))
    THEN RETURN true;
    ELSE RETURN false;
    END IF;
    END;
    $$ LANGUAGE PLpgSQL;

    このCREATETABLEステートメントで、チェック制約を'テーブルで定義していることに注意してください。 'レベルですが、以前は'でのみ例を提供していました 'レベル。

    テーブルレベルで定義されたチェック制約が完全に有効です:

    learning=> CREATE TABLE nice_trail(nt_id SERIAL PRIMARY KEY,
    learning(> name TEXT, mat_surface_id INTEGER,
    learning(> CONSTRAINT better_surface_check CHECK(easy_hike(mat_surface_id)));
    CREATE TABLE

    これらのインサートはすべて優れています:

    learning=> INSERT INTO nice_trail(name, mat_surface_id)
    learning-> VALUES ('Smooth Rock Loop', 101), ('High Water Bluff', 303);
    INSERT 0 2

    これで、列mat_surface_idの制限を満たさないトレイルのINSERTが実行されます:

    learning=> INSERT INTO nice_trail(name, mat_surface_id)
    learning-> VALUES('South Branch Fork', 404);
    ERROR: new row for relation "nice_trail" violates check constraint "better_surface_check"
    DETAIL: Failing row contains (3, South Branch Fork, 404).

    チェック制約定義のFUNCTION呼び出しは設計どおりに機能し、不要な列の値を制限します。

    煙と鏡?

    チェック制約で見られるようにすべてですか?すべて白黒?前もってファサードはありませんか?

    注目に値する例。

    存在する唯一のINTEGER列のDEFAULT値を10にする単純なテーブルがあります:

    learning=> CREATE TABLE surprise(id INTEGER DEFAULT 10, CHECK (id <> 10));
    CREATE TABLE

    ただし、idをその数と等しくすることはできないと定義することにより、値10を禁止するチェック制約も含めました。

    その日はどちらが勝ちますか? DEFAULTまたはCheck制約?

    それがどれであるかを知って驚くかもしれません。

    私はそうだった。

    任意のINSERT、正常に動作:

    learning=> INSERT INTO surprise(id) VALUES(17);
    INSERT 0 1
    learning=> SELECT * FROM surprise;
    id 
    ----
    17
    (1 row)

    そして、DEFAULT値を持つINSERT:

    learning=> INSERT INTO surprise(id) VALUES(DEFAULT);
    ERROR: new row for relation "surprise" violates check constraint "surpise_id_check"
    DETAIL: Failing row contains (10).

    おっと...

    繰り返しますが、別の構文で:

    learning=> INSERT INTO surprise DEFAULT VALUES;
    ERROR: new row for relation "surprise" violates check constraint "surpise_id_check"
    DETAIL: Failing row contains (10).

    チェック制約はデフォルト値よりも優先されます。

    奇妙な例

    チェック制約は、作成中にテーブル定義のほぼどこにでも表示される可能性があります。列レベルでも、チェックにまったく関係のない列に設定できます。

    説明する例を次に示します。

    learning=> CREATE TABLE mystery(id_1 INTEGER CHECK(id_2 > id_3),
    learning(> id_2 INTEGER, id_3 INTEGER);
    CREATE TABLE

    制約をテストするためのINSERT:

    learning=> INSERT INTO mystery(id_1, id_2, id_3) VALUES (1, 2, 3);
    ERROR: new row for relation "mystery" violates check constraint "mystery_check"
    DETAIL: Failing row contains (1, 2, 3).

    意図したとおりに機能します。

    検証および無効

    この単純なテーブルとデータがあります:

    learning=> CREATE TABLE v_check(id INTEGER);
    CREATE TABLE
    learning=> INSERT INTO v_check SELECT * FROM generate_series(1, 425);
    INSERT 0 425

    ここで、50未満の値を禁止するチェック制約を実装する必要があるとします。

    これが本番環境の大きなテーブルであり、ALTER TABLEステートメントの結果として、現時点で取得したロックを実際に購入する余裕がないことを想像してみてください。ただし、前進するために、この制約を設定する必要があります。

    ALTER TABLEはロックを取得します(それぞれの異なるサブフォームに依存します)。前述のように、このテーブルは本番環境にあるため、「ピーク時間」が終了するまで待ちます。 '。

    チェック制約を作成するときに、NOVALIDオプションを使用できます。

    learning=> ALTER TABLE v_check ADD CONSTRAINT fifty_chk CHECK(id > 50) NOT VALID; 
    ALTER TABLE
    今日のホワイトペーパーをダウンロードするClusterControlを使用したPostgreSQLの管理と自動化PostgreSQLの導入、監視、管理、スケーリングを行うために知っておくべきことについて学ぶホワイトペーパーをダウンロードする

    チェック制約に違反するINSERTまたはUPDATEを試行した場合は、操作を続行します。

    learning=> INSERT INTO v_check(id) VALUES(22);
    ERROR: new row for relation "v_check" violates check constraint "fifty_chk"
    DETAIL: Failing row contains (22).

    「問題のある」列の値は禁止されています。

    次に、ダウンタイム中に、チェック制約を検証して、違反している可能性のある(任意の)既存の列に対して適用します。

    learning=> ALTER TABLE v_check VALIDATE CONSTRAINT fifty_chk;
    ERROR: check constraint "fifty_chk" is violated by some row

    私の意見では、メッセージはかなり不可解です。ただし、制約に準拠していない行があることは通知されます。

    ALTER TABLEのドキュメント(引用符で囲まれたドキュメントから直接冗長化)から含めたい重要なポイントは次のとおりです。

    • 構文:ADD table_constraint [NOT VALID]-付随する説明(部分的) "このフォームは、CREATE TABLEと同じ構文に加えて、現在外部キーに対してのみ許可されているオプションNOT VALIDを使用して、テーブルに新しい制約を追加します。制約をチェックします。制約が[無効]とマークされている場合、テーブル内のすべての行が制約を満たしていることを確認するための、潜在的に長い初期チェックはスキップされます。 "
    • 構文:VALIDATE CONSTRAINTConstraint_name-付随する説明(部分的) "このフォームは、テーブルをスキャンして制約が満たされていない行がないことを確認することにより、以前にNOTVALIDとして作成された外部キーまたはチェック制約を検証します。 「」 「検証では、変更されるテーブルのSHAREUPDATEEXCLUSIVEロックのみが取得されます。」

    余談ですが、その過程で学んだことは注目に値する2つのポイントです。セットを返す関数とサブクエリは、チェック制約定義では許可されていません。他にもあると確信しており、以下のコメントでそれらに関するフィードバックを歓迎します。

    チェック制約は素晴らしいです。 PostgreSQLデータベース自体が提供する「組み込み」ソリューションを使用して、データ制限を適用することは完全に理にかなっています。必要な列のチェック制約の実装に費やされる時間と労力は、まったく実装しないことをはるかに上回ります。したがって、長期的には時間を節約できます。これらの種類の要件を処理するためにデータベースに頼るほど、より良い結果が得られます。データベース管理の他の領域/側面にリソースを集中して適用できるようにします。

    読んでいただきありがとうございます。


    1. ExcelVBAを使用してSQLクエリを実行する

    2. 同じ値のブロックに属する値を分離せずに、いくつかの一意の値を取得します

    3. OracleストアドプロシージャからWebサービスにアクセスする

    4. JSONサブコンポーネントを抽出するための演算子