Bagaimana menghubungkan dua baris dalam tabel yang sama

11

Saya memiliki sebuah tabel di mana baris-baris tersebut dapat saling berhubungan satu sama lain, dan secara logis, hubungan berjalan dua arah (pada dasarnya, tanpa arah) antara dua baris. (Dan jika Anda bertanya-tanya, ya, ini benar-benar harus menjadi satu tabel. Ini adalah dua hal dari entitas / tipe logis yang sama persis.) Saya dapat memikirkan beberapa cara untuk mewakili ini:

  1. Simpan hubungan dan kebalikannya
  2. Menyimpan hubungan satu arah, membatasi database agar tidak menyimpannya dengan cara lain, dan memiliki dua indeks dengan pesanan berlawanan untuk FK (satu indeks menjadi indeks PK)
  3. Simpan hubungan satu arah dengan dua indeks dan biarkan yang kedua tetap dimasukkan (kedengarannya agak menjijikkan, tapi hei, kelengkapan)
  4. Buat semacam tabel pengelompokan dan minta sebuah FK di atas meja asli untuk itu. (Meningkatkan banyak pertanyaan. Tabel pengelompokan hanya akan memiliki nomor; mengapa bahkan memiliki tabel? Membuat FK NULLable atau memiliki grup dengan satu baris yang terkait?)

Apa saja pro dan kontra utama dari cara-cara ini, dan tentu saja, adakah cara yang belum saya pikirkan?

Inilah SQLFiddle untuk dimainkan: http://sqlfiddle.com/#!12/7ee1a/1/0 . (Terjadi menjadi PostgreSQL karena itulah yang saya gunakan, tetapi saya tidak berpikir pertanyaan ini sangat spesifik untuk PostgreSQL.) Saat ini menyimpan hubungan dan membalikkannya hanya sebagai contoh.

jpmc26
sumber
Dapatkah nilai yang diberikan terkait dengan lebih dari satu lainnya? Apakah nilai yang diberikan selalu terkait dengan yang lain? Apakah mereka berbagi data umum lainnya yang sama?
Philᵀᴹ
Ya, mereka dapat dikaitkan dengan lebih dari 1 baris lainnya. Tidak, mereka tidak selalu terkait dengan baris lain. Mereka tidak perlu memiliki data umum. Terima kasih.
jpmc26
Ups. Lupa @Phil. Juga diedit untuk menambahkan struktur potensial yang baru saja terjadi pada saya.
jpmc26

Jawaban:

9

Apa yang Anda rancang bagus. Apa yang perlu ditambahkan adalah kendala untuk membuat hubungan tanpa arah. Jadi, Anda tidak dapat memiliki (1,5)baris tanpa (5,1)menambahkan baris juga.

Ini dapat dilakukan * dengan batasan referensi diri di tabel jembatan.

*: dapat diselesaikan di Postgres, Oracle, DB2, dan semua DBMS yang telah menerapkan batasan kunci asing seperti yang dijelaskan oleh standar SQL (ditangguhkan, mis. diperiksa pada akhir transaksi.) Pemeriksaan yang ditangguhkan sebenarnya tidak diperlukan, seperti pada SQL- Server yang memeriksa mereka di akhir pernyataan dan konstruksi ini masih berfungsi. Anda tidak dapat melakukan ini di MySQL karena "InnoDB memeriksa batasan UNIQUE dan FOREIGN KEY baris-demi-baris" .

Jadi, di Postgres berikut ini akan sesuai dengan kebutuhan Anda:

CREATE TABLE x
(
  x_id SERIAL NOT NULL PRIMARY KEY,
  data VARCHAR(10) NOT NULL
);

CREATE TABLE bridge_x
(
  x_id1 INTEGER NOT NULL REFERENCES x (x_id),
  x_id2 INTEGER NOT NULL REFERENCES x (x_id),
  PRIMARY KEY(x_id1, x_id2),
  CONSTRAINT x_x_directionless
    FOREIGN KEY (x_id2, x_id1)
    REFERENCES bridge_x (x_id1, x_id2)
);

Diuji di: SQL-Fiddle

Jika Anda mencoba menambahkan baris (1,5):

INSERT INTO bridge_x VALUES
(1,5) ;

Gagal dengan:

GALAT: menyisipkan atau memperbarui pada tabel "bridge_x" melanggar batasan kunci asing "x_x_directionless"
Detail: Key (x_id2, x_id1) = (5, 1) tidak ada dalam tabel "bridge_x" .:
INSERT INTO bridge_x VALUES (1,5)

Selain itu, Anda dapat menambahkan CHECKbatasan jika Anda ingin melarang (y,y)baris:

ALTER TABLE bridge_x
  ADD CONSTRAINT x_x_self_referencing_items_not_allowed
    CHECK (x_id1 <> x_id2) ;

Ada cara lain untuk mengimplementasikan ini seperti yang Anda sebutkan, seperti menyimpan hanya satu arah hubungan (dalam satu baris, bukan dua) dengan memaksa id yang lebih rendah di x_id1dan id yang lebih tinggi di x_id2kolom. Tampaknya lebih mudah diterapkan, tetapi biasanya mengarah ke pertanyaan yang lebih kompleks nanti:

CREATE TABLE bridge_x
(
  x_id1 INTEGER NOT NULL REFERENCES x (x_id),
  x_id2 INTEGER NOT NULL REFERENCES x (x_id),
  PRIMARY KEY(x_id1, x_id2),
  CONSTRAINT x_x_directionless
    CHECK (x_id1 <= x_id2)                       -- or "<" to forbid `(y,y)` rows
);
ypercubeᵀᴹ
sumber