Come mettere in relazione due righe nella stessa tabella


11

Ho una tabella in cui le righe possono essere correlate tra loro e, logicamente, la relazione va in entrambe le direzioni (sostanzialmente, è senza direzione) tra le due righe. (E se ti stai chiedendo, sì, questo dovrebbe davvero essere un tavolo. Sono due cose della stessa identica entità / tipo logico.) Posso pensare a un paio di modi per rappresentare questo:

  1. Memorizza la relazione e il suo contrario
  2. Archivia la relazione in un modo, impedisce al database di archiviarlo nell'altro modo e dispone di due indici con ordini opposti per gli FK (un indice è l'indice PK)
  3. Memorizza la relazione in un modo con due indici e consenti comunque di inserire il secondo (sembra un po 'schifoso, ma ehi, completezza)
  4. Crea una sorta di tabella di raggruppamento e disponi di un FK nella tabella originale. (Solleva molte domande. La tabella di raggruppamento avrebbe solo un numero; perché anche avere la tabella? Rendere FK NULLable o avere gruppi con una singola riga associata?)

Quali sono alcuni dei principali pro e contro di questi modi e, naturalmente, c'è un modo a cui non ho pensato?

Ecco un SQLFiddle con cui giocare: http://sqlfiddle.com/#!12/7ee1a/1/0 . (Sembra essere PostgreSQL poiché è quello che sto usando, ma non penso che questa domanda sia molto specifica per PostgreSQL.) Attualmente memorizza sia la relazione che il suo contrario solo come esempio.


Un determinato valore può essere correlato a più di un altro? Un dato valore è sempre correlato ad un altro? Condividono gli stessi altri dati comuni?
Philᵀᴹ

Sì, possono essere correlati a più di 1 altra riga. No, non sono necessariamente sempre correlati a un'altra riga. Non hanno necessariamente dati comuni. Grazie.
jpmc26,

Ops. Hai dimenticato @Phil. Modificato anche per aggiungere una potenziale struttura che mi è appena venuta in mente.
jpmc26,

Risposte:


9

Quello che hai progettato è buono. Ciò che deve essere aggiunto è un vincolo per rendere la relazione senza direzione. Pertanto, non è possibile avere una (1,5)riga anche senza (5,1)l'aggiunta di una riga.

Ciò può essere realizzato * con un vincolo autoreferenziale sulla tabella bridge.

*: può essere realizzato in Postgres, Oracle, DB2 e in tutti i DBMS che hanno implementato vincoli di chiave esterna come descritto dallo standard SQL (differito, ad esempio verificato al termine della transazione.) Il controllo differito non è comunque necessario, come in SQL- Server che li controlla alla fine dell'istruzione e questa costruzione funziona ancora. Non puoi farlo in MySQL perché "InnoDB controlla i vincoli UNIQUE e FOREIGN KEY riga per riga" .

Quindi, in Postgres le seguenti esigenze corrisponderanno alle tue esigenze:

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)
);

Testato su: SQL-Fiddle

Se provi ad aggiungere una riga (1,5):

INSERT INTO bridge_x VALUES
(1,5) ;

Non riesce con:

ERRORE: inserire o aggiornare nella tabella "bridge_x" viola il vincolo di chiave esterna "x_x_directionless"
Dettaglio: Chiave (x_id2, x_id1) = (5, 1) non è presente nella tabella "bridge_x" .:
INSERISCI Bridge_x VALUES (1,5)

Inoltre, puoi aggiungere un CHECKvincolo se vuoi vietare le (y,y)righe:

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

Esistono altri modi per implementarlo come dici tu, come memorizzare una sola direzione della relazione (in una riga, non due) forzando l'ID inferiore x_id1e l'id superiore nella x_id2colonna. Sembra più facile da implementare, ma di solito porta a query più complesse in seguito:

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
);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.