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

PostgreSQL9.3を使用してCTEUPERTでデフォルト値を生成する

    Postgres9.5で実装されたUPSERT 。以下を参照してください。

    Postgres9.4以前

    これは難しい問題です。この制限に直面しています(ドキュメントごと):

    VALUESINSERTのトップレベルに表示されるリスト 、式はDEFAULTに置き換えることができます destinationcolumnのデフォルト値を挿入する必要があることを示します。 DEFAULT VALUESの場合は使用できません 他のコンテキストで表示されます。

    大胆な強調鉱山。デフォルト値は、挿入するテーブルがないと定義されません。したがって、直接はありません あなたの質問に対する解決策ですが、正確な要件に応じて、いくつかの可能な代替ルートがあります 。

    システムカタログからデフォルト値を取得しますか?

    あなたはできた システムカタログからそれらをフェッチしますpg_attrdef @Patrickがコメントしたように、またはinformation_schema.columnsから 。ここで完全な手順:

    • Postgresのテーブル列のデフォルト値を取得しますか?

    しかし、あなたはまだ のリストのみがあります デフォルト値をクックするための式のテキスト表現を使用します。使用する値を取得するには、ステートメントを動的に作成して実行する必要があります。退屈で厄介です。代わりに、組み込みのPostgres機能にそれを行わせることができます

    簡単なショートカット

    ダミーの行を挿入し、生成されたデフォルトを使用するように戻します:

    INSERT INTO playlist_items DEFAULT VALUES RETURNING *;
    

    問題/ソリューションの範囲

    • これは、 STABLEでのみ機能することが保証されています またはIMMUTABLE デフォルトの式 。ほとんどのVOLATILE 関数も同様に機能しますが、保証はありません。 current_timestamp 関数のファミリーは、トランザクション内で値が変更されないため、安定していると見なされます。
      特に、これはserialに副作用があります。 列(またはシーケンスから描画されるその他のデフォルト)。ただし、通常はserialに書き込むことはないため、これは問題にはなりません。 列を直接。それらはINSERTにリストされるべきではありません ステートメントすべて。
      serialの残りの欠陥 列:シーケンスは、デフォルトの行を取得するための1回の呼び出しによって引き続き進められ、番号付けにギャップが生じます。繰り返しになりますが、ギャップは一般的に予想されるため、これは問題にはなりません。 serial 列。

    さらに2つの問題を解決できます:

    • 列を定義している場合NOT NULL 、ダミー値を挿入してNULLに置き換える必要があります 結果に。

    • 実際にはダミー行を挿入したくありません 。後で(同じトランザクションで)削除することもできますが、トリガーON DELETEなど、より多くの副作用が発生する可能性があります。 。より良い方法があります:

    ダミー行を避ける

    一時テーブルのクローンを作成します 列のデフォルトを含め、 thatに挿入します :

    BEGIN;
    CREATE TEMP TABLE tmp_playlist_items (LIKE playlist_items INCLUDING DEFAULTS)
       ON COMMIT DROP;  -- drop at end of transaction
    
    INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *;
    ...
    

    同じ結果、より少ない副作用。デフォルトの式は逐語的にコピーされるため、クローンは同じシーケンスがあればそれから描画します。ただし、不要な行またはトリガーによる他の副作用は完全に回避されます。

    アイデアに対するIgorの功績:

    • Postgresql、「偽の」行を選択します

    NOT NULLを削除します 制約

    NOT NULLにはダミー値を指定する必要があります 列、理由(ドキュメントごと):

    null以外の制約は、常に新しいテーブルにコピーされます。

    INSERTのいずれかに対応します ステートメントまたは(より良い)制約を排除します:

    ALTER TABLE tmp_playlist_items
       ALTER COLUMN foo DROP NOT NULL
     , ALTER COLUMN bar DROP NOT NULL;
    

    迅速で汚い方法があります スーパーユーザー権限を持つ場合:

    UPDATE pg_attribute
    SET    attnotnull = FALSE
    WHERE  attrelid = 'tmp_playlist_items'::regclass
    AND    attnotnull
    AND    attnum > 0;
    

    これは、データやその他の目的のない一時的なテーブルであり、トランザクションの終了時に削除されます。したがって、ショートカットは魅力的です。それでも、基本的なルールは、システムカタログを直接改ざんしないことです。

    それでは、クリーンな方法を調べてみましょう。 DOで動的SQLを使用して自動化する 声明。必要なのは通常の特権だけです 同じ役割で一時テーブルが作成されたので、必ず持っているはずです。

    DO $$BEGIN
    EXECUTE (
       SELECT 'ALTER TABLE tmp_playlist_items ALTER '
           || string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
           || ' DROP NOT NULL'
       FROM   pg_catalog.pg_attribute
       WHERE  attrelid = 'tmp_playlist_items'::regclass
       AND    attnotnull
       AND    attnum > 0
       );
    END$$
    

    はるかにクリーンで、それでも非常に高速です。動的コマンドで注意を払い、SQLインジェクションに注意してください。このステートメントは安全です。私はより多くの説明とともにいくつかの関連する答えを投稿しました。

    一般的なソリューション(9.4以前)

    BEGIN;
    
    CREATE TEMP TABLE tmp_playlist_items
       (LIKE playlist_items INCLUDING DEFAULTS) ON COMMIT DROP;
    
    DO $$BEGIN
    EXECUTE (
       SELECT 'ALTER TABLE tmp_playlist_items ALTER '
           || string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
           || ' DROP NOT NULL'
       FROM   pg_catalog.pg_attribute
       WHERE  attrelid = 'tmp_playlist_items'::regclass
       AND    attnotnull
       AND    attnum > 0
       );
    END$$;
    
    LOCK TABLE playlist_items IN EXCLUSIVE MODE;  -- forbid concurrent writes
    
    WITH default_row AS (
       INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *
       )
    , new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
       VALUES
          (651, 21, 30012, 'a', 30, 1, FALSE)
        , (NULL, 21, 1, 'b', 34, 2, NULL)
        , (668, 21, 30012, 'c', 30, 3, FALSE)
        , (7428, 21, 23068, 'd', 0, 4, FALSE)
       )
    , upsert AS (  -- *not* replacing existing values in UPDATE (?)
       UPDATE playlist_items m
       SET   (  playlist,   item,   group_name,   duration,   sort,   legacy)
           = (n.playlist, n.item, n.group_name, n.duration, n.sort, n.legacy)
       --                                   ..., COALESCE(n.legacy, m.legacy)  -- see below
       FROM   new_values n
       WHERE  n.id = m.id
       RETURNING m.id
       )
    INSERT INTO playlist_items
            (playlist,   item,   group_name,   duration,   sort, legacy)
    SELECT n.playlist, n.item, n.group_name, n.duration, n.sort
                                       , COALESCE(n.legacy, d.legacy)
    FROM   new_values n, default_row d   -- single row can be cross-joined
    WHERE  NOT EXISTS (SELECT 1 FROM upsert u WHERE u.id = n.id)
    RETURNING id;
    
    COMMIT;

    必要なのはLOCKだけです 同じテーブルに書き込もうとしている同時トランザクションがある場合。

    要求に応じて、これは列legacyのNULL値のみを置き換えます INSERTの入力行 場合。他の列またはUPDATEで機能するように簡単に拡張できます ケースも。たとえば、UPDATE 条件付きでも:入力値がNOT NULLの場合のみ 。 UPDATEにコメント行を追加しました 上記。

    余談:キャストする必要はありません VALUESの最初の行以外の任意の行の値 型は最初のから派生するため、式 行。

    Postgres 9.5

    UPSERTを実装します INSERT .. ON CONFLICT .. DO NOTHING | UPDATE 。これにより、操作が大幅に簡素化されます。

    INSERT INTO playlist_items AS m (id, playlist, item, group_name, duration, sort, legacy)
    VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
    ,      (DEFAULT, 21, 1, 'b', 34, 2, DEFAULT)  -- !
    ,      (668, 21, 30012, 'c', 30, 3, FALSE)
    ,      (7428, 21, 23068, 'd', 0, 4, FALSE)
    ON CONFLICT (id) DO UPDATE
    SET (playlist, item, group_name, duration, sort, legacy)
     = (EXCLUDED.playlist, EXCLUDED.item, EXCLUDED.group_name
      , EXCLUDED.duration, EXCLUDED.sort, EXCLUDED.legacy)
    -- (...,  COALESCE(l.legacy, EXCLUDED.legacy))  -- see below
    RETURNING m.id;
    

    VALUESを添付できます INSERTへの句 直接、DEFAULTを許可します キーワード。 (id)での固有の違反の場合 、代わりにPostgresが更新されます。 UPDATEで除外された行を使用できます 。マニュアル:

    SET およびWHERE ON CONFLICT DO UPDATEの句 テーブルの名前(またはエイリアス)を使用して既存の行にアクセスし、特別なexcludedを使用して挿入を提案された行にアクセスできます。 テーブル。

    そして:

    行ごとのすべてのBEFORE INSERTの影響に注意してください トリガーは除外された値に反映されます。これらの影響により、行が挿入から除外された可能性があるためです。

    残りのコーナーケース

    UPDATEにはさまざまなオプションがあります :できます...

    • ...まったく更新されない:WHEREを追加します UPDATEの句 選択した行にのみ書き込む。
    • ...選択した列のみを更新します。
    • ...列が現在NULLの場合にのみ更新:COALESCE(l.legacy, EXCLUDED.legacy)
    • ...新しい値がNOT NULLの場合にのみ更新します :COALESCE(EXCLUDED.legacy, l.legacy)

    ただし、DEFAULTを識別する方法はありません。 値とINSERTで実際に提供される値 。結果のEXCLUDEDのみ 行が表示されます。区別が必要な場合は、前のソリューションに戻ってください。両方を自由に使用できます。




    1. MAX()–MySQLの列の最大値を見つける

    2. SearchOracle.comのADDM

    3. Oracle Database Developer Choice Awards

    4. MySQLでrow_numberを取得する方法