web-dev-qa-db-fra.com

Une clé étrangère peut-elle référencer un index non unique?

Je pensais qu'une clé étrangère signifiait qu'une seule ligne devait référencer une seule ligne, mais je regarde certaines tables où ce n'est certainement pas le cas. Table1 a column1 avec une contrainte de clé étrangère sur column2 dans table2, MAIS il existe de nombreux enregistrements dans table2 avec la même valeur dans column2. Il y a aussi un index non unique sur column2. Qu'est-ce que ça veut dire? Une contrainte de clé étrangère signifie-t-elle simplement qu'au moins un enregistrement doit exister avec les bonnes valeurs dans les colonnes de droite? Je pensais que cela signifiait qu'il devait exister un seul enregistrement de ce type (je ne sais pas comment les valeurs nulles s'intègrent dans la photo, mais cela m'inquiète moins pour le moment).

update: Apparemment, ce comportement est spécifique à MySQL. C'est ce que j'utilisais, mais je ne l'ai pas mentionné dans ma question initiale.

37
allyourcode

De documentation MySQL :

InnoDB permet à une contrainte de clé étrangère de faire référence à une clé non unique. Ceci est une extension InnoDB au standard SQL.

Cependant, il existe une raison pratique pour éviter les clés étrangères sur des colonnes non uniques de la table référencée. Autrement dit, quelle devrait être la sémantique de "ON DELETE CASCADE" dans ce cas?

La documentation conseille en outre :

Le traitement des références de clés étrangères à des clés non uniques ou contenant des valeurs NULL n'est pas bien défini (...).

44
Hobbes

Votre analyse est correcte. les clés ne doivent pas nécessairement être uniques et les contraintes agissent sur l'ensemble des lignes correspondantes. Ce n'est généralement pas un comportement utile, mais des situations peuvent se présenter où vous le souhaitez.

7
chaos

Oui, vous pouvez créer des clés étrangères pour n’importe quelle colonne de la table. La plupart du temps, vous les créerez sur la clé primaire.

Si vous utilisez des clés étrangères qui ne pointent pas vers une clé primaire, vous pouvez également créer un index (non unique) sur la ou les colonnes référencées pour des raisons de performances.

Dépend du SGBDR que vous utilisez. Je pense que certains le font pour vous implicitement, ou utilisent d'autres astuces. RTM.

3
Evan

Lorsque cela se produit, cela signifie généralement que deux clés étrangères sont liées l'une à l'autre . Souvent, la table qui contiendrait la clé en tant que clé primaire ne serait même pas dans le schéma.

Exemple: Deux tables, COLLEGES et STUDENTS, contiennent toutes deux une colonne appelée ZIPCODE.

Si nous vérifions rapidement 

SELECT * FROM COLLEGES JOIN STUDENTS ON COLLEGES.ZIPCODE = STUDENTS.ZIPCODE

Nous pourrions découvrir que la relation est multiple. Si notre schéma avait une table appelée ZIPCODES, avec la clé primaire ZIPCODE, ce qui se passe réellement serait évident. 

Mais notre schéma n'a pas une telle table. Ce n'est pas parce que notre schéma n'a pas de table que cela signifie que de telles données n'existent pas. quelque part, sur le territoire USPO, il existe un tel tableau. Et COLLEGES.ZIPCODE et STUDENTS.ZIPCODE sont des références à cette table, même si nous ne la reconnaissons pas.

Cela a plus à voir avec la philosophie des données que la pratique de la construction de bases de données, mais illustre clairement quelque chose de fondamental: les données ont des caractéristiques que nous découvrons, et pas seulement des caractéristiques que nous inventons. Bien sûr, ce que nous découvrons pourrait être ce que quelqu'un d'autre a inventé. C'est certainement le cas avec ZIPCODE.

3
Walter Mitty

PostgreSQL le refuse également (de toute façon, même si c'est possible , cela ne veut pas dire que c'est une bonne idée):

essais=> CREATE TABLE Cities (name TEXT, country TEXT);
CREATE TABLE
essais=> INSERT INTO Cities VALUES ('Syracuse', 'USA');
INSERT 0 1
essais=> INSERT INTO Cities VALUES ('Syracuse', 'Greece');
INSERT 0 1
essais=> INSERT INTO Cities VALUES ('Paris', 'France');
INSERT 0 1
essais=> INSERT INTO Cities VALUES ('Aramits', 'France');
INSERT 0 1
essais=> INSERT INTO Cities VALUES ('Paris', 'USA');
INSERT 0 1

essais=> CREATE TABLE People (name TEXT, city TEXT REFERENCES Cities(name));
ERROR:  there is no unique constraint matching given keys for referenced table "cities"
1
bortzmeyer

De quelle base de données parle-t-on? En SQL 2005, je ne peux pas créer de contrainte de clé étrangère qui référence une colonne qui n'a pas de contrainte unique (clé primaire ou autre).

create table t1
(
  id int identity,
  fk int
);

create table t2
(
  id int identity,
);

CREATE NONCLUSTERED INDEX [IX_t2] ON [t2] 
(
    [id] ASC
);
ALTER TABLE t1 with NOCHECK
ADD CONSTRAINT FK_t2 FOREIGN KEY (fk)
    REFERENCES t2 (id) ;


Msg 1776, Level 16, State 0, Line 1
There are no primary or candidate keys in the referenced table 't2' 
that match the referencing column list in the foreign key 'FK_t2'.
Msg 1750, Level 16, State 0, Line 1
Could not create constraint. See previous errors.

Si vous pouviez réellement le faire, vous auriez effectivement une relation plusieurs à plusieurs, ce qui est impossible sans une table intermédiaire. Je serais vraiment intéressé à en savoir plus à ce sujet ...

Voir cette question connexe et les réponses également.

0
cdonner

Nécromancie. 
Comme d’autres l'ont déjà dit, vous ne devriez pas faire référence à une clé non unique en tant que clé étrangère. 
Mais ce que vous pouvez faire à la place (sans supprimer le danger en cascade), c’est d’ajouter une contrainte de vérification (au moins en MS-SQL). 
Ce n'est pas exactement la même chose qu'une clé étrangère, mais au moins cela empêchera l'insertion de données invalides/orphelines/mortes.

Voir ici pour référence (vous devrez porter le code MS-SQL à la syntaxe MySQL):
Clé étrangère vers clé non primaire

Modifier:
En recherchant les raisons du vote négatif, selon contrainte de vérification de Mysql , MySQL ne prend pas vraiment en charge les contraintes de vérification. 
Vous pouvez les définir dans votre requête DDL pour des raisons de compatibilité, mais elles sont simplement ignorées ...

Mais comme mentionné ici, vous pouvez créer un déclencheur BEFORE INSERT et BEFORE UPDATE, qui générera une erreur lorsque les exigences des données ne sont pas satisfaites, ce qui est fondamentalement la même chose, sauf que le désordre est encore plus grand.

Sur la question:

Je pensais qu'une clé étrangère signifiait qu'une seule ligne devait faire référence à un rangée simple, mais je regarde certaines tables où c’est vraiment pas le cas.

Cela est vrai dans tout SGBDR raisonnable. 
Le fait que cela soit possible dans MySQL n’est qu’une autre raison pour laquelle 
MySQL est un SGBDR en toute sécurité.
C'est peut-être rapide, mais sacrifier l'intégrité référentielle et la qualité des données sur l'autel de la vitesse n'est pas mon idée d'une qualité-rdbms. 
En fait, si ce n’est pas conforme à ACID, ce n’est pas vraiment un SGBDR (qui fonctionne correctement).

0
Stefan Steiger