web-dev-qa-db-fra.com

Meilleures pratiques pour générer des clés multicolores uniques pour des entités faibles?

Comment générer des identifiants non naturels non naturels pour les entités faibles?

Par exemple, si order_id est la clé principale d'une table order et (order_id, item_number) est la clé principale d'un order_item table avec une clé étrangère sur order_id, comment mieux générer item_number?

Quelques possibilités à l'esprit, mais aucun ne semble idéal:

  1. Incrémentation automatique item_number: les order_item L'entité n'est plus faible et la clé composite est redondante.

  2. Utilisez un déclencheur pour rechercher le courant maximum item_number pour un order_id, alors incrément: si une ligne est supprimée, cela pourrait entraîner une réaffectation d'un PK à un dossier différent - cela ne semble pas être une bonne idée? (Modifier: Ceci peut également être effectué sans utiliser de déclencheurs comme expliqué dans Réponse de Joanolo )

  3. Utilisez un déclencheur pour créer une nouvelle séquence pour chaque order_id, et tracer en quelque sorte item_numbers à partir de la séquence appropriée: Ceci est fonctionnellement le comportement souhaité, mais semble que ce serait un gâchis à mettre en œuvre. Est-il même possible de faire référence à une séquence par un order_id?

Modifier - étroitement lié (si pas dupliquer):

2
Gord Stephen

1. est le moins sujet d'erreur, le plus simple et le plus rapide.
Solutions de déclenchement comme dans 2. ou 3. sont soumis à des conditions de course subtiles dans un accès en écriture simultanée.

Faire item_number A serial colonne et aussi le PK pour order_item Dans ce cas. Stick avec les valeurs par défaut dessinées à partir de la séquence sous-jacente et jamais Mettre à jour la colonne.

Créez un index multi-colonnes sur (order_id, item_number) Pour une exécution des requêtes typiques. (Pourrait aussi bien être UNIQUE, mais ne doit pas nécessairement être.) Dans une configuration typique (order_id Et item_number Peut être uniforme integer), L'indice multicolumnique se trouve être aussi petit et rapide qu'un index sur juste order_id:

(Comme je l'ai commenté :) Typiquement, le seul rôle important d'un numéro d'article est unique (et immutable ). Si vous avez besoin d'un Stable ordre de tri entre les éléments, vous pouvez simplement compter sur la valeur de série de item_number. Sachez que ces chiffres ne sont pas nécessairement dans l'ordre des commentaires des transactions. Il peut être utile d'ajouter l'horodatage de transaction current_timestamp (Ou éventuellement statement_timestamp() ou clock_timestamp()) à la ligne. Dépend des exigences et des modèles d'accès.

Vous pouvez ajouter un VIEW pour l'œil humain, avec des numéros d'élément par order_id À partir de 1 , généré dynamiquement avec row_number() , commandé par des critères ci-dessus. Mais fonctionne avec l'unique, immuable item_number Interne.

4
Erwin Brandstetter

Vous pouvez avoir une quatrième alternative, c'est cohérente avec votre définition, où la base de données s'occupe des éléments de l'élément lorsque vous INSERT vos données; sans avoir besoin de déclencheurs. [Bien que vous puissiez les utiliser aussi.]

En supposant que c'est votre schéma:

CREATE TABLE "Order"
(
    "orderId" integer primary key,  -- this should probably be serial
    "order" text not null
) ;

CREATE TABLE "OrderItem"
(
    "orderId" integer not null REFERENCES "Order"("orderId") 
         ON DELETE CASCADE ON UPDATE CASCADE,
    "itemNumber" integer not null,
    "orderItem" text,

    PRIMARY KEY ("orderId", "itemNumber")
) ;

et que nous avons trois ordres:

INSERT INTO "Order" ("orderId", "order") VALUES(1, 'Order number 1') ;
INSERT INTO "Order" ("orderId", "order") VALUES(2, 'Order number 2') ;
INSERT INTO "Order" ("orderId", "order") VALUES(3, 'Order number 3') ;

Si vous avez besoin d'ajouter un article à la commande 3, vous feriez:

INSERT INTO 
    "OrderItem" 
    ("orderId", "orderItem", "itemNumber")
VALUES 
    (3, 'whichever item you need', 
     (SELECT coalesce(max("itemNumber"),0) + 1 
        FROM "OrderItem" 
       WHERE "orderId"=3)
    ) ;

C'est-à-dire que lorsque vous INSERT, vous recherchez simplement la fonction max("itemNumber") pour votre "order_id" spécifique et ajouter 1 à celui-ci. S'il s'agit du premier élément, vous insérez un élément à une commande, la coalesce (___, 0) + 1 vous donnera un 0 + 1, au lieu de NULL + 1 (qui ne fonctionnerait pas).

Si vous devez ajouter plusieurs éléments à plusieurs commandes (ce qui n'est pas aussi commun pour la plupart des applications), vous pouvez le faire de manière légèrement plus compliquée, mais en utilisant le même principe:

-- Assume you want to add items to orders 1 and 2
WITH "orderItemsToAdd" ("orderId", "orderItem") AS
(
    VALUES 
    (1, 'first item, 1st order'), 
    (1, 'second item, 1st order'),
    (2, 'first item, 2nd order'),
    (2, 'second item, 2nd order'),
    (2, 'third item, 2nd order')
)

-- Get the max item numbers (or zero, via coalesce) for each order

, "maxItemNumbers" AS
(
SELECT
    "orderId", coalesce(max("itemNumber"), 0) AS "baseItemNumber"
FROM
    "orderItemsToAdd"
    LEFT JOIN "OrderItem" USING("orderId")
GROUP BY
    "orderId"                    
)

-- We insert new items, using row_number() + baseItemNumber as the new item numbers
-- (NOTE: orderItemsToAdd should, in practice be ORDERed BY something!)
INSERT INTO 
    "OrderItem" ("orderId", "itemNumber", "orderItem")
SELECT
    "orderId", "baseItemNumber" + (row_number() over (partition by "orderId")), "orderItem"
FROM
    "orderItemsToAdd" 
    JOIN "maxItemNumbers" USING("orderId")
RETURNING
    * ;

C'est ce que vous obtiendrez:

    +--------------------------------+
    ¦ 1 ¦ 1 ¦ first item, 1st order  ¦
    ¦---+---+------------------------¦
    ¦ 1 ¦ 2 ¦ second item, 1st order ¦
    ¦---+---+------------------------¦
    ¦ 2 ¦ 1 ¦ first item, 2nd order  ¦
    ¦---+---+------------------------¦
    ¦ 2 ¦ 2 ¦ second item, 2nd order ¦
    ¦---+---+------------------------¦
    ¦ 2 ¦ 3 ¦ third item, 2nd order  ¦
    +--------------------------------+

Vous pouvez le vérifier à REXTER .

Vous devez être au courant de:


En tant que note latérale, si vous utilisez PostgreSQL, je conseillerais d'utiliser underscored_lowercase_identifiers Au lieu de camelCaseOnes. Vous économisez beaucoup de temps à ne pas avoir à taper " (Et le risque d'oublier) ;-)


Remarque secondaire: cette approche peut entraîner des conditions de course (et des transactions à abandonner, puis de récupérer) si beaucoup Les gens essaient de changer le même ordre à le même temps. Voir les commentaires par @erwin.

0
joanolo