PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Batasan tabel silang di PostgreSQL

Klarifikasi

Perumusan persyaratan ini menyisakan ruang untuk interpretasi:
di mana UserRole.role_name berisi nama peran karyawan.

Interpretasi saya:
dengan entri di UserRole yang memiliki role_name = 'employee' .

Konvensi penamaan . Anda adalah bermasalah (diperbarui sekarang). User adalah kata yang dicadangkan dalam SQL standar dan Postgres. Itu ilegal sebagai pengidentifikasi kecuali jika dikutip ganda - yang akan keliru. Nama resmi pengguna sehingga Anda tidak perlu mengutip dua kali.

Saya menggunakan pengenal bebas masalah dalam implementasi saya.

Masalahnya

FOREIGN KEY dan CHECK kendala adalah alat kedap udara yang terbukti untuk menegakkan integritas relasional. Pemicu adalah fitur yang kuat, berguna, dan serbaguna tetapi lebih canggih, tidak terlalu ketat, dan dengan lebih banyak ruang untuk kesalahan desain dan kasus sudut.

Kasus Anda sulit karena kendala FK tampaknya tidak mungkin pada awalnya:memerlukan PRIMARY KEY atau UNIQUE batasan untuk referensi - tidak ada yang mengizinkan nilai NULL. Tidak ada batasan FK parsial, satu-satunya jalan keluar dari integritas referensial yang ketat adalah nilai NULL di referensi kolom karena default MATCH SIMPLE perilaku kendala FK. Per dokumentasi:

MATCH SIMPLE memungkinkan salah satu kolom kunci asing menjadi nol; jika salah satunya adalah nol, baris tidak perlu memiliki kecocokan dalam tabel yang dirujuk.

Jawaban terkait di dba.SE dengan lebih banyak:

  • Konstrain kunci asing dua kolom hanya jika kolom ketiga BUKAN NULL

Solusinya adalah dengan memperkenalkan flag boolean is_employee untuk menandai karyawan di kedua sisi, didefinisikan NOT NULL di users , tetapi diperbolehkan menjadi NULL di user_role :

Solusi

Ini memberlakukan persyaratan Anda dengan tepat , sambil meminimalkan kebisingan dan overhead:

CREATE TABLE users (
   users_id    serial PRIMARY KEY
 , employee_nr int
 , is_employee bool NOT NULL DEFAULT false
 , CONSTRAINT role_employee CHECK (employee_nr IS NOT NULL = is_employee)  
 , UNIQUE (is_employee, users_id)  -- required for FK (otherwise redundant)
);

CREATE TABLE user_role (
   user_role_id serial PRIMARY KEY
 , users_id     int NOT NULL REFERENCES users
 , role_name    text NOT NULL
 , is_employee  bool CHECK(is_employee)
 , CONSTRAINT role_employee
   CHECK (role_name <> 'employee' OR is_employee IS TRUE)
 , CONSTRAINT role_employee_requires_employee_nr_fk
   FOREIGN KEY (is_employee, users_id) REFERENCES users(is_employee, users_id)
);

Itu saja.

Pemicu . ini bersifat opsional tetapi disarankan untuk kemudahan menyetel tag tambahan is_employee secara otomatis dan Anda tidak perlu melakukan apa pun tambahan:

-- users
CREATE OR REPLACE FUNCTION trg_users_insup_bef()
  RETURNS trigger AS
$func$
BEGIN
   NEW.is_employee = (NEW.employee_nr IS NOT NULL);
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

CREATE TRIGGER insup_bef
BEFORE INSERT OR UPDATE OF employee_nr ON users
FOR EACH ROW
EXECUTE PROCEDURE trg_users_insup_bef();

-- user_role
CREATE OR REPLACE FUNCTION trg_user_role_insup_bef()
  RETURNS trigger AS
$func$
BEGIN
   NEW.is_employee = true;
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

CREATE TRIGGER insup_bef
BEFORE INSERT OR UPDATE OF role_name ON user_role
FOR EACH ROW
WHEN (NEW.role_name = 'employee')
EXECUTE PROCEDURE trg_user_role_insup_bef();

Sekali lagi, tanpa basa-basi, dioptimalkan dan hanya dipanggil saat dibutuhkan.

Fiddle SQL demo untuk Postgres 9.3. Harus bekerja dengan Postgres 9.1+.

Poin utama

  • Sekarang, jika kita ingin mengatur user_role.role_name = 'employee' , maka harus ada user.employee_nr yang cocok pertama.

  • Anda masih dapat menambahkan employee_nr ke apa saja pengguna, dan Anda masih dapat (kemudian) memberi tag pada user_role dengan is_employee , terlepas dari role_name yang sebenarnya . Mudah untuk dilarang jika Anda perlu, tetapi implementasi ini tidak memperkenalkan batasan lebih dari yang diperlukan.

  • users.is_employee hanya bisa true atau false dan dipaksa untuk mencerminkan keberadaan employee_nr dengan CHECK paksaan. Pemicu membuat kolom tetap sinkron secara otomatis. Anda dapat mengizinkan false tambahan untuk tujuan lain dengan hanya sedikit pembaruan pada desain.

  • Aturan untuk user_role.is_employee sedikit berbeda:harus benar jika role_name = 'employee' . Ditegakkan oleh CHECK kendala dan diatur secara otomatis oleh pemicu lagi. Tapi itu diperbolehkan untuk mengubah role_name ke sesuatu yang lain dan tetap menyimpan is_employee . Tidak ada yang mengatakan pengguna dengan employee_nr wajib untuk memiliki entri yang sesuai di user_role , justru sebaliknya! Sekali lagi, mudah diterapkan tambahan jika diperlukan.

  • Jika ada pemicu lain yang dapat mengganggu, pertimbangkan ini:
    Cara Menghindari Looping Trigger Calls Di PostgreSQL 9.2.1
    Namun kita tidak perlu khawatir akan aturan yang mungkin dilanggar karena pemicu di atas hanya untuk kenyamanan. Aturan itu sendiri ditegakkan dengan CHECK dan batasan FK, yang tidak mengizinkan pengecualian.

  • Selain:Saya meletakkan kolom is_employee pertama dalam batasan UNIQUE (is_employee, users_id) karena suatu alasan . users_id sudah tercakup dalam PK, sehingga dapat menempati posisi kedua di sini:
    Entitas asosiatif DB dan pengindeksan



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Java Enums, JPA dan Postgres enums - Bagaimana cara membuatnya bekerja bersama?

  2. pgDash Alternatives - Pemantauan Database PostgreSQL dengan ClusterControl

  3. Perbaiki "INSERT memiliki lebih banyak ekspresi daripada kolom target" di PostgreSQL

  4. Apa cara paling elegan untuk menyimpan cap waktu dengan nanosec di postgresql?

  5. 3 Cara Mendaftar semua Pemicu untuk Tabel yang Diberikan di PostgreSQL