Domanda

Bene, ecco il mio problema, ho tre tabelle; regioni, paesi, stati. I paesi possono essere all'interno delle regioni, gli stati possono essere all'interno delle regioni. Le regioni sono il vertice della catena alimentare.

Ora sto aggiungendo una tabella popular_areas con due colonne; region_id e popular_place_id. È possibile rendere popular_place_id una chiave esterna per entrambi gli stati OR . Probabilmente dovrò aggiungere una colonna popular_place_type per determinare se l'id sta descrivendo un paese o uno stato in entrambi i modi.

È stato utile?

Soluzione

Quello che stai descrivendo si chiama Associazioni polimorfiche. Cioè, la "chiave esterna" La colonna contiene un valore ID che deve esistere in una di una serie di tabelle di destinazione. In genere, le tabelle di destinazione sono in qualche modo correlate, come ad esempio istanze di alcune superclassi di dati comuni. Avresti anche bisogno di un'altra colonna lungo il lato della colonna chiave esterna, in modo che su ogni riga, puoi designare quale tabella di destinazione fa riferimento.

CREATE TABLE popular_places (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  place_type VARCHAR(10) -- either 'states' or 'countries'
  -- foreign key is not possible
);

Non è possibile modellare le associazioni polimorfiche usando i vincoli SQL. Un vincolo di chiave esterna fa sempre riferimento a una tabella di destinazione.

Le associazioni polimorfiche sono supportate da framework come Rails e Hibernate. Ma affermano esplicitamente che è necessario disabilitare i vincoli SQL per utilizzare questa funzione. Invece, l'applicazione o il framework devono svolgere un lavoro equivalente per garantire che il riferimento sia soddisfatto. Cioè, il valore nella chiave esterna è presente in una delle possibili tabelle di destinazione.

Le associazioni polimorfiche sono deboli per quanto riguarda l'applicazione della coerenza del database. L'integrità dei dati dipende da tutti i client che accedono al database con la stessa logica di integrità referenziale applicata e che l'applicazione deve essere priva di bug.

Ecco alcune soluzioni alternative che sfruttano l'integrità referenziale forzata dal database:

Crea una tabella aggiuntiva per target. Ad esempio popular_states e popular_countries , che fanno riferimento a stati e paesi rispettivamente. Ognuno di questi "popolari" le tabelle fanno anche riferimento al profilo dell'utente.

CREATE TABLE popular_states (
  state_id INT NOT NULL,
  user_id  INT NOT NULL,
  PRIMARY KEY(state_id, user_id),
  FOREIGN KEY (state_id) REFERENCES states(state_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

CREATE TABLE popular_countries (
  country_id INT NOT NULL,
  user_id    INT NOT NULL,
  PRIMARY KEY(country_id, user_id),
  FOREIGN KEY (country_id) REFERENCES countries(country_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

Ciò significa che per ottenere tutti i luoghi preferiti di un utente popolari è necessario interrogare entrambe queste tabelle. Ma significa che puoi fare affidamento sul database per imporre coerenza.

Crea una tabella places come supertable. Come menzionato Abie, una seconda alternativa è che i tuoi luoghi popolari fanno riferimento a una tabella come places , che è un genitore di entrambi stati e paesi . Cioè, sia gli stati che i paesi hanno anche una chiave esterna per posti (puoi anche rendere questa chiave esterna anche la chiave primaria di stati e paesi ).

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  PRIMARY KEY (user_id, place_id),
  FOREIGN KEY (place_id) REFERENCES places(place_id)
);

CREATE TABLE states (
  state_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (state_id) REFERENCES places(place_id)
);

CREATE TABLE countries (
  country_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

Utilizza due colonne. Invece di una colonna che può fare riferimento a una delle due tabelle di destinazione, utilizza due colonne. Queste due colonne possono essere NULL ; in effetti solo uno di questi dovrebbe essere non NULL .

CREATE TABLE popular_areas (
  place_id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  state_id INT,
  country_id INT,
  CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
  CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
  FOREIGN KEY (state_id) REFERENCES places(place_id),
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

In termini di teoria relazionale, le associazioni polimorfiche violano First Normal Form , perché il popular_place_id è in effetti una colonna con due significati: è uno stato o un paese. Non memorizzeresti age di una persona e il suo numero_telefono in una singola colonna, e per lo stesso motivo non dovresti memorizzare sia state_id che country_id in una singola colonna. Il fatto che questi due attributi abbiano tipi di dati compatibili è una coincidenza; significano ancora diverse entità logiche.

Le associazioni polimorfiche violano anche Terza forma normale , perché il significato della colonna dipende dall'ulteriore colonna che indica la tabella a cui fa riferimento la chiave esterna. In Third Normal Form, un attributo in una tabella deve dipendere solo dalla chiave primaria di quella tabella.


Ri commenta da @SavasVedova:

Non sono sicuro di seguire la tua descrizione senza vedere le definizioni della tabella o una query di esempio, ma sembra che tu abbia semplicemente più tabelle Filtri , ognuna contenente una chiave esterna che fa riferimento a una Products .

CREATE TABLE Products (
  product_id INT PRIMARY KEY
);

CREATE TABLE FiltersType1 (
  filter_id INT PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

CREATE TABLE FiltersType2 (
  filter_id INT  PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

...and other filter tables...

Unire i prodotti a un tipo specifico di filtro è facile se sai quale ty

Altri suggerimenti

Questa non è la soluzione più elegante al mondo, ma potresti usare ereditarietà di tabelle concrete per farlo funzionare.

Concettualmente stai proponendo una nozione di una classe di "cose ??che possono essere aree popolari" da cui ereditano i tuoi tre tipi di luoghi. Puoi rappresentarlo come una tabella chiamata, ad esempio, luoghi in cui ogni riga ha una relazione uno a uno con una riga in regioni , paesi o stati . (Gli attributi condivisi tra regioni, paesi o stati, se presenti, potrebbero essere inseriti in questa tabella dei luoghi.) Il tuo popular_place_id sarebbe quindi un riferimento di chiave esterna a una riga della tabella dei luoghi che quindi indirizzarti verso una regione, un Paese o uno stato.

La soluzione che proponi con una seconda colonna per descrivere il tipo di associazione sembra essere il modo in cui Rails gestisce le associazioni polimorfiche, ma non sono un fan di questo in generale. Bill spiega in modo eccellente perché le associazioni polimorfiche non sono tue amiche.

Ecco una correzione di "supertable" di Bill Karwin approccio, utilizzando una chiave composta (place_type, place_id) per risolvere le violazioni della forma normale percepita:

CREATE TABLE places (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) NOT NULL
     CHECK ( place_type = 'state', 'country' ),
  UNIQUE ( place_type, place_id )
);

CREATE TABLE states (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'state' NOT NULL
     CHECK ( place_type = 'state' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to states go here
);

CREATE TABLE countries (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'country' NOT NULL
     CHECK ( place_type = 'country' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to country go here
);

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  UNIQUE ( user_id, place_id ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
);

Ciò che questo disegno non può garantire che per ogni riga in luoghi esiste una riga in stati o paesi (ma non entrambi). Questo è un limite di chiavi esterne in SQL. In un DBMS completo conforme agli standard SQL-92 potresti definire vincoli inter-tabella differibili che ti permetterebbero di ottenere lo stesso ma è ingombrante, implica una transazione e un tale DBMS deve ancora arrivare sul mercato.

Mi rendo conto che questo thread è vecchio, ma ho visto questo e mi è venuta in mente una soluzione e ho pensato di buttarlo lì.

Regioni, Paesi e Stati sono posizioni geografiche che vivono in una gerarchia.

Potresti evitare del tutto il problema creando una tabella di domini denominata Geographic_location_type che popoleresti con tre righe (Regione, Paese, Stato).

Quindi, anziché le tre tabelle delle posizioni, crea una singola tabella di posizione geografica con una chiave esterna di posizione geografica_tipo_id (in modo da sapere se l'istanza è una Regione, Paese o Stato).

Modella la gerarchia rendendo questa tabella autoreferenziale in modo tale che un'istanza di Stato mantenga il tasto fKey nell'istanza del Paese padre che a sua volta trattiene il tasto fKey nell'istanza della regione padre. Le istanze di regione manterrebbero NULL in quel tasto. Questo non è diverso da quello che avresti fatto con le tre tabelle (avresti 1 - molte relazioni tra regione e paese e tra paese e stato) tranne ora che è tutto in una tabella.

La tabella popular_user_location sarebbe una tabella di risoluzione dell'ambito tra user e georgraphical_location (quindi a molti utenti potrebbero piacere molti posti).

Soooo & # 8230;

 inserisci qui la descrizione dell'immagine

CREATE TABLE [geographical_location_type] (
    [geographical_location_type_id] INTEGER NOT NULL,
    [name] VARCHAR(25) NOT NULL,
    CONSTRAINT [PK_geographical_location_type] PRIMARY KEY ([geographical_location_type_id])
)

-- Add 'Region', 'Country' and 'State' instances to the above table


CREATE TABLE [geographical_location] (
   [geographical_location_id] BIGINT IDENTITY(0,1) NOT NULL,
    [name] VARCHAR(1024) NOT NULL,
    [geographical_location_type_id] INTEGER NOT NULL,
    [geographical_location_parent] BIGINT,  -- self referencing; can be null for top-level instances
    CONSTRAINT [PK_geographical_location] PRIMARY KEY ([geographical_location_id])
)

CREATE TABLE [user] (
    [user_id] BIGINT NOT NULL,
    [login_id] VARCHAR(30) NOT NULL,
    [password] VARCHAR(512) NOT NULL,
    CONSTRAINT [PK_user] PRIMARY KEY ([user_id])
)


CREATE TABLE [popular_user_location] (
    [popular_user_location_id] BIGINT NOT NULL,
    [user_id] BIGINT NOT NULL,
    [geographical_location_id] BIGINT NOT NULL,
    CONSTRAINT [PK_popular_user_location] PRIMARY KEY ([popular_user_location_id])
)

ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_type_geographical_location] 
    FOREIGN KEY ([geographical_location_type_id]) REFERENCES [geographical_location_type] ([geographical_location_type_id])



ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_geographical_location] 
    FOREIGN KEY ([geographical_location_parent]) REFERENCES [geographical_location] ([geographical_location_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [user_popular_user_location] 
    FOREIGN KEY ([user_id]) REFERENCES [user] ([user_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [geographical_location_popular_user_location] 
    FOREIGN KEY ([geographical_location_id]) REFERENCES [geographical_location] ([geographical_location_id])

Non ero sicuro di quale fosse il DB di destinazione; quanto sopra è MS SQL Server.

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