web-dev-qa-db-ja.com

PostgreSQLパーティションID列をデフォルトにする方法は?

PostgreSQL 11
パーティションテーブルのID列のデフォルト値を生成する最良の方法は何ですか。
例えば

CREATE TABLE data.log
(
  id              BIGINT GENERATED ALWAYS AS IDENTITY
                  (
                    INCREMENT BY 1
                    MINVALUE -9223372036854775808
                    MAXVALUE 9223372036854775807
                    START WITH -9223372036854775808
                    RESTART WITH -9223372036854775808
                    CYCLE
                  ),
  Epoch_millis    BIGINT NOT NULL,
  message         TEXT NOT NULL

) PARTITION BY RANGE (Epoch_millis);

CREATE TABLE data.foo_log
PARTITION OF data.log
(
  PRIMARY KEY (id)
)
FOR VALUES FROM (0) TO (9999999999);

私が行った場合:

INSERT INTO data.foo_log (Epoch_millis, message)
VALUES (1000000, 'hello');

私は得ます:

エラー:列「id」のnull値はnot-null制約に違反しています
詳細:失敗した行には(null、1000000、hello)が含まれます。
SQL状態:23502

デフォルトで生成された値はパーティションに適用されないため、次のようにルートテーブルに挿入します。

INSERT INTO data.log (Epoch_millis, message)
VALUES (1000000, 'hello');

ただし、パフォーマンス上の理由(一括COPYの実行など)のために、特定のパーティションに直接挿入したい場合もあります。
これを機能させる唯一の方法は、次のようにID列に対して暗黙的に作成されたシーケンスを認識しながらパーティションを作成することです。

CREATE TABLE data.foo_log
PARTITION OF data.log
(
  id DEFAULT nextval('data.log_id_seq'),
  PRIMARY KEY (id)
)
FOR VALUES FROM (0) TO (9999999999);

これを行うためのより良い方法はありますか?

4
akagixxer

一般的には、より良い解決策を知りません。ただし、いくつかのマイナーなこと:

pg_get_serial_sequence()

親の暗黙のシーケンスの名前がわからない場合は、 pg_get_serial_sequence() を使用します。

SELECT pg_get_serial_sequence('data.log', 'id');

CREATE TABLEスクリプトで式を直接使用することもできますが、デフォルトの実際の名前を計算するために非常にわずかな追加コストがかかります(トランザクションごとに1回と思います)。これはパフォーマンスの最適化に関するものなので...

COPYGENERATED ALWAYSをオーバーライドしますが、トリガーはしません

id列をGENERATED ALWAYS AS IDENTITYとして定義すると、次のようなオーバーライド句を使用している場合でも、idステートメントで列INSERTにユーザー値を提供することはできません。

INSERT INTO data.log (Epoch_millis, message) OVERRIDING USER VALUE
VALUES (1000000, 'hello');

これが機能するためにはGENERATED BY DEFAULTであるか、またはidからomitINSERTを完全に使用する必要があります。 マニュアル:

OVERRIDING USER VALUE

この句が指定されている場合、GENERATED BY DEFAULTとして定義されているID列に指定された値はすべて無視され、デフォルトのシーケンス生成値が適用されます。

この句は、テーブル間で値をコピーする場合などに役立ちます。 INSERT INTO tbl2 OVERRIDING USER VALUE SELECT * FROM tbl1を書き込むと、tbl1のID列ではないすべての列がtbl2からコピーされますが、tbl2のID列の値は、tbl2に関連付けられたシーケンスによって生成されます。

COPYいずれの場合も上書きされます。 マニュアル:

ID列の場合、COPY FROMコマンドは、INSERTオプションOVERRIDING SYSTEM VALUEのように、常に入力データで提供される列の値を書き込みます。

しかし、パーティションに直接書き込むとき、ソリューションではINSERTもオーバーライドされるため、id列にユーザー値を直接指定しないようにするためにあなたの責任になります。代替策は、パーティションでトリガーを使用することですデフォルト値の代わりに

CREATE OR REPLACE FUNCTION trg_log_default_id()
  RETURNS trigger AS
$func$
BEGIN
   NEW.id := nextval('data.log_id_seq')
   RETURN NEW;
END
$func$  LANGUAGE plpgsql;

CREATE TRIGGER insbef_default_id
  BEFORE INSERT ON data.foo_log  -- the partition
  FOR EACH ROW
  EXECUTE PROCEDURE trg_log_default_id();

これにより、シーケンスから番号が割り当てられます。親のGENERATED ALWAYSの動作をより厳密にエミュレートします。さらに厳密に、COPYがルールに違反するのを防ぎます。 マニュアル:

COPY FROMは、トリガーを呼び出し、宛先テーブルの制約をチェックします。

ただし、トリガーは単純なデフォルト値よりも少し高価です。また、親テーブルを介した通常の挿入では、行ごとに追加のシリアル番号が書き込まれます。 (トリガーではケースを区別できるはずですが、今は試みませんでした。)

3