シリアル列への明示的な挿入を防ぎたい。私は次のトリガーに達しました:
drop table test_table;
create table test_table(
id bigserial primary key,
foobar text
);
create or replace function serial_id_check() returns trigger as
$$
begin
if new.id != currval(TG_TABLE_NAME||'_id_seq') then
raise exception 'Explicit insert into serial id, currval = %, tried to insert = %', currval(TG_TABLE_NAME||'_id_seq'), new.id;
end if;
return new;
end;
$$ language plpgsql;
create trigger test_table_serial_id_check
before insert on test_table
for each row
execute procedure serial_id_check();
多分もっと良いアプローチがありますか?たぶんこのアプローチは壊れており、これはまったく達成できませんか?
追伸また、挿入と更新の権限を付与せず、挿入/更新のためのpgplsqlプロシージャのみを許可することも考えています。しかし、現在のところ、このアプローチは不可能です。
Postgres 9.4では、ビューは自動的に更新可能です列ごと。つまり、基本的な条件が満たされている限り、列は基になる列への単純な参照のために自動的に更新可能です。式はそうではありません。この機能を利用できます。
_CREATE TABLE test_table(
id serial PRIMARY KEY
, foobar text
);
CREATE VIEW test_view AS
SELECT id * 1 AS id -- id unchanged but not updatable!
, foobar
FROM test_table;
_
id
と_1
_を乗算すると、値は変更されませんが、列は自動的に更新できなくなります。
test_table
_に対するREVOKE
INSERT
/UPDATE
特権。GRANT INSERT
_の_test_view
_/UPDATE
をユーザーに提供します。今ではすべてを行うことができますが、id
の値を手動で設定または変更することはできません。 DELETE
の処理方法はユーザーが選択します。
このシンプルで迅速なソリューションは、Postgres 9.4ではそのまま使用できます。自動更新可能なビューはPostgres 9.3で導入されましたが、機能が動作するためには、そのバージョンですべての列が更新可能である必要があります。
Postgres 9.3以前で、_INSTEAD OF INSERT
_トリガーまたは無条件_ON INSERT DO INSTEAD
_ルールを提供します。
ルール/トリガーを手動で記述している間、_id * 1
_トリックは必要ありません。 Postgres 9.4以降でもこれを使用して機能を微調整することができます。例:
_CREATE VIEW test_view1 AS TABLE test_table;
CREATE OR REPLACE RULE ins_up AS
ON INSERT TO test_view1 DO INSTEAD
INSERT INTO test_table (foobar) VALUES (NEW.foobar);
_
現在、ビューでINSERT
を使用できますが、UPDATE
またはDELETE
はまだ使用できません。必要に応じて、さらにルールを記述します。
SQLフィドル (Postgres 9.6)
重要difference:自動的に更新可能なビューは、INSERT
/UPDATE
の値への試みを拒否しますが、id
exceptionを使用すると、デモンストレーションされたRULE
は単にid
の-ignores値であり、例外なく続行されます。
または、シリアル列をシーケンスの次の値無条件で単純に上書きすることもできます。
_CREATE OR REPLACE FUNCTION force_serial_id()
RETURNS trigger AS
$func$
BEGIN
NEW.id := nextval(pg_get_serial_sequence(quote_ident(TG_TABLE_NAME), 'id'));
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
_
これは、スマートにしようとするよりも簡単で安価で、エラーが発生しにくくなります。 quote_ident()
不正な名前を安全にエスケープします(SQLインジェクションからも保護されます)。
@ dezso commented のように、これは通常の操作でtwoserial
列を使用して行ごとに数値を書き込みます。デフォルトがフェッチされるためですbeforeトリガー関数が起動します。通常、シーケンスのギャップは(とにかく予想される)問題ではありませんが、列からDEFAULT
を削除することで副作用を回避できます。次に、トリガーのみに依存します。
別のトリガーとトリガー自体の条件WHEN (OLD.id <> NEW.id)
を使用してUPDATE
ケースを微調整できます。構文例:
pg_get_serial_sequence()
の使用に注意してください。これは、非基本的な識別子の元のように壊れません。 _"MyTable"
_またはデフォルト以外のシーケンス名を考えてください。列名はid
と仮定しますが、私は個人的にneverを使用しています。
これは正確ではない答えまだではありませんが、あなたが望むのはGENERATED ALWAYS
。そして、それはその途中です、おそらくPostgreSQLの次のリリースであるPostgreSQL 10
CREATE TABLE itest4 (
a int GENERATED ALWAYS AS IDENTITY,
b text
);
パッチに関するメール から構文を盗んだ動詞。
ここのcommitfestの問題 を追跡します。
SQL仕様から、
If <identity column specification> is specified, then:
i) An indication that the column is an identity column.
ii) If ALWAYS is specified, then an indication that values are always generated.
iii) If BY DEFAULT is specified, then an indication that values are generated by default.
iv) The General Rules of Subclause 9.26, “Creation of a sequence generator”, are applied with
SGO as OPTIONS and ICT as DATA TYPE; let the descriptor of the sequence generator SG be
the SEQGENDESC returned from the application of those General Rules.
承認された回答はすべて問題ありませんが、どこかからのコードが明示的なID値を挿入し、それによってシーケンスを途中で壊すように見えるシナリオをトラブルシューティングしようとする場合、単に問題を修正するのではなく、問題のコードにエラーを発生させたいと思います。その場での問題(またはビューを使用するためにすべてを書き換える)。
OP独自のソリューションは、INSERT INTO table (col2) VALUES ('val'), ('val')
またはINSERT INTO table (col2) SELECT somefield FROM anothertable
を使用して複数の挿入を行うシナリオでは機能しません。
トリガー条件に対するこのわずかな修正により、この問題が修正されます。また、シーケンスの名前をより確実に特定するためのpg_get_serial_sequence
の使用にも注意してください。 (pg_get_serial_sequence
は、シーケンスのOWNED BY
プロパティが正しく設定されていることに依存していることに注意してください。)
create or replace function serial_id_check() returns trigger as
$$
begin
if new.id > currval(pg_get_serial_sequence(TG_TABLE_NAME, 'id')) then
raise exception 'Explicit insert into serial id, currval = %, tried to insert = %', currval(pg_get_serial_sequence(TG_TABLE_NAME, 'id')), new.id;
end if;
return new;
end;
$$ language plpgsql;