mardi 11 octobre 2011

Comportement du Many to Many avec plusieurs dimensions de jointure

Chez nos clients préférés, nous tombons bien souvent sur une modélisation un peu bizarre d'un cube AS. Bien sûre, il y a toujours une justification à cela car cette modélisation a été mise en place pour répondre à un besoin bien précis.
Voici ladite modélisation (ça sera notre cas n°1) :





Un acheteur (DimAcheteur) se charge de l'approvisionnement aurpès des fournisseurs (FaitCommandeFournisseur). Les acheteurs sont associés à des groupes (DimGroupe) et un acheteur peut appartenir à plusieurs groupes, d'où la présence de la table de fait FaitGroupeAcheteur qui va permettre de faire le lien entre mes acheteurs et mes groupes via un Many-to-Many. De cette manière, on pourra faire une analyse des quantités commandées par groupe d'acheteur par exemple. Jusqu'ici, une Many-to-Many classique :




L'élément perturbateur, c'est la notion de responsable (DimResponsable). Un responsable, c'est un acheteur particulier, qui est responsable d'un groupe d'acheteur. On se dit tout de suite que nous devrions avoir une Parent-Child sur notre dimension DimAcheteur afin de créer cette relation hiérarchique ou a minima, que l'association avec FaitCommandeFournisseur générera des role playing dimension. Ici, rien de cela, la modélisation proposée sera la suivante (cas n°2) :




Bien évidemment, avant de se soucier de notre Many-to-Many, il y a clairement un problème de conception de notre DimAcheteur/DimResponsable. Mais imaginons que les changements ne peuvent être apportés tout de suite (trop d'impact, pas assez de temps, release de reporting qui approche, pression des utilisateurs pour avoir les groupes pour les repsonsables et les acheteurs...). Donc  passons afin de nous attarder sur la tentative d'association de tous ces objets.
Le but est toujours d'avoir la possibilité d'aggréger nos données fournisseurs en fonction du groupe d'acheteur. On pense donc à créer une relation de type Many-to-Many en plus de celle présente à la base. On obtiendra quelque chose de la sorte :




Et là on se dit : "Super, étant donné que mes reponsables sont aussi des acheteurs, mes clés vont correspondre avec ma table de fait intermédiaire". C'est vrai, mais le processing ne passera pas et les résultats de vos requêtes seront fausses. Pourquoi? Pour le premier point, étant donné que DimResponsable est un sous ensemble de DimAcheteur, il y a de grandes chances d'avoir des KeyErrors (dans le sens tous les acheteurs ne sont pas responsables). Donc soit on complète DimResponsable avec les lignes manquantes, soit on positionne le Key Not Found sur Ignore Error pendant le processing. Toujours dans un soucis de delivery, on préférera la seconde solution (bien évidemment, les deux propositions sont inacceptables en production, j'espère que vous l'aurez remarqué, c'est juste pour l'exemple). En ce qui concerne le second point, il faut revenir à la définition de ce qu'est une Many-to-Many.
Une Many-to-Many va chercher à associer des tables de faits entre elles. Pour cela, AS utilisera une dimension commune à ces deux tables : c'est pour cela que dans le titre je parle de dimension de jointure. Dans notre premier cas qui fonctionne, c'est DimAcheteur qui sera utilisée.
Mais dans le cas ou deux dimensions sont communes, comme dans l'exemple ci-dessus, laquelle sera choisie? DimAcheteur ou DimResponsable? Et bien ça depend du type de dimension : pour en savoir plus, je vous renvoie au Many-to-Many Revolution de Marco Russo (disponible ici). Ici, les deux seront utilisées (elles possèdent le même type d'association avec notre table de fait intermédiaire et la même granularité).

Que ce passe t-il quand je vais vouloir requêter mon groupe de mesure avec ma dimension DimGroupe?
Soit la requête suivante :

SELECT[Measures].[Quantite Commandee] ON 0,
NON EMPTY [DimGroupe].[Groupe].[Groupe]*[DimAcheteur].[Acheteur].[Acheteur] ON 1
FROM [Cube]

SSAS va créer deux sous-cubes : un premier pour faire les lookups sur le groupe de mesure FaitGroupeAcheteur et un second pour aller chercher les données fournisseur (bien entendu, le lookup est la première opération réalisée). Dans le premier sous-cube, nous allons avoir trois colonnes : l'AcheteurID, le ResponsableID et le GroupID. Dans le second, ce que je projette : ici, l'AcheteurID et le GroupeID. Suite à cela, une comparaison sera faite entre les colonnes du premier  et du second sous-cube. C'est à ce moment que quelque chose cloche, car AS va faire ces correspondances pour toutes les dimensions associées au groupe de mesure factless, donc DimAcheteur et DimResponsable. Du coup, nous nous retrouverons seulement avec les lignes de valeurs pour lesquelles le responsable et l'acheteur pour une commande fournisseur sont les mêmes. Les agrégations par groupe d'acheteur seront fausses car plusieurs valeurs de la mesure sont omises. On le voit bien dans ce profiler :


En rouge, c'est la génération de mon sous-cube de Lookup, en bleu, la restitution de mes résultats. Pour la partie rouge, on voit que toutes mes dimensions attachées à mon groupe de mesure FaitGroupeAcheteur sont utilisées (1,1,1).

Bref, c'était juste une illustration de ce qu'il ne faut pas faire et du pourquoi ça ne fonctionne pas. J'ai bien tenté de trouver un cas plus concret d'utilisation d'un Many-to-Many avec plusieurs dimension de jointure, mais sans succès.

3 commentaires:

  1. Vincent Rainardi avait écrit un truc du même genre : http://dwbi1.wordpress.com/2011/05/28/many-to-many-which-dimension-is-used/.
    Merci Romuald pour la précision

    RépondreSupprimer
  2. Il me semble qu'avant d'être un problème du Many to Many, c'est un problème de modélisation de la relation (ternaire) Groupe-Acheteur-Responsable. Si on enlevait FaitCommandeFournisseur du tableau, le problème de modélisation subsisterait.

    La phrase "Un responsable, c'est un acheteur particulier, qui est responsable d'un groupe d'acheteur" suggère une relation directe DimGroup -> DimResponsable.

    RépondreSupprimer
  3. Oui, je suis complètment d'accord, c'est avant tout un problème de modélisation et je l'ai d'ailleurs précisé dans l'article. Mais parfois, nous sommes obligés, sous la contrainte du temps, de tenter des implémentations particulières (ici, sous AS).

    RépondreSupprimer