Qual è il modo migliore per rappresentare una relazione molti-a-molti tra i record in una singola tabella SQL?

StackOverflow https://stackoverflow.com/questions/474119

Domanda

Ho una tabella SQL in questo modo:

Aggiornamento: sto cambiando la tabella di esempio poiché la natura gerarchica esistente dei dati originali (Stato, Città, Scuole) sta mettendo in ombra il fatto che è necessaria una semplice relazione tra gli elementi.

entities
id      name               
1       Apple     
2       Orange            
3       Banana             
4       Carrot                
5       Mushroom        

Voglio definire relazioni bilaterali tra queste entità in modo che un utente che visualizza un'entità possa vedere un elenco di tutte le entità correlate.

Le relazioni sono definite da un utente finale.

Qual è il modo migliore per rappresentare queste relazioni nel database e successivamente interrogarle e aggiornarle?

Un modo per come lo vedo ...

Il mio istinto dice una tabella di relazioni come questa:

entity_entity
entity_id_a       entity_id_b
1                 2
5                 1
4                 1
5                 4
1                 3

Stando così le cose, dato un entity_id fornito di 4, come si otterrebbero tutti i record correlati, che sarebbero 1 e 5?

Allo stesso modo una query di entity_id = 1 dovrebbe restituire 2, 3, 4 e 5.

Grazie per il tuo tempo e fammi sapere se posso chiarire la domanda.

È stato utile?

Soluzione

Definisci un vincolo: entity_id_a < entity_id_b.

Crea indici:

CREATE UNIQUE INDEX ix_a_b ON entity_entity(entity_id_a, entity_id_b);
CREATE INDEX ix_b ON entity_entity(entity_id_b);

Il secondo indice non deve includere entity_id_a poiché lo userai solo per selezionare tutti i a in uno b. RANGE SCAN su ix_b sarà più veloce di un SKIP SCAN su ix_a_b.

Popola la tabella con le tue entità come segue:

INSERT
INTO entity_entity (entity_id_a, entity_id_b)
VALUES (LEAST(@id1, @id2), GREATEST(@id1, @id2))

Quindi selezionare:

SELECT entity_id_b
FROM entity_entity
WHERE entity_id_a = @id
UNION ALL
SELECT entity_id_a
FROM entity_entity
WHERE entity_id_b = @id

UNION ALL qui ti consente di utilizzare gli indici sopra ed evitare l'ordinamento extra per unicità.

Tutto quanto sopra è valido per una relazione simmetrica e antiriflessiva. Ciò significa che:

  • Se a è correlato a b , allora b è legato a a

  • a non è mai correlato a a

Altri suggerimenti

Penso che la struttura che hai suggerito vada bene.

Per ottenere i record correlati, fare qualcosa del genere

SELECT related.* FROM entities AS search 
LEFT JOIN entity_entity map ON map.entity_id_a = search.id
LEFT JOIN entities AS related ON map.entity_id_b = related.id
WHERE search.name = 'Search term'

Spero che sia d'aiuto.

L'approccio della tabella dei collegamenti sembra corretto, tranne per il fatto che potresti desiderare un "tipo di relazione" in modo da sapere PERCHÉ sono correlati.

Ad esempio, la relazione tra Raleigh e North Carolina non è la stessa di una relazione tra Raleigh e Durham. Inoltre, potresti voler sapere chi è il 'genitore' nella relazione, nel caso in cui stavi guidando i menu a discesa condizionali. (vale a dire che selezioni uno Stato, puoi vedere le città che si trovano nello stato).

A seconda della complessità delle tue esigenze, la semplice configurazione che hai in questo momento potrebbe non essere sufficiente. Se devi semplicemente dimostrare che due record sono in qualche modo correlati, la tabella dei collegamenti dovrebbe essere sufficiente.

Ho già pubblicato un modo per farlo nel tuo design, ma volevo anche offrire queste informazioni dettagliate sul design se hai una certa flessibilità nel tuo design e questo si adatta meglio alle tue esigenze.

Se gli elementi sono in classi di equivalenza (non sovrapposte), potresti voler fare delle classi di equivalenza la base per la progettazione della tabella, dove tutto in classe è considerato equivalente. Le classi stesse possono essere anonime:

CREATE TABLE equivalence_class (
    class_id int -- surrogate, IDENTITY, autonumber, etc.
    ,entity_id int
)

entity_id dovrebbe essere unico per una partizione non sovrapposta del tuo spazio.

Ciò evita il problema di garantire la giusta mano sinistra o destra o forzare una matrice di relazione in alto a destra.

Quindi la tua query è leggermente diversa:

SELECT c2.entity_id
FROM equivalence_class c1
INNER JOIN equivalence_class c2
    ON c1.entity_id = @entity_id
    AND c1.class_id = c2.class_id
    AND c2.entity_id <> @entity_id

o, equivalentemente:

SELECT c2.entity_id
FROM equivalence_class c1
INNER JOIN equivalence_class c2
    ON c1.entity_id = @entity_id
    AND c1.class_id = c2.class_id
    AND c2.entity_id <> c1.entity_id

Posso pensare ad alcuni modi.

Un singolo passaggio con CASE:

SELECT DISTINCT
    CASE
        WHEN entity_id_a <> @entity_id THEN entity_id_a
        WHEN entity_id_b <> @entity_id THEN entity_id_b
    END AS equivalent_entity
FROM entity_entity
WHERE entity_id_a = @entity_id OR entity_id_b = @entity_id

O due query filtrate UNIONed così:

SELECT entity_id_b AS equivalent_entity
FROM entity_entity
WHERE entity_id_a = @entity_id
UNION
SELECT entity_id_a AS equivalent_entity
FROM entity_entity
WHERE entity_id_b = @entity_id
select * from entities
where entity_id in 
(
    select entity_id_b 
    from entity_entity 
    where entity_id_a = @lookup_value
)

In base allo schema aggiornato questa query dovrebbe funzionare:

select if(entity_id_a=:entity_id,entity_id_b,entity_id_a) as related_entity_id where :entity_id in (entity_id_a, entity_id_b)

dove: entity_id è associato all'entità su cui si sta eseguendo la query

Il mio consiglio è che il design della tabella iniziale non è valido. Non memorizzare diversi tipi di cose nella stessa tabella. (La prima regola di progettazione del database, proprio lì con non memorizza più informazioni nello stesso campo). Questo è molto più difficile da interrogare e causerà significativi problemi di prestazioni lungo la strada. Inoltre, sarebbe un problema inserire i dati nella tabella di realership - come fai a sapere quali entità dovrebbero essere realizzate quando fai una nuova voce? Sarebbe molto meglio progettare correttamente le tabelle relazionali. Le tabelle delle entità sono quasi sempre una cattiva idea. Dall'esempio non vedo alcun motivo per avere questo tipo di informazioni in una tabella. Francamente avrei un tavolo universitario e una relativa tabella degli indirizzi. Sarebbe facile interrogare ed eseguire molto meglio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top