VALUES dans les itérateurs DAX

VALUES dans les itérateurs DAX : le contexte caché qui change tout

13 avril 2026 | Power BI

Pourquoi utiliser VALUES à l’intérieur de SUMX, AVERAGEX ou RANKX peut transformer radicalement vos résultats — et comment en tirer profit.

Table des matières
  1. Ce que VALUES fait vraiment
  2. VALUES vs la table directe : une question de granularité
  3. Le problème : itérateurs sans VALUES
  4. VALUES dans SUMX
  5. VALUES dans AVERAGEX
  6. Cas avancés : RANKX et MAXX
  7. Quand ne pas utiliser VALUES
  8. Récapitulatif des patrons

1. Ce que VALUES fait vraiment

VALUES(<colonne>) retourne une table d’une seule colonne contenant les valeurs uniques visibles dans le contexte de filtre courant. Contrairement à ALL() , qui ignore les filtres, VALUES() les respecte, incluant les filtres de relations et ceux appliqués depuis des segments ou des pages du rapport.

Elle inclut aussi une rangée vide si des relations avec intégrité référentielle incomplète sont présentes. C’est important : DISTINCT() ne l’inclut pas, VALUES() oui.

Fonction Syntaxe Comportement
VALUES() VALUES(Table[Col]) Valeurs uniques visibles dans le contexte de filtre actuel. Inclut la rangée vide si applicable.
DISTINCT() DISTINCT(Table[Col]) Valeurs uniques du contexte de filtre actuel. N’inclut pas la rangée vide des relations.

Dans la grande majorité des cas à l’intérieur d’itérateurs, VALUES et DISTINCT produisent le même résultat. La différence importe quand vous avez des relations avec des valeurs orphelines.

 


2. VALUES vs la table directe : une question de granularité

Avant de plonger dans les patrons, il faut comprendre pourquoi on utilise VALUES(colonne) plutôt que de passer la table directement à un itérateur. La réponse tient en un mot : granularité.

Considérez une table Produits avec 10 000 rangées réparties dans 5 catégories :

-- Itère sur les 10 000 rangées de la table
SUMX(Produits, [Marge])

-- Itère sur les 5 catégories uniques visibles
SUMX(VALUES(Produits[Catégorie]), [Marge])

Dans le premier cas, l’itérateur parcourt chaque rangée individuellement. Dans le second, il parcourt seulement les valeurs uniques d’une colonne. C’est l’équivalent conceptuel de la différence entre un scan complet de table et un GROUP BY en SQL.

Cette distinction change le résultat quand votre expression calcule des ratios ou des moyennes. Sommer une marge rangée par rangée n’est pas la même chose que sommer une marge par catégorie, parce que les ratios ne s’additionnent pas de façon linéaire.


3. Le problème : itérateurs sans VALUES

Les itérateurs comme SUMX, AVERAGEX ou RANKX reçoivent une table comme premier argument et créent un contexte de rangée pour chaque élément de cette table. Quand une mesure est référencée dans l’expression du deuxième argument, DAX effectue automatiquement une transition de contexte : le contexte de rangée est converti en contexte de filtre, ce qui permet à la mesure de s’évaluer au bon niveau de granularité.

Le choix de la table du premier argument détermine donc quels filtres sont actifs pendant cette évaluation.

Considérez ce modèle : une table Ventes avec les colonnes Produit, Montant et Quantité. On veut calculer la moyenne du ratio ventes/quantité par produit.

Version avec ALL :

-- Itère sur TOUS les produits, même ceux filtrés par un slicer
FormulaAvecAll =
AVERAGEX(
ALL(Ventes[Produit]),
DIVIDE([TotalVentes], [TotalQuantité])
)

Le problème ici n’est pas que les mesures s’évaluent au niveau global : grâce à la transition de contexte, [TotalVentes] et [TotalQuantité] s’évaluent bien par produit à chaque itération. Le problème, c’est que ALL() ignore les filtres externes. Si un slicer de catégorie est actif dans le rapport, ALL va quand même itérer sur tous les produits, pas seulement ceux de la catégorie sélectionnée.

Version corrigée avec VALUES :

-- Itère seulement sur les produits visibles dans le contexte de filtre
FormulaAvecValues =
AVERAGEX(
VALUES(Ventes[Produit]),
DIVIDE([TotalVentes], [TotalQuantité])
)

</div

À retenir : ALL() dans un itérateur enlève les filtres externes. VALUES() les préserve. La transition de contexte (contexte de rangée → contexte de filtre) se produit dans les deux cas quand une mesure est référencée.

4. VALUES dans SUMX

Le patron classique : on itère sur la liste des valeurs uniques d’une colonne, et pour chacune, la transition de contexte crée un contexte de filtre temporaire. Les mesures s’évaluent alors correctement à ce niveau de granularité.

Exemple : marge pondérée par catégorie

MargeParCatégorie =
SUMX(
VALUES(Produits[Catégorie]), -- itère sur chaque catégorie visible
DIVIDE(
[TotalMarge],
[TotalVentes]
)
)

Pour chaque rangée retournée par VALUES(Produits[Catégorie]), la transition de contexte filtre automatiquement le modèle sur cette catégorie. Les mesures [TotalMarge] et [TotalVentes] s’évaluent donc dans ce contexte restreint.

Résultat simulé – contexte : toutes catégories

Catégorie Marge
Électronique 34,2 %
Vêtements 58,7 %
Alimentation 22,1 %
Somme des marges 115,0 %
Ce total dépasse 100 % car on somme des ratios, pas des montants. C’est intentionnel dans ce type de calcul pondéré.

5. VALUES dans AVERAGEX

Même logique, mais on calcule une moyenne sur les valeurs par segment. Un cas d’usage très courant : la moyenne du panier moyen par client, en évitant que les gros clients ne biaisent le calcul global.

PanierMoyenParClient =
AVERAGEX(
VALUES(Clients[ClientID]),
DIVIDE(
[TotalVentes],
[NbCommandes]
)
)

Ici, VALUES(Clients[ClientID]) génère une rangée par client visible. Pour chacune, la transition de contexte évalue les mesures dans le contexte de ce client précis. Pas besoin d’envelopper les mesures dans CALCULATE : les mesures déclenchent automatiquement la transition de contexte. L’écriture explicite avec CALCULATE est une question de style, pas une nécessité.

La moyenne finale est une moyenne des paniers individuels, ce qui est sémantiquement différent de DIVIDE([TotalVentes], [NbCommandes]) calculé globalement.


6. Cas avancés : RANKX et MAXX

Dans RANKX, VALUES est essentiel pour construire la table de référence du classement en respectant le contexte de filtre courant.

RangProduit =
RANKX(
ALL(Produits[Produit]), -- tous les produits pour le classement complet
CALCULATE(
[TotalVentes],
VALUES(Produits[Catégorie]) -- mais en gardant le filtre de catégorie
),
,
DESC,
DENSE
)

Ce patron combine ALL() pour élargir la table de référence et VALUES() pour préserver un filtre externe spécifique — typiquement celui d’un segment catégorie actif dans le rapport.

MAXX avec VALUES imbriqué

-- Meilleure vente journalière, pour chaque produit visible
MeilleureJournée =
MAXX(
VALUES(Calendrier[Date]),
CALCULATE([TotalVentes])
)

7. Quand ne pas utiliser VALUES

VALUES n’est pas toujours la bonne réponse. Il y a des situations où d’autres fonctions sont préférables.

Utilisez ALL() si…

Vous voulez ignorer tous les filtres pour calculer une proportion sur le total absolu (ex. % du total général).

Utilisez ALLSELECTED() si…

Vous voulez le total visible dans le rapport (hors filtres de la visualisation courante) pour un % du total sélectionné.

VALUES dans un itérateur sur une table volumineuse (millions de rangées) peut impacter la performance. Préférez travailler sur des colonnes de dimensions, pas des tables de faits directement.

8. Récapitulatif des patrons

Patron Utilisation Filtre respecté ?
SUMX(VALUES(col), mesure) Somme d’une mesure par valeur unique Oui
AVERAGEX(VALUES(col), mesure) Moyenne par segment (ex. client, produit) Oui
RANKX(ALL(col), CALCULATE(..., VALUES(...))) Classement global avec filtre partiel préservé Partiel
MAXX(VALUES(col), CALCULATE(mesure)) Maximum d’une mesure sur une dimension Oui
SUMX(ALL(col), mesure) Calcul ignorant tous les filtres Non
La règle d’or : si vous voulez que votre mesure réagisse aux filtres du rapport à l’intérieur d’un itérateur, utilisez VALUES. Si vous voulez tout ignorer, utilisez ALL.