[Database design] Versionnement

J’ai un petit problème avec le design de ma base de données pour un projet.

J’ai une application basée sur un produit central, appelons le “Produit” qui peut avoir plusieurs versions. Lorsqu’un utilisateur demande à créer une nouvelle version d’un produit, j’aimerais que l’ancienne version soit gardée dans son état (un champs permettra de dire que son statut n’est plus actif) et qu’une nouvelle version de ce produit soit créé, en incrémentant un numéro de version mais en copiant toutes les autres informations.

Si je résume, j’ai un produit qui a plusieurs informations (un nom, une date de création, un status, un vendeur et des coûts supplémentaires) et j’aimerais pouvoir faire du versionnement.

Après un peu de réflexion je suis arrivé à deux possibilités :

Produit (ProduitID, Nom, DateCreation, StatusID, VendeurID)
Status (StatusID, Nom) // le seul moyen que j’ai trouvé pour faire une énumération… qqn a une meilleure idée??
Vendeur (VendeurID, Nom, Prénom)
CoutSupp (CoutSuppID, Nom, Valeur, ProduitID)
Version (VersionID, ProduitID, VersionDate, Commentaire, PremierProduitID)

Où PremierProduitID est une clé étrangère liée à ProduitID de la table Produit

Produit (ProduitID, Nom, DateCreation, StatusID)
Status (StatusID, Nom) // le seul moyen que j’ai trouvé pour faire une énumération… qqn a une meilleure idée??
Vendeur (VendeurID, Nom, Prénom)
CoutSupp (CoutSuppID, Nom, Valeur, VersionID)
Version (VersionID, ProduitID, VersionDate, Commentaire, VendeurID)

Dans la première méthode, pour chaque nouvelle version j’aurais un nouvel enregistrement dans la table Produit et toutes mes informations correspondant à un produit seront dans la table produit, la table version ne contient que les informations correspondant à la version, comme son commentaire et sa date de création. Si je modifie les informations d’une version, ça ne modifie pas les informations des autres versions, on est bon. Par contre j’ai une clé qui est formée de deux champs dans la table version, ce qui n’est pas, je crois, normalisé et semble foutre un peu la m… avec mes objets Linq to SQL.

Dans la seconde méthode, j’aurais les informations sur mon produit splittées entre deux tables. Toutes les informations qui sont uniques à toutes les versions d’un produit sont dans la table produits. Les informations qui peuvent être variables entre les versions sont dans ma table Version. Je devrais faire un join pour avoir toutes les informations d’un produit particulier, mais il sera plus aisé de gérer ce qui doit identique pour toutes les versions d’un produit ou ce qui est pour une version particulière d’un produit.

Qu’est-ce que vous en pensez? Est-ce que par hasard qqn a une meilleure idée?

Edit: Ah oui, encore une chose. Pour les coûts supplémentaires et quelque autres tables du même genre, je dois aussi garder une sorte d’historique, c’est à dire que si je crée une nouvelle version d’un produit, je dois forcément recréer des coûts supplémentaires identiques à ceux qui étaient présents dans l’ancien produit et les lier à la nouvelle version.

Je n’ai pas compris si VersionID c’est un identifiant uniquement technique que tu n’afficheras pas, ou le numéro de ta version connu par l’utilisateur.

Si c’est le numéro de ta version connu par l’utilisateur ça voudrait dire que VERSION (VersionID, ProduitID,…) ne fonctionnerait pas car tu ne pourrais pas avoir, par exemple, qu’une version 3 pour un seul produit a, la PK t’empêcherait d’avoir la même version 3 pour un autre produit b.

exemple
Table Produit : ProduitID=A
Table VERSION: Produit ProduitID=A, VersionID=1 pour le produit A version 1
Table VERSION : ProduitID=B, VersionID=1 => impossible d’avoir un produit B car la version1 est déjà utilisée.

Donc dans ce cas là il faudrait faire
PRODUIT (ProduitID, ,…)
VERSION (ProduitID,VersionID, …) avec une contrainte d’intégrité entre Version.ProduitID qui vérifie son existence dans Produit.ProduitID.

Dans le cas contraire si VersionID n’est qu’un identifiant technique

PRODUIT (ProduitID, ,…)
VERSION (VersionID,ProduitID,version_num, …) avec une contrainte d’intégrité entre Version.ProduitID qui vérifie son existence dans Produit.ProduitID.
mais avec une clé unique sur (ProduitID,num_version)

Exemple:
Table Produit : ProduitID=A
Table VERSION: Produit ProduitID=A, VersionID=1, version_num=10 => pour le produit A version 10
Table VERSION : ProduitID=B, VersionID=2,version_num=10 => pour le produit B version 10

Je me posais la question de savoir quelle version on choisit ( max dernière version ?) , mais ta demande simplifie le problème il suffit de rajouter dans la table VERSION un flag oui/non de version courante.

Si le nom du produit est inchangeant tu le mets dans produit, s’il est relatif à la version tu le mets dans VERSION

[quote]J’ai un petit problème avec le design de ma base de données pour un projet.

J’ai une application basée sur un produit central, appelons le “Produit” qui peut avoir plusieurs versions. Lorsqu’un utilisateur demande à créer une nouvelle version d’un produit, j’aimerais que l’ancienne version soit gardée dans son état (un champs permettra de dire que son statut n’est plus actif) et qu’une nouvelle version de ce produit soit créé, en incrémentant un numéro de version mais en copiant toutes les autres informations.

Si je résume, j’ai un produit qui a plusieurs informations (un nom, une date de création, un status, un vendeur et des coûts supplémentaires) et j’aimerais pouvoir faire du versionnement.[/quote]
Je me suis arrété là, c’était trop long pour un vendredi soir post réunion pour lire la suite.

Moi je ferais ca comme ca (en y réflechissant 20 secondes)

Un table “Produit” avec :
Tous_les_champs_du_produit
NouvelleVersion(id)
Champs_bonus

Avec NouvelleVersion de meme nature que ta clé/identity/guid de produit. Si c’est la derniere version, le champs est vide ou null, sinon il a l’id/clé/guid de la version précédente. Ca te permet de retracer facilement tout l’historique du produit et de ne sélectionner que les dernières versions. Ca t’économise aussi un paquet de requetes (à vue de nez hein) et de Join dans tous les sens. Si t’a des champs spécifiques à ta version, tu les rajoute en bout de table avec un NULL par défaut, etc…

Bishop tu veux faire une unique table produit, sans table versions, avec le numéro de version dans cette table produit ?

L’inconvénient de cela, à part que cela ne sera pas une forme normale, c’est que si on joint cette table avec une table appelante on va se faire des produits cartésiens à moins de mettre systématiquement un filtre sur la version. Filtre qui rajoute de complexité sur des outils comme GENIO ou BO.

Tu vas me dire qu’il faudra ce filtre sur la table version. Oui mais ça sera une forme normale et on ne rajoutera la version qu’en cas de besoin.

Autant je pourrais adhérer à ta solution, en freinant des 4 fers, s’il n’y avait que cette table, mais visiblement Gimly a besoin d’historiser pas mal d’autres tables. Et ça va être un beau bordel si tout est dénormalisé avec des tables avec versions se joignant avec d’autres tables avec versions. Sans parler des problèmes de clé étrangère.

Gimly ça me fait penser. Tu as besoin des versions tout le temps ou c’est de l’historisation ? Si c’est de l’historisation tu peux créer une deuxième base centré sur les versions qui sera un datawarehouse avec modélisation en étoile, et la première base n’aura que la version courante.

Mmh en fait j’aurais besoin des versions tout le temps, l’utilisateur pouvant ouvrire une ancienne version d’un produit (en lecture seule) ou faire d’une ancienne version la version actuelle, et donc refaire une nouvelle version avec une ancienne version.

Pour ta première question, VersionID est bien juste un ID technique et ne sera pas montré à l’utilisateur. Je vais donc bien partir avec une table Produits et une table Versions, où je mettrais les choses globales dans Produits et les choses spécifiques à une version dans Versions.

Sinon, pour les éléments qui sont en fait des types énumératifs, il n’y a pas un moyen un peu moins crade que d’avoir une table pour ça? J’utilise SQL Server 2005, j’ai cherché un peu s’il n’existait pas de type énumératifs dedans, mais je n’ai rien trouvé.

C’est pas crade d’avoir une table, c’est la maniere dont c’est censé etre fait.

Oui bon, dire que c’est “crade” c’est un poil exagéré, mais comme j’utilise Linq to SQL, dans mes objets j’aurais préféré avec un type énumératif pour Status à la place d’avoir un type Status (qui représente ma table Status).

Ca m’aurais permis d’écrire qqch du genre :
if(monProduit.Status == StatusProduit.EnCours)

à la place d’avoir :
if(monProduit.Status.Name == “EnCours”)

M’enfin… c’est compréhensible dans les deux cas, seulement puisqu’on a le type énumératif en C# je trouve dommage de pas l’utiliser quand on en a l’occasion :).

Et http://blogs.msdn.com/dinesh.kulkarni/arch…ap-an-enum.aspx ou http://davidhayden.com/blog/dave/archive/2…nceMapping.aspx non?

Tout dépend de quel point de vue on se place. Mais si je me place du côté base de données en puriste c’est ta solution qui est crade de mettre une énumération comme type de colonne. :slight_smile:

Mais on peut très bien partir d’une base de donnée clean (table status) et au moment du chargement en C# tu le transformes en code clean (énumération).

edit: ce que proposes Glop d’ailleurs, je n’avais lu ses liens qu’en diagonale la première fois.

Ouais, j’étais déjà tombé sur le blog de David Hayden et sur ce billet en particulier, mais ce que je n’aime pas avec cette solution c’est qu’il faut synchroniser le code et la base de données. Si je veux rajouter une énumération dans le type énumératif, je dois rajouter à la main la ligne dans la base de données. Ou alors, je ne met qu’une valeur entière comme représentation de mon énumération dans la base de données, mais dans ce cas je perds un peu de la clarté dans les données.

Ou alors j’ai raté un truc?

Non c’est comme ca avec LINQ to SQL de base, LINQ to Entities est surement plus evolue, mais comme dit le monsieur:

Je ne sais pas si j’arrive après l’orage, mais juste pour revenir à tes besoins principaux et essayer de les reformuler, c’est comme si tu avais un modèle de série (un « produit ») avec plusieurs options possibles. Un employé arrive et crée un package en sélectionnant des options pour ce modèle, ce qui devient une version packagée de ton « produit » (la « nouvelle version »). Tu voudrais aussi garder un historique de tous les changements, c’est bien ça ?

Dans ce cas, une table produit, une pour les options, une pour la version (ex : prix final, nom public de la version, création, statut,…) et une table d’association entre ces 3-là devraient suffire. Ca nous fait donc 4 tables principales (avec chacune sa table de log associée) si jamais tu voulais conserver de la flexibilité.

Sinon, +1 pour les 2 du dessus, les énums, garde-les dans ton code. :slight_smile: