Batasan pengecualian
Saya sarankan Anda menggunakan batasan pengecualian, yang jauh lebih sederhana, lebih aman, dan lebih cepat:
Anda perlu menginstal modul tambahan btree_gist
pertama. Lihat instruksi dan penjelasan dalam jawaban terkait ini:
Dan Anda harus menyertakan "ParentID"
dalam tabel "Bar"
berlebihan, yang akan menjadi harga kecil yang harus dibayar. Definisi tabel dapat terlihat seperti ini:
CREATE TABLE "Foo" (
"FooID" serial PRIMARY KEY
"ParentID" int4 NOT NULL REFERENCES "Parent"
"Details1" varchar
CONSTRAINT foo_parent_foo_uni UNIQUE ("ParentID", "FooID") -- required for FK
);
CREATE TABLE "Bar" (
"ParentID" int4 NOT NULL,
"FooID" int4 NOT NULL REFERENCES "Foo" ("FooID"),
"Timerange" tstzrange NOT NULL,
"Detail1" varchar,
"Detail2" varchar,
CONSTRAINT "Bar_pkey" PRIMARY KEY ("FooID", "Timerange"),
CONSTRAINT bar_foo_fk
FOREIGN KEY ("ParentID", "FooID") REFERENCES "Foo" ("ParentID", "FooID"),
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist ("ParentID" WITH =, "Timerange" WITH &&)
);
Saya juga mengubah tipe data untuk "Bar"."FooID"
dari ke int8
int4
. Ini merujuk pada "Foo"."FooID"
, yang merupakan serial
, yaitu int4
. Gunakan jenis pencocokan int4
(atau cukup integer
) karena beberapa alasan, salah satunya adalah kinerja.
Anda tidak memerlukan pemicu lagi (setidaknya tidak untuk tugas ini), dan Anda tidak membuat indeks lagi, karena dibuat secara implisit oleh batasan pengecualian."Bar_FooID_Timerange_idx"
Indeks btree pada ("ParentID", "FooID")
kemungkinan besar akan berguna, meskipun:
CREATE INDEX bar_parentid_fooid_idx ON "Bar" ("ParentID", "FooID");
Terkait:
Saya memilih UNIQUE ("ParentID", "FooID")
dan bukan sebaliknya karena suatu alasan, karena ada indeks lain dengan "FooID"
di depan di salah satu tabel:
Selain:Saya tidak pernah menggunakan CaMeL yang dikutip ganda -pengidentifikasi kasus di Postgres. Saya hanya melakukannya di sini untuk mematuhi tata letak Anda.
Hindari kolom yang berlebihan
Jika Anda tidak dapat atau tidak akan menyertakan "Bar"."ParentID"
berlebihan, ada lagi nakal cara - dengan syarat "Foo"."ParentID"
tidak pernah diperbarui . Pastikan itu, dengan pemicu misalnya.
Anda dapat memalsukan IMMUTABLE
fungsi:
CREATE OR REPLACE FUNCTION f_parent_of_foo(int)
RETURNS int AS
'SELECT "ParentID" FROM public."Foo" WHERE "FooID" = $1'
LANGUAGE sql IMMUTABLE;
Saya memenuhi syarat skema nama tabel untuk memastikan, dengan asumsi public
. Sesuaikan dengan skema Anda.
Selengkapnya:
- KONTRAINT untuk memeriksa nilai dari tabel yang terkait dari jarak jauh (melalui join, dll.)
- Apakah PostgreSQL mendukung "aksen tidak sensitif " koleksi?
Kemudian gunakan dalam batasan pengecualian:
CONSTRAINT bar_parent_timerange_excl
EXCLUDE USING gist (f_parent_of_foo("FooID") WITH =, "Timerange" WITH &&)
Saat menyimpan satu int4
yang berlebihan kolom, kendala akan lebih mahal untuk diverifikasi dan seluruh solusi bergantung pada lebih banyak prasyarat.
Menangani konflik
Anda dapat membungkus INSERT
dan UPDATE
ke dalam fungsi plpgsql dan menjebak kemungkinan pengecualian dari batasan pengecualian (23P01 exclusion_violation
) untuk mengatasinya.
INSERT ...
EXCEPTION
WHEN exclusion_violation
THEN -- handle conflict
Contoh kode lengkap:
Menangani konflik di Postgres 9.5
Di Postgres 9.5 anda dapat menangani INSERT
langsung dengan implementasi "UPSERT" yang baru. Dokumentasi:
Namun:
Tapi Anda masih bisa menggunakan ON CONFLICT DO NOTHING
, sehingga menghindari kemungkinan exclusion_violation
pengecualian. Cukup periksa apakah ada baris yang benar-benar diperbarui, mana yang lebih murah:
INSERT ...
ON CONFLICT ON CONSTRAINT bar_parent_timerange_excl DO NOTHING;
IF NOT FOUND THEN
-- handle conflict
END IF;
Contoh ini membatasi pemeriksaan ke batasan pengecualian yang diberikan. (Saya menyebutkan batasan secara eksplisit untuk tujuan ini dalam definisi tabel di atas.) Pengecualian lain yang mungkin tidak tertangkap.