Comment implémenter une relation plusieurs-à-plusieurs dans PostgreSQL?

95

Je pense que le titre est explicite. Comment créer la structure de table dans PostgreSQL pour créer une relation plusieurs-à-plusieurs.

Mon exemple:

Product(name, price);
Bill(name, date, Products);
Radu Gheorghiu
la source
2
supprimer les produits de la table de facturation, créer une nouvelle table appelée "bill_products" avec deux champs: un pointant vers les produits, un pointant vers la facture. faites de ces deux champs la clé primaire de cette nouvelle table.
Marc B
Donc, bill_products (facture, produits); ? Et les deux PK?
Radu Gheorghiu
1
Ouais. ils seraient individuellement un FK pointant vers leurs tables respectives, et ensemble, ils seraient le PK pour la nouvelle table.
Marc B
Donc, bill_product (référence produit nom.produit, facture référence facture.nom, clé primaire (produit, facture))?
Radu Gheorghiu
Ils indiqueraient ce que seraient les champs PK des tables Product et Bill.
Marc B

Réponses:

298

Les instructions SQL DDL (langage de définition de données) pourraient ressembler à ceci:

CREATE TABLE product (
  product_id serial PRIMARY KEY  -- implicit primary key constraint
, product    text NOT NULL
, price      numeric NOT NULL DEFAULT 0
);

CREATE TABLE bill (
  bill_id  serial PRIMARY KEY
, bill     text NOT NULL
, billdate date NOT NULL DEFAULT CURRENT_DATE
);

CREATE TABLE bill_product (
  bill_id    int REFERENCES bill (bill_id) ON UPDATE CASCADE ON DELETE CASCADE
, product_id int REFERENCES product (product_id) ON UPDATE CASCADE
, amount     numeric NOT NULL DEFAULT 1
, CONSTRAINT bill_product_pkey PRIMARY KEY (bill_id, product_id)  -- explicit pk
);

J'ai fait quelques ajustements:

  • La relation n: m est normalement implémentée par une table séparée - bill_productdans ce cas.

  • J'ai ajouté des serialcolonnes comme clés primaires de substitution . Dans Postgres 10 ou version ultérieure, envisagez plutôt une IDENTITYcolonne . Voir:

    Je le recommande vivement, car le nom d'un produit n'est guère unique (pas une bonne "clé naturelle"). En outre, appliquer l'unicité et référencer la colonne dans les clés étrangères est généralement moins cher avec un 4 octets integer(ou même un 8 octets bigint) qu'avec une chaîne stockée en tant que textou varchar.

  • N'utilisez pas de noms de types de données de base datecomme des identificateurs . Bien que cela soit possible, c'est un mauvais style et conduit à des erreurs et des messages d'erreur déroutants. Utilisez des identifiants légaux, en minuscules et sans guillemets . N'utilisez jamais de mots réservés et évitez les identifiants de casse mixtes entre guillemets si vous le pouvez.

  • "nom" n'est pas un bon nom. J'ai renommé la colonne de la table productpour être product( product_nameou similaire). C'est une meilleure convention de dénomination . Sinon, lorsque vous joignez quelques tables dans une requête - ce que vous faites souvent dans une base de données relationnelle - vous vous retrouvez avec plusieurs colonnes nommées «nom» et devez utiliser des alias de colonne pour trier le désordre. Cela ne sert à rien. Un autre anti-pattern répandu serait juste "id" comme nom de colonne.
    Je ne sais pas quel billserait le nom d'un . bill_idsuffira probablement dans ce cas.

  • priceest du type de donnéesnumeric pour stocker les nombres fractionnaires précisément tels qu'ils ont été saisis (type de précision arbitraire au lieu de type virgule flottante). Si vous traitez exclusivement des nombres entiers, faitesinteger . Par exemple, vous pouvez enregistrer les prix sous forme de cents .

  • Le amount( "Products"dans votre question) entre dans la table de liaison bill_productet est également de type numeric. Encore une fois, integersi vous traitez exclusivement des nombres entiers.

  • Vous voyez les clés étrangères dans bill_product? J'ai créé à la fois à des changements en cascade: ON UPDATE CASCADE. Si un product_idou bill_iddoit changer, le changement est appliqué en cascade à toutes les entrées dépendantes de bill_productet rien ne casse. Ce ne sont que des références sans signification propre.
    J'ai également utilisé ON DELETE CASCADEpour bill_id: Si une facture est supprimée, ses détails meurent avec elle.
    Ce n'est pas le cas pour les produits: vous ne souhaitez pas supprimer un produit utilisé dans une facture. Postgres lancera une erreur si vous essayez cela. Vous ajouteriez une autre colonne à productpour marquer les lignes obsolètes ("soft-delete") à la place.

  • Toutes les colonnes de cet exemple de base finissent par être NOT NULL, les NULLvaleurs ne sont donc pas autorisées. (Oui, toutes les colonnes - les colonnes de clé primaire sont définies UNIQUE NOT NULLautomatiquement.) C'est parce que les NULLvaleurs n'ont aucun sens dans aucune des colonnes. Cela facilite la vie d'un débutant. Mais vous ne vous en sortirez pas si facilement, vous devez quand même comprendre la NULLmanipulation . Des colonnes supplémentaires peuvent autoriser des NULLvaleurs, des fonctions et des jointures peuvent introduire des NULLvaleurs dans les requêtes, etc.

  • Lisez le chapitre sur CREATE TABLEdans le manuel .

  • Les clés primaires sont implémentées avec un index unique sur les colonnes clés, ce qui accélère les requêtes avec des conditions sur la ou les colonnes PK. Cependant, la séquence des colonnes clés est pertinente dans les clés multicolonnes. Puisque le PK bill_productest activé (bill_id, product_id)dans mon exemple, vous pouvez ajouter un autre index sur juste product_idou (product_id, bill_id)si vous avez des requêtes à la recherche d'unproduct_id et d'un non bill_id. Voir:

  • Lisez le chapitre sur les index dans le manuel .

Erwin Brandstetter
la source
Comment puis-je créer un index pour la table de mappage bill_product? Normalement , il devrait ressembler à : CREATE INDEX idx_bill_product_id ON booked_rates(bill_id, product_id). Est-ce correct?
codyLine
1
@codyLine: Cet index est créé automatiquement par le PK.
Erwin Brandstetter
1
@ErwinBrandstetter: Ne devrait-on pas créer un index sur bill_product pour la colonne product_id?
Christian
2
@ ChristianB.Almeida: C'est utile dans de nombreux cas, oui. J'ai ajouté un peu sur l'indexation.
Erwin Brandstetter
1
@Jakov: Il y a seulement 1 ligne pour chaque facture dans le tableau bill. Nous avons besoin du montant par article ajouté bill_product.
Erwin Brandstetter