Présentation du scénario
Dans ce scénario, j’étudie les variations journalières (intraday) d’une action. L’objectif est de compter le nombre de jours où la variation est inférieure à 1%, à 2% , à 3% etc., en tenant compte du volume d’actions échangées (<1 million ou >1 million) :
Pour cela je dispose d’une table ASML demo et de mesures me précisant :
- La date (c’est une colonne)
- Un index (colonne indiquant un numéro unique pour chaque ligne de la table)
- Le plus bas (mesure _bas)
- Le plus haut (mesure _haut)
- Le volume (mesure _vol)
- Le nombre de jours (mesure définie par COUNT(‘ASML demo’[Date]))
- La variation intraday (mesure définie par ([_haut] – [_bas]) / [_bas])
Pour créer le segment volumétrie, je crée cette fois-ci une colonne définie par
volumétrie =
IF(
[_vol] > 1000000,
">1M",
"<1M"
)
Jusque là pas de message d’erreur.
Pour créer l’axe X (horizontale) de mon graphique, je dois également créer une colonne (la mesure ne permet pas de créer cet axe) : il s’agit de multiplier la variation par 100 et de prendre l’arrondi supérieur. C’est l’amplitude de variation que vous voyez sur le graphique. La première idée est donc :
groupe variation =
"<" & CEILING( [_variation intraday] *100 , 1 ) &"%"
C’est à ce moment que l’erreur liée à la dépendance circulaire apparaît.
Pourquoi une erreur liée à la dépendance circulaire ?
La dépendance circulaire, comme son nom l’indique, survient lorsque qu’une colonne B fait référence lors de son calcul à une colonne A, qui elle-même fait référence à la colonne B.
Ce phénomène ne se produit que parce que les deux calculs sont des colonnes ! Une dépendance circulaire n’est pas possible avec une mesure.
(Notez qu’il existe aussi des cas plus rares de dépendance entre deux tables, dans certaines conditions)
Pourtant, dans la formule
"<" & CEILING( [_variation intraday] *100 , 1 ) &"%"
Je ne fais pas référence à une autre colonne !
Et c’est que vous ne devez pas oublier trois règles fondamentales du DAX : tout d’abord, première règle, lors de l’appel d’une mesure dans une formule (ici la mesure _variation intraday), un CALCULATE est toujours implicitement ajouté.
Deuxième règle fondamentale : lorsqu’il est appelé dans un contexte de ligne, CALCULATE déclenche une transition de contexte. Ici, parce que nous sommes en train de créer une colonne, nous sommes bien dans un contexte de ligne.
Troisième règle fondamentale : une transition de contexte consiste à transformer un contexte de ligne en un ensemble de filtres équivalents, et concrètement, à filtrer chacune des autres colonnes de la table.
La boucle est bouclée ! La transition de contexte déclenchée par le CALCULATE implicite dû à l’appel de la mesure _variation intraday fait bien référence aux autres colonnes de la table, et par conséquent à la colonne volumétrie. Mais la colonne volumétrie elle-même fait appel à la mesure _vol : celle-ci introduit donc un CALCULATE implicite et une transition de contexte, qui fait référence à la colonne groupe variation.
En définitive, et de manière implicite, la dépendance circulaire vient donc du fait que groupe variation fait référence à volumétrie qui fait elle-même référence à groupe variation
Mais alors comment s’en sortir ?
La première façon d’éviter cette erreur est d’éviter de créer des colonnes ! Créer des mesures le plus souvent possible est la quatrième règle fondamentale du DAX, et vous ne devez recourir aux colonnes que dans des cas bien précis (dont ceux proposés dans ce scénario).
Dans le cas où la formule doit être une colonne, il faut donc s’arranger pour que le CALCULATE implicite ne déclenche pas une transition de contexte sur toutes les colonnes de la table. Or, et c’est là le secret, si une colonne de votre table permet d’identifier de manière unique chaque ligne, DAX n’aura pas besoin de filtrer toutes les colonnes dans le cadre d’une transition de contexte.
Dans notre scénario, deux colonnes répondent à cette condition : la date et l’index (notez que ce dernier peut être ajouté à l’aide de Power Query).
Je vais donc réécrire la formule de groupe variation, et écrire explicitement le CALCULATE et son filtre, en faisant appel à la fonction ALLEXCEPT, qui enlève tous les filtres sauf celui de la colonne indiquée :
groupe variation =
"<" &
CALCULATE(
CEILING([_variation intraday]*100,1),
ALLEXCEPT('ASML demo', 'ASML demo'[Index])
)
&"%"
Conclusion
N’oubliez pas les quatre règles fondamentales du DAX :
- L’appel d’une mesure ajoute un CALCULATE implicite
- Dans un contexte de ligne, CALCULATE déclenche une transition de contexte
- Une transition de contexte pose un filtre sur toutes les colonnes de la table
- Ne créez des colonnes que lorsque vous n’avez pas le choix
Et pour ce qui concerne la dépendance circulaire, dans le cadre de la création d’une colonne, le motif explicite CALCULATE (ALLEXCEPT()) est recommandé dès que vous faite appel à une mesure, c’est-à-dire très souvent.
Post-scriptum
Une erreur de débutant consisterait à créer la formule suivante sous forme d’une colonne :
En effet, le filtre ‘ASML demo'[Volume] > 1500000 est traduit par DAX en
FILTER(
ALL(ASML demo) ,
'ASML demo'[Volume] > 1500000)
)
C’est le fait que la fonction ALL porte sur toute la table qui génère l’erreur. Là encore, utiliser ALLEXCEPT permet de régler le problème :
Mais rappelons-le, ici, l’erreur principale c’est de vouloir faire de cette formule une colonne : c’est clairement sous forme d’une mesure qu’il faut faire ce calcul.