web-dev-qa-db-fra.com

ENUM et ID en tant que clé étrangère

Utilisation de PostgreSQL 11.

J'ai essayé de fouiller dans Stack Overflow et ici et je n'ai pas pu trouver de réponse sur les meilleures pratiques.

Je travaille sur une conception de base de données et suis arrivé à un schéma qui utilise une "table de jointure" générique. Cette table de jointure contient cinq colonnes:

CREATE TABLE many_joins_table (
  id PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  object_id int NOT NULL,
  object_table joins_object_t NOT NULL,
  parent_id int NOT NULL,
  parent_table joins_parent_t NOT NULL);

J'utilise cette table pour représenter des relations adjacentes et plusieurs à plusieurs entre les objets de ma base de données. Un tel exemple est les balises.

CREATE TABLE tag (
  id PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  name text NOT NULL UNIQUE);

CREATE TABLE comment (
  id PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY);


CREATE TYPE joins_object_t AS ENUM ('tag');
CREATE TYPE joins_parent_t AS ENUM ('comment');

Lorsqu'une balise est ajoutée à la table des commentaires, j'insère une nouvelle ligne dans cette table de jointure avec les champs suivants:

INSERT INTO many_joins_table
VALUES(1, 'tag'::joins_object_t, 1, 'comment'::joins_parent_t);

Outre la rigidité des énumérations, adressée avec PostgreSQL 9.1 https://stackoverflow.com/questions/1771543/adding-a-new-value-to-an-existing-enum-type/7834949#7834949 .

Y a-t-il des inconvénients ou des avantages significatifs d'une telle approche? Je crains d'avoir implémenté par erreur un anti-modèle. Existe-t-il des meilleures pratiques que je peux appliquer pour améliorer cette implémentation (indexation ou contraintes)?

Merci!

Remarque: Je suis conscient qu'il existe de meilleures façons d'implémenter des balises, à savoir utiliser des intrarays. J'utilise juste des balises comme exemple car c'est facile à comprendre. https://stackoverflow.com/questions/23508551/integer-array-lookup-using-postgres

Edit: Suppression des UUID car cela peut être une distraction pour la question.

3
Jason

Tout d'abord, évitez les énumérations pour des choses comme celle-ci. Les valeurs d'énumération ne peuvent jamais être supprimées, alors ne les utilisez que si vous êtes sûr que cela ne sera jamais nécessaire, ce qui ne semble pas être le cas ici.

Quoi qu'il en soit, je dirais que votre conception est trop compliquée et manque toujours de la caractéristique cruciale de l'intégrité référentielle.

Utilisez une table de jonction pour chaque paire d'objets pouvant être liés. De cette façon, vous

  • préciser quels objets peuvent être liés

  • peut avoir une intégrité référentielle

Avoir de nombreuses tables est une bonne chose pour une base de données. Si vous soutenez que vous avez 1000 tables et que chaque objet peut être lié les uns aux autres, ce serait trop de tables. Mais dans ce cas, vous devriez probablement opter pour un modèle où vous n'avez de toute façon pas de table par type d'objet.

4
Laurenz Albe

Je pense que vous rendez ce complexe. Tout d'abord, ce blog est un mauvais conseil. Bienvenue sur Internet. N'y faites pas attention.

Utilisez un int PRIMARY KEY GENERATED BY IDENTITY AS DEFAULT (c'est à dire, IDENTITY COLUMN jusqu'à ce que vous ayez une raison pas trop

Maintenant, vous avez deux choses ..

  • commentaires
  • mots clés

Ces deux peuvent être hiérarchiques. C'est la seule chose qu'ils ont en commun. Les tables ne modélisent pas différentes structures de données. Les tables contiennent des données. Et tout le but d'une base de données relationnelle est de modéliser les relations des données. Ceci est totalement perverti lorsque vous modélisez des schémas abstraits qui correspondent à toutes vos données.

Des questions pour vous,

  • Avez-vous besoin d'une hiérarchie? C'est plus complexe et plus lent. Cela fait beaucoup plus. Apprenez des requêtes récursives et vous pouvez cependant le faire assez rapidement pour la plupart des charges de travail. Sinon, ne modélisez pas les choses de manière hiérarchique pour le plaisir. StackOverflow n'a pas de balises hiérarchiques - elles réussissent assez bien.
  • Vous avez donc besoin d'une hiérarchie? A-t-il besoin d'un héritage multiple? Vous pouvez répondre à une réponse ou à une question avec un "commentaire" sur ce réseau. Pensez à combien il serait plus complexe de pouvoir répondre à plusieurs réponses avec le même commentaire . Pensez à l'interface utilisateur. Vous pouvez y aller, en avez-vous besoin?

En supposant que vous ayez besoin d'un héritage unique, vous pouvez le faire

CREATE TABLE tag (
  tag_id     int   PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  tag        name  text,
  parent_tag int   REFERENCES tag
);

Pour plus d'informations à ce sujet, voir hierarchy , et plus précisément ma réponse ici qui traite des commentaires filetés. C'est bien beau, mais maintenant vous avez besoin d'une requête récursive pour interroger cette table. Vous devez également l'épingler à une question. Une question est-elle étiquetée avec toutes les sous-étiquettes? Est-il étiqueté avec toutes les étiquettes parentales? Une question peut-elle être balisée avec deux balises dans la même hiérarchie?

Souvent, avec les balises, il est plus facile de faire simplement ..

-- case insensitive
CREATE EXTENSION citext;

CREATE OR REPLACE FUNCTION array_lacks_dupes(anyarray)
RETURNS bool
AS $$
        SELECT coalesce(hasdupe,nodupe) AS hasdupe
        FROM (VALUES (true)) AS t(nodupe)
        LEFT OUTER JOIN (
                SELECT false
                FROM unnest($1) AS e
                GROUP BY e
                HAVING count(*) > 1
                LIMIT 1
        ) AS g(hasdupe)
        ON true
$$ LANGUAGE sql
STRICT IMMUTABLE;


CREATE TABLE question (
  question_id int      PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
  tags        citext[] CHECK (array_lacks_dupes(tags))
);

CREATE INDEX ON question USING gin (tags);

Avec le schéma ci-dessus, vous n'avez pas à l'interroger de manière étrange et vous pouvez toujours résoudre la requête sur un index ( vous pouvez même ajouter d'autres choses arbitraires sur cet index et tout faire en une seule recherche )

SELECT * FORM question
WHERE tags @> ARRAY['foo']::citext;

Cela trouvera toutes les questions marquées 'foo' sur un index! Vous voulez trouver si vous correspondez à plusieurs balises?

WHERE tags @> ARRAY['foo', 'bar']::citext;
3
Evan Carroll