Question

Eh bien voici mon problème, j'ai trois tables; régions, pays, états. Les pays peuvent être à l'intérieur de régions, les États peuvent être à l'intérieur de régions. Les régions sont le sommet de la chaîne alimentaire.

J'ajoute maintenant une table popular_areas à deux colonnes; region_id et popular_place_id. Est-il possible de faire de popular_place_id une clé étrangère vers les pays OU ? Je vais probablement devoir ajouter une colonne popular_place_type pour déterminer si l'identifiant décrit un pays ou un état d'une manière ou d'une autre.

Était-ce utile?

La solution

Ce que vous décrivez s'appelle des associations polymorphes. C’est-à-dire que la "clé étrangère" colonne contient une valeur id qui doit exister dans l’un des ensembles de tables cibles. En règle générale, les tables cible sont liées d’une certaine manière, par exemple en tant qu’instances d’une superclasse de données commune. Vous aurez également besoin d’une autre colonne à côté de la colonne de clé étrangère pour pouvoir indiquer sur chaque ligne la table cible à référencer.

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

Il n’existe aucun moyen de modéliser des associations polymorphes à l’aide de contraintes SQL. Une contrainte de clé étrangère fait toujours référence à un tableau cible.

Les associations polymorphiques sont supportées par des frameworks tels que Rails et Hibernate. Mais ils disent explicitement que vous devez désactiver les contraintes SQL pour utiliser cette fonctionnalité. Au lieu de cela, l'application ou le cadre doit effectuer un travail équivalent pour s'assurer que la référence est satisfaite. C'est-à-dire que la valeur de la clé étrangère est présente dans l'une des tables cibles possibles.

Les associations polymorphiques sont faibles en ce qui concerne l'application de la cohérence de la base de données. L’intégrité des données dépend de l’accès de tous les clients à la base de données avec la même logique d’intégrité référentielle appliquée, qui doit également être exempte de bogues.

Voici quelques solutions alternatives qui tirent parti de l'intégrité référentielle imposée par la base de données:

Créez une table supplémentaire par cible. Par exemple, popular_states et popular_countries , qui fait référence à états et pays respectivement. Chacun de ceux-ci "populaire" Les tables font également référence au profil de l'utilisateur.

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

Cela signifie que pour obtenir tous les lieux favoris populaires d'un utilisateur, vous devez interroger ces deux tables. Mais cela signifie que vous pouvez compter sur la base de données pour appliquer la cohérence.

Créez une table places en tant que supertable. Comme le mentionne Abie, une autre alternative est que vos lieux populaires référencent une table telle que places , qui est un parent des états et des pays . Autrement dit, les États et les pays ont également une clé étrangère pour emplacements (vous pouvez même faire en sorte que cette clé étrangère soit également la clé primaire de états et de pays ).

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

Utilisez deux colonnes. Au lieu d'une colonne pouvant faire référence à l'une des tables cibles, utilisez-en deux. Ces deux colonnes peuvent être NULL ; en fait, un seul d'entre eux doit être différent de 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)
);

En termes de théorie relationnelle, les associations polymorphiques violent la la première forme normale , car le popular_place_id est en effet une colonne ayant deux significations: c'est un état ou un pays. Vous ne stockeriez pas le âge d'une personne et son numéro de téléphone dans une seule colonne. Pour la même raison, vous ne devriez pas stocker à la fois state_id et id_pays dans une seule colonne. Le fait que ces deux attributs possèdent des types de données compatibles est une coïncidence; ils signifient toujours différentes entités logiques.

Les associations polymorphiques violent également la troisième forme normale , car la signification de la colonne dépend du caractère extra colonne qui nomme la table à laquelle la clé étrangère fait référence. Dans la troisième forme normale, un attribut d’une table ne doit dépendre que de la clé primaire de cette table.

Commentaire de @SavasVedova:

Je ne suis pas sûr de suivre votre description sans consulter les définitions de table ni un exemple de requête, mais il semblerait que vous disposiez simplement de plusieurs tables Filtres , chacune contenant une clé étrangère qui fait référence à une <> code> produits .

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...

Il est facile de joindre les produits à un type de filtre spécifique si vous savez quel type de filtre

Autres conseils

Ce n’est pas la solution la plus élégante au monde, mais vous pouvez utiliser le héritage de table en béton faire ce travail.

Conceptuellement, vous proposez la notion de classe de "choses pouvant être des zones populaires". dont héritent vos trois types de lieux. Vous pouvez le représenter sous forme de tableau appelé, par exemple, espaces , chaque ligne ayant une relation un à un avec une ligne dans régions , pays ou indique . (Les attributs partagés entre des régions, des pays ou des États, le cas échéant, pourraient être insérés dans cette table d'emplacements.) Votre popular_place_id serait alors une référence de clé étrangère à une ligne de la table d'espaces qui puis dirigez-vous vers une région, un pays ou un État.

La solution que vous proposez avec une deuxième colonne pour décrire le type d’association est la manière dont Rails gère les associations polymorphes, mais je ne suis pas un fan de cela en général. Bill explique en détail pourquoi les associations polymorphes ne sont pas vos amis.

Voici une correction à "Supertable" de Bill Karwin approche, en utilisant une clé composée (lieu_type, place_id) pour résoudre les violations de forme normales perçues:

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

Ce que cette conception ne peut pas garantir que pour chaque ligne située dans emplacements , il existe une ligne dans indique ou pays (mais pas les deux). Il s'agit d'une limitation des clés étrangères en SQL. Dans un SGBD complet conforme aux normes SQL-92, vous pouvez définir des contraintes inter-tables pouvant être différées, ce qui vous permettrait d'atteindre le même objectif, mais il est difficile, implique des transactions et un tel SGBD n'a pas encore été mis sur le marché.

Je me rends compte que ce fil est vieux, mais je l’ai vu et une solution m'est venue à l’esprit et je pensais le jeter.

Les régions, les pays et les États sont des lieux géographiques qui vivent dans une hiérarchie.

Vous pouvez éviter votre problème en créant une table de domaine appelée type_emplacement géographique que vous renseigneriez avec trois lignes (Région, Pays, État).

Ensuite, au lieu des trois tables d'emplacement, créez une table unique géo_location qui a une clé étrangère de type_local_g_graphique (vous pouvez ainsi savoir si l'instance est une région, un pays ou un état).

Modélisez la hiérarchie en faisant de cette table une référence automatique de sorte qu'une instance State conserve la clé fKey dans son instance de pays parent, laquelle à son tour détient la clé fKey dans son instance de région parent. Les instances de région contiendraient NULL dans cette clé. Ce n’est pas différent de ce que vous auriez fait avec les trois tables (vous auriez 1 - beaucoup de relations entre la région et le pays et entre le pays et l’état), sauf que tout est dans une seule table.

La table popular_user_location serait une table de résolution de portée entre user et georgraphical_location (de nombreux utilisateurs pourraient donc aimer de nombreux endroits).

Soooo…

 entrer la description de l'image ici

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

Je ne savais pas quelle était la base de données cible; ce qui précède est MS SQL Server.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top