Est-il raisonnable de marquer toutes les colonnes sauf une comme clé primaire?

9

J'ai une table représentant des films. Les champs sont les suivants :
id (PK), title, genre, runtime, released_in, tags, origin, downloads.

Ma base de données ne peut pas être polluée par des lignes dupliquées, je souhaite donc appliquer l'unicité. Le problème est que différents films peuvent avoir le même titre, voire les mêmes champs sauf tagset downloads. Comment imposer l'unicité?

J'ai pensé à deux façons:

  • faire tous les champs sauf downloadsla clé primaire. Je me retiens downloadscar c'est JSON et cela aura probablement un impact sur les performances.
  • conserver uniquement idcomme clé primaire, mais ajouter une contrainte unique à toutes les autres colonnes (sauf, encore une fois downloads).

J'ai lu cette question qui est très similaire, mais je ne comprenais pas très bien quoi faire. Actuellement, ce tableau n'est lié à aucun autre tableau, mais pourrait l'être à l'avenir.

Pour le moment, j'ai un peu moins de 20 000 disques, mais je m'attends à ce que ce nombre augmente. Je ne sais pas si cela est quelque peu pertinent pour la question.

EDIT: J'ai modifié le schéma et voici comment je créerais la table:

CREATE TABLE movies (
    id          serial PRIMARY KEY,
    title       text NOT NULL,
    runtime     smallint NOT NULL CHECK (runtime >= 0),
    released_in smallint NOT NULL CHECK (released_in > 0),
    genres      text[] NOT NULL default ARRAY[]::text[],
    tags        text[] NOT NULL default ARRAY[]::text[],
    origin      text[] NOT NULL default ARRAY[]::text[],
    downloads   json NOT NULL,
    inserted_at timestamp NOT NULL default current_timestamp,
    CONSTRAINT must_be_unique UNIQUE(title,runtime,released_in,genres,tags,origin)
);

J'ai également ajouté la timestampcolonne, mais ce n'est pas un problème car je n'y toucherai pas. Ce sera donc toujours automatique et unique.

rubik
la source
Question étroitement liée (avec réponse) sur SO: Ai-je besoin d'une clé primaire pour ma table, qui a UNIQUE (composite à 4 colonnes), dont l'une peut être NULL? . Si l'une des colonnes peut être NULL, considérez d'urgence ceci: dba.stackexchange.com/q/9759/3684 .
Erwin Brandstetter

Réponses:

4

La définition de votre table semble désormais raisonnable. Avec toutes les colonnes, NOT NULLla UNIQUEcontrainte fonctionnera comme prévu - à l'exception des fautes de frappe et des différences orthographiques mineures, qui peuvent être assez courantes, je le crains. Considérez le commentaire de @ a_horse .

Alternative avec index unique fonctionnel

L'autre option serait un index fonctionnel unique (similaire à ce que @Dave a commenté ). Mais j'utiliserais un uuidtype de données pour optimiser la taille et les performances de l'index.

La conversion du tableau en texte n'est pas IMMUTABLE(en raison de son implémentation générique):

Par conséquent, vous avez besoin d'une petite fonction d'aide pour la déclarer immuable:

CREATE OR REPLACE FUNCTION f_movie_uuid(_title text
                                      , _runtime int2
                                      , _released_in int2
                                      , _genres text[]
                                      , _tags text[]
                                      , _origin text[])
  RETURNS uuid LANGUAGE sql IMMUTABLE AS  -- faking IMMUTABLE
'SELECT md5(_title || _runtime::text || _released_in::text
         || _genres::text || _tags::text || _origin::text)::uuid';

Utilisez-le pour la définition de l'index:

CREATE UNIQUE INDEX movies_uni_idx
ON movies (f_movie_uuid(title,runtime,released_in,genres,tags,origin));

SQL Fiddle.

Plus de détails:

Vous pouvez utiliser l'UUID généré comme PK, mais j'utiliserais toujours la serialcolonne avec ses 4 octets, ce qui est simple et bon marché pour les références FK et à d'autres fins. Un UUID serait une excellente option pour les systèmes distribués qui doivent générer des valeurs PK indépendamment. Ou pour de très grandes tables, mais il n'y a presque pas assez de films dans notre système solaire pour cela.

Avantages et inconvénients

Une contrainte unique est implémentée avec un index unique sur les colonnes impliquées. Mettez d'abord les colonnes pertinentes dans la définition de contrainte et vous disposez d'un indice utile à d'autres fins comme garantie.

Il existe d'autres avantages spécifiques, voici une liste:

L' indice fonctionnel unique est (potentiellement beaucoup) plus petit, ce qui peut le rendre beaucoup plus rapide. Si vos colonnes ne sont pas trop grandes, la différence ne sera pas grande. Il y a aussi les petits frais généraux pour le calcul.

La concaténation de toutes les colonnes peut introduire des faux positifs ( 'foo ' || 'bar' = 'foob ' || 'ar', mais cela semble très peu probable dans ce cas. Les fautes de frappe sont tellement plus susceptibles que vous puissiez les ignorer en toute sécurité ici.

Unicité et tableaux

Les tableaux devraient être triés de manière cohérente pour avoir du sens dans tout arrangement unique reposant sur l' =opérateur, car '{1,2}' <> '{2,1}'. Je suggère des tables de recherche pour genre, taget originavec serialPK et des entrées uniques, qui permettent une recherche floue des éléments du tableau. Alors:

Quoi qu'il en soit, en travaillant directement avec des tableaux ou avec un schéma normalisé et une vue matérialisée, la recherche peut être très efficace avec le bon index et les bons opérateurs:

De côté

Si vous utilisez Postgres 9.4 ou une version ultérieure, pensez à la jsonbplace dejson .

Erwin Brandstetter
la source
6

Imaginez que vous sortiez avec un groupe d'amis et que la conversation tourne au cinéma. Quelqu'un demande: "Qu'avez-vous pensé des" Trois mousquetaires "?" Vous répondez: "Lequel?"

De quelles informations supplémentaires auriez-vous besoin pour être absolument certain que vous pensez tous les deux au même film? Le nom du réalisateur? Le studio de production? L'année de sa sortie? Un des noms de la star? Une combinaison de deux ou plus?

La réponse à ma question et la vôtre sont les mêmes.

Cependant, je ne pense pas que ce genre serait un bon candidat. L'une des raisons pour lesquelles le genre est un critère beaucoup trop subjectif. L'action des «trois mousquetaires»? drame? aventure? comédie? action-aventure? comédie romantique? Je vois souvent le même film répertorié sous différents genres. Même lorsque vous autorisez plusieurs genres, votre utilisateur peut en sélectionner un entièrement différent, non répertorié avec le film réel qu'il recherche.

Même les temps d'exécution peuvent différer, en particulier entre les versions cinéma et magnétoscope / DVD / rayons B.

Vous avez donc besoin d'attributs durs et objectifs qui ne changeront pas d'un communiqué de presse à un autre. Malheureusement, cela peut exclure le nom du film car les films ont été renommés, surtout après la sortie d'une suite.

Et la date de sortie? La sortie en salles de 1993? La sortie du magnétoscope de 1999? La sortie DVD de 2004? Vous avez eu l'idée.

À bien y penser, qu'en est-il de tous ces films réalisés par Alan Smithee? Le vrai réalisateur a-t-il finalement décidé de mettre son nom sur le projet après coup? Je ne sais pas.

Hmm, je ferais mieux de m'arrêter tant qu'il reste des critères.

Quelques points supplémentaires:

  • Oui, conservez la clé de substitution et créez un index unique sur les champs de clé naturels (si vous pouvez enfin les clouer). La clé de substitution est la meilleure pour les références de clés étrangères. Vous ne voulez pas dupliquer tous les champs de clé naturelle dans chaque table contenant une référence à un film.
  • Supprimez les champs du tableau (genres, tags, origines). Allez-y et normalisez correctement ces attributs. Je n'ai jamais vu un champ de tableau qui n'était pas beaucoup plus difficile qu'il n'en valait la peine, surtout si vous voulez qu'ils soient consultables ("... où genre = 'horror' ..."). Notez que cela n'éliminera pas automatiquement les problèmes de différences de casse et d'orthographe ("Science Fiction" vs "SciFi") - à moins que vous ne gériez correctement les tables de recherche . Mais il est beaucoup plus facile de vérifier de telles différences dans un champ d'un petit tableau que dans chaque cellule de tableau de chaque ligne d'un grand tableau.
TommCatt
la source
4

La colonne ID n'a aucun avantage en ce qui concerne l'unicité que vous souhaitez / devez appliquer. L'unicité de toute combinaison d'attributs ne sera jamais appliquée en ajoutant un ID sans signification. Son «avantage» ne s'affiche que lorsque vous arrivez au point où vous auriez besoin d'une nouvelle table qui a besoin d'une clé étrangère pour celle-ci. Dans ce cas, et SI vous avez inclus l'ID, vous pouvez l'utiliser comme FK dans votre nouvelle table. (Mais ne pensez pas que ce sera un déjeuner gratuit. L'inconvénient d'une telle approche est que vous vous retrouverez probablement à écrire plus de jointures dans le seul but de récupérer des informations qui auraient parfaitement pu faire partie de ce nouveau tableau que vous avez créé. )

Erwin Smout
la source
1
Si les règles métier stipulent que la combinaison de valeurs dans les attributs FOO et BAR doit être unique, l'ajout d'un ID ne va pas y parvenir. L'ajout de l'ID permet simplement d'éviter d'avoir à inclure FOO et BAR en tant que tels dans les tables de référencement. Ce qui à son tour nécessite plus de jointures car les attributs FOO et BAR (qui portent des identifiants BUSINESS) ne sont pas là où ils auraient pu être (et où ils sont très probablement ATTENDUS d'être, au moins d'un point de vue commercial).
Erwin Smout
1
Ce ne sont PAS les "lignes" qui doivent être uniques, c'est ce que l'entreprise dit être leurs identifiants. Si c'est une combinaison d'attributs FOO et BAR, alors c'est la combinaison d'attributs FOO et BAR.
Erwin Smout
2
Le fait d'avoir l'identifiant ou non ne résout aucun problème d'application de l'unicité des colonnes "métier" de votre table. L'application de l'unicité doit être effectuée en déclarant les clés appropriées (ce que vous faites - le fait que vous ayez utilisé le mot syntaxique "CONTRAINTE" au lieu de "CLÉ" ne signifie pas qu'il ne s'agit pas d'une clé).
Erwin Smout