PowerView for Multidimensional Models (a.k.a. DaxMD) est sorti!

Ca y est. C’est officiel et on vous l’avait dit aux TechDays, ça n’allait pas trainer: DaxMD, qui apporte le support du DAX à SSAS Multidimensionnel est sorti. Il est inclus dans un CU – ce qui est certes assez inhabituel pour un tel ajout de fonctionnalité – et vous permet d’interroger des cubes en DAX à travers une couche de métas CSDL.

Ce que ça apporte? De meilleures performances au niveau fin, le support de PowerView dans Sharepoint. Pour Excel la réponse est:

Enabling this scenario is a top priority for the team but we do not have any updates on the roadmap or timeframe to share at this time.

Ce que l’on pourrait traduire par:

Cette fonctionnalité est une des principales priorités de l’équipe, mais nous ne pouvons pas encore communiquer sur une roadmap ou une date de sortie.

Wait and see donc. En attendant ça se passe ci-dessous!

download_iconTélécharger SQL Server 2012 SP1 CU4.

Et pour la tester et valider l’intérêt technique, quoi de mieux que d’essayer quelques scénarios de notre session sur le sujet avec Romuald?

A bientôt!

PS: Allez jeter un œil au billet de Chris, dont l’analyse politique de cette sortie est très intéressante.

PPS: Et la documentation MSDN a été mise à jour. Elle est elle aussi très instructive: http://msdn.microsoft.com/en-us/library/dn198310.aspx.

Publicités

Techdays 2013, épilogue

TD13

Bonjour à tous, un petit peu crevé après cette préparation éreintante de session qui, d’après les premiers retours, vous a pas mal plu! Je vous rassure on s’est bien amusés avec le grand – autant par les New Rock que par le talent – Romu aussi. Sur un plan plus perso la préparation quelque peu tardive m’a empêché de venir aux sessions qui m’intéressaient mais avec Romuald nous avions des slides à finir à la force du poignet (oui je sais Aurélien ceci est une trèèès mauvaise blague, et sache que toute la salle était avec toi, bon rétablissement mon poulet!).

Merci en tous les cas aux présents, je pense à Pascale, Flo, David, Fab, Rom,  Dominique et j’en oublie, ainsi qu’à tous ceux qui sont venus nous faire leur retours à la sortie, ça fait chaud au coeur, vraiment!

Démos, Director’s Cut

Bon comme on a été un petit peu à la bourre je voulais revenir sur quelques démos histoire que les passages peu clairs le soient un peu moins. On va faire ça dans l’ordre, et ça concerne surtout la partie DaxMD, aussi connu sous le nom de PowerView for Multidimensional Models, CTP publique d’une version du moteur OLAP supportant le DAX, disponible ici. Au passage et ce depuis la session, David Joubert a posté un excellent test de la conversion de modèles tabulaires vers du MD qui fait un peu le même job que certaines de nos démos.

CSDL Metadata

On vous l’a dit, la raison pour laquelle on peut envoyer du DAX à un modèle multidimensionnel – qui permet à PowerView de s’y connecter – dans cette CTP tient en deux raisons

  • Un Formula Engine DAX côté multidimensionnel, capable d’interroger un SE MOLAP
  • Des métadonnées au format CSDL, le format normé de Microsoft introduit avec Entity Framework qui permet de décrire un modèle de données. Pour étendre le CSDL aux données d’analyse, Microsoft a publié une extension au CSDL nommée CSDL-BI.

Pour requêter les métadonnées CSDL, un Rowset XMLA (ou une DMV) existe. Il suffit de passer le nom de la base, du cube (oui car en multidimensionnel on peut avoir plusieurs cubes!) dans le champ… PERSPECTIVE_NAME (Oui bon c’est une CTP hein), et un champ non documenté, VERSION, qui dit que… qui dit 2.0. (Je vous ai déjà dit que c’est une CTP?) Voilà.

<Envelope xmlns=”http://schemas.xmlsoap.org/soap/envelope/”>
       <Body>
              <Discover xmlns=”urn:schemas-microsoft-com:xml-analysis”>
                     <RequestType>DISCOVER_CSDL_METADATA</RequestType>
                     <Restrictions>
                           <RestrictionList>
                                 <CATALOG_NAME>AdventureWorksDW2012Multidimensional-EE<CATALOG_NAME>
                                 <VERSION>2.0</VERSION>
                                 <PERSPECTIVE_NAME>Adventure Works</PERSPECTIVE_NAME>
                           </RestrictionList>
                     </Restrictions>
                     <Properties>
                           <PropertyList>
                                 <FORMAT>Tabular</FORMAT>
                           </PropertyList>
                     </Properties>
              </Discover>
       </Body>

PowerView comprend ça. Pas vous. C’est normal. Mais ça vous montre qu’on a dorénavant une couche de métadonnées dans SSAS (ce qui n’était pas le cas avant Tabular).

Drill-through en MDX, en DRILLTHROUGH ou en DAX?

Pour faire des extractions de détail en MDX, on a basiquement deux solutions. Faire des requêtes MDX bien barbares, ce qui est assez lent, mais pas compliqué à écrire (et au passage ce que génère Excel PivotTable ou SSRS au Designer).

SELECT
{
	[Measures].[Reseller Sales Amount]
}
ON 0
,
NON EMPTY
{
	[Date].[Date].[Date]
	*[Geography].[City].[City]
	*[Product].[Product].[Product]
	*[Reseller].[Reseller].[Reseller]
	*[Sales Territory].[Sales Territory Region].[Sales Territory Region]
}
DIMENSION PROPERTIES [Reseller].[Reseller].[Bank Name]
ON 1
FROM [Adventure Works]

Quand on est bon, on peut faire des requêtes de DRILLTHROUGH sur une cellule. Les vieux de la vieille du RS 2000 faisaient ça avec de l’OLE DB for OLAP dans SSRS (c’est faisable aussi avec le provider AS, mais en mode mining, éditeur coupé). Avantage: lent à la première exécution, mais monte bien en cache.

DRILLTHROUGH
SELECT
{
 [Measures].[Reseller Sales Amount]
}
ON 0
FROM [Adventure Works]
RETURN
 [$Date].[Date]
 ,[$Geography].[City]
 ,[$Product].[Product]
 ,[$Reseller].[Reseller]
 ,[$Sales Territory].[Sales Territory Region]
 ,[Reseller Sales].[Reseller Sales Amount]

Dans la session on vous a montré qu’on peut aussi faire ça en DAX. pour de l’extraction de détail, ça envoie sacrément (les temps de réponse sont en moyenne 10 fois meilleurs sur notre petite base d’exemple).

En plus cela semble constant au vu de nos tests contrairement aux autres modes d’accès.

Evaluate
(
	Summarize
	(
		'Reseller Sales'
		,'Date'[Date.Key0]
		,'Date'[Date]
		,'Geography'[City.Key1]
		,'Geography'[City.Key0]
		,'Product'[Product.Key0]
		,'Product'[Product]
		,'Reseller'[Reseller.Key0]
		,'Reseller'[Reseller]
		,'Reseller'[Bank Name]
		,'Sales Territory'[Sales Territory Region.Key0]
		,'Sales Territory'[Sales Territory Region]
		,"Sales Amount",'Reseller Sales'[Reseller Sales Amount]
	)
)
Order By
	'Date'[Date]
	,'Geography'[City.Key0]
	,'Product'[Product]
	,'Reseller'[Reseller]
	,'Reseller'[Bank Name]
	,'Sales Territory'[Sales Territory Region]

Vous noterez au passage les splendides Key0, Key1 dans la requête. Kézako? Et bien si vous vous y connaissez en member properties vous savez que les Key0…KeyN en MDX sont les KeyColumns d’un attribut. Pour développer DaxMD – de la même manière qu’il a du être difficile de faire rentrer le concept de DefaultMember dans un PowerPivot – cela n’a pas du être une mince affaire pour les équipes de Redmond de faire comprendre l’idée de clés composites et de NameColumn à du DAX . Moyennant quoi dès que la NameColumn est différente de la KeyColumn dans la Dimension, on doit spécifier cette dernière, sans quoi on prend un beau message d’erreur:

Column [Truc] is part of composite key, but not all columns of the composite key are included in the expression or its dependent expression.

Bon à savoir…

Rapport de drill en SSRS

Pour la suite de la démo, Romu a montré l’inclusion de ce code dans un Rapport SSRS  pour lequel il faut utiliser le mode DMX pour taper la requête (comme dans ce vieux post sur le Drillthrough). Les paramètres quant à eux sont évidemment gérés (Syntaxe @Paramètre) et le sous rapport envoyé grâce à un JumpToReport sur le camemb… l’histogramme.

DAX DrillThrough Action dans SSAS

Enfin la cerise sur le gâteau concernait l’exécution de ce code dans une action de type Rowset côté AS. L’idée étant d’avoir un Drill en DAX sur un cube. Une action de Rowset a une expression de type chaîne de caractère qui va être évaluée dans un bloc XMLA <Command>… qui contient normalement du MDX. Mais dans DaxMD, les Command peuvent être en DAX (oui c’est le principe), donc on peut y mettre la requête DAX précédente.

Problème: comme pour le DrillThrough on Calculated Members de Mosha on doit contextualiser la cellule (si je clique sur une cellule, la requête DAX doit être filtrée sur les dimensions autour). Dans son post, Mosha explique que:

In order for drillthrough to address the correct cell, we need to put current cell coordinates into the SELECT clause. One way of doing it would be to write long MDX string concatenation, using <hierarchy>.CurrentMember.UniqueName subexpression for every hierarchy in the cube. However, this is tedious and error-prone approach. There are hundreds of hierarchies, new ones can be added, old ones removed or renamed etc. Instead, we can just call stored procedure which will enumerate all the current coordinates (note that we could’ve done the same with the RETURN clause too).

Procédure que l’on ne possède pas ici (Mosha utilise celle de ASSP qui renvoie du… MDX). Ce que j’ai montré pendant la session c’est le principe de la chose appliqué sur une seule hiérarchie que les plus courageux d’entre vous finiront. (mais si un candidat se présente pour écrire une stored proc… :)). La partie importante de la requête est dans le Filter, qui va désactiver le filtre si on est au niveau All en générant une clause en DAX depuis du MDX. Oui je sais c’est tordu:

"Evaluate
(
	CalculateTable
	(
		Summarize
		(
			'Reseller Sales'
			,'Date'[Date.Key0]
			,...
		)
		,'Sales Territory'[Sales Territory Region.Key0] = "+[Sales Territory].[Sales Territory Region].CurrentMember.Properties('Key0')+"
        ||"""+[Sales Territory].[Sales Territory Region].CurrentMember.Name+"""="""+[Sales Territory].[Sales Territory Region].DefaultMember.Name+"""
    )
)"

Conclusion

Voilà c’était à peu près tout. Merci à tous pour votre présence, à vos commentaires si vous voyez une erreur. Quant au contenu, ce sera avant Vendredi sur mon SkyDrive, j’éditerai le post.

A bientôt!

Techdays 2013: une petite dose d’Analysis Services pour démarrer l’année!

Bonjour à tous!

Cette année si vous n’aviez pas assez vu de MDX, de DAX avec David Joubert aux JSS, vous allez pouvoir venir aux Techdays 2013, voir ce que valent nos deux moteurs de Redmond au banc d’essai! Nous animerons en effet avec l’exceptionnel Romuald Coutaud et le non moins exceptionnel Aurélien Koppel de Microsoft une session qui vise à pousser les deux moteurs dans leurs retranchements fonctionnels et techniques afin que vous soyez sûr en repartant:

  1. De faire votre prochain projet avec Analysis Services
  2. De choisir le moteur qui satisfera vos utilisateurs.

Alors on vous attend, préparez des questions qui piquent, potassez votre MDX Cookbook, on se voit le mardi 12 à l’heure du gouter (ou de l’apéro, comme vous préférez!)

Allez vous inscrire, c’est par ici!

Merci aux participants de notre session aux JSS2012!

Merci à tous ceux qui ont eu le courage de venir suivre notre session de DAX et MDX aux Journées SQL Server! Merci surtout au grand David Joubert, Marco Juberto du DAX avec qui je me suis bien marré et vous aussi je l’espère, en plus d’apprendre quelques trucs.

La salle était pas mal remplie pour une 400, avec pas mal de collègues et de copains en guest star (Florian Eiden, Charles-Henri Sauget, Thomas Ricquebourg, Aurélien Koppel, Romuald Coutaud, David Tang…)

Plus globalement sur l’event que je n’ai pas eu le temps de suivre (qui a dit qu’on était en retard dans les répétitions 🙂 ?) le ressenti des gens j’ai pu croiser est bon voire très bon, l’organisation s’affine petit à petit, et tout le monde s’accorde à dire que l’on tient là l’évènement de référence sur SQL Server. Longue vie aux JSS, et félicitations aux nombreux speakers (et les copains de la FC.BI qui dort un peu en particulier).

PS: Comme je suis un type sympa, vous pouvez retrouver les slides et les démos dans ce dossier sur mon SkyDrive.

A bientôt, aux TechDays?

Passez me dire bonjour aux Journées SQL Server!

Bonjour!

Comme dit précédemment, je co-animerai une passionnante session sur DAX et MDX aux Journées SQL Server avec David Joubert, centrée sur l’utilisation de DAX dans Analysis Services Tabular, et plus particulièrement dans des usages précédemment réservés à MDX.

La session aura lieu le mardi 11 décembre à 14h au Centre de Conférences Microsoft à Issy les Moulineaux.

Ce sera l’occasion pour vous de découvrir DAX, de voir comment il peut vous aider à résoudre de manière élégante des problématiques lourdes en MDX (Attribute Overwriting, multi-sélection), sans pour autant négliger les usages intéressants et toujours exclusifs au MDX. Venez avec vos questions, ça va être sympa 🙂

Pour ça rien de plus simple: allez vous inscrire, et n’hésitez pas aussi à venir aux sessions de mes petits camarades d’Infinite Square:

Thomas Ricquebourg le matin même sur MDS, DQS et SSIS

Charles-Henri Sauget sur de la modélisation dimensionnelle avec l’immmmense Florian Eiden le lundi

Patrice Harel la veille aussi sur du processing SSAS

Image

Alors on se retrouve aux Journées SQL Server?

Bonne soirée!

Save the date pour les Journées SQL Server!

La date est officielle ! La seconde édition des Journées SQL Server aura lieu les 10 et 11 décembre 2012 au

Centre de Conférences Microsoft
39 quai du Président Roosevelt
Issy-Les-Moulineaux

Vous pouvez dès à présent bloquer la date dans vos agendas pour participer à la plus grande conférence SQL Server en France.

Les inscriptions seront ouvertes dans quelques semaines, encore un peu de patience. En attendant rendez vous sur la page Facebook des JSS pour plus d’informations!

DAX Wars – Un nouvel espoir pour les allergiques au MDX

Je crois vraiment au DAX en tant que langage d’expressions. Je dois vous avouer que j’étais, et que je suis encore en partie beaucoup plus sceptique en ce qui concerne son usage en tant que langage de requêtes, comparé au MDX que je trouve plus élégant que cette espèce de LISP qui aurait couché avec des formules Excel. Roger Doherty évangéliste SQL Server à Corp, avec qui je parlais il y a quelques jours, m’a répondu à ce sujet que c’était souvent l’avis des MDX-geeks, mais que c’est justement parce que les geeks MDX sont 45 dans le monde et que tout le monde comprend les formules Excel que le DAX va probablement s’imposer. Un point pour lui. Blague à part, pourquoi ai-je eu du mal avec le DAX ? Probablement parce que j’ai mis du temps à appréhender correctement le MDX en oubliant complètement le SQL, et que DAX est plus une sorte de couche relationnelle avec des clauses de jointures implicites qui fait de l’analyse ad-hoc sur des schémas en étoile sans notion d’Exists. Pour les gens comme moi j’écrirais un post, qui sera le suivant en fait.

Il y a une autre raison à ce post. Pour taper les modèles tabulaires, DAX est beaucoup plus efficace que le Formula Engine MDX qu’Excel utilise pour l’attaquer. Donc dans des frontaux où le développeur écrit sa requête, savoir écrire du DAX est à mon sens indispensable. Donc pour la majorité des gens, qui font du SSRS (par exemple) je pense qu’il vaut mieux présenter DAX comme étant à la base un truc fondamentalement relationnel. Au sens d’Edgar Codd – loué soit son nom. Des tables, des jointures, des agrégats… Mon objectif ici est de vous expliquer DAX de cette manière, en reprenant le chapitre 1 de ma formation de SQL de base, et en transposant les concepts.

Back to school : algèbre relationnelle, projections et sélections

DAX manipule des tables. L’instruction la plus simple qu’on puisse faire sur une table est une projection de toutes ces colonnes :

Evaluate
(
	‘Product’
)

fait un

SELECT *
FROM [Product]

rien de plus. Si je souhaite faire une sélection, au sens de l’algèbre relationnelle, c’est-à-dire un filtrage, je vais simplement utiliser la fonction Filter, qui remplace le WHERE.

Evaluate
(
	Filter
	(
		Product,
		Product[Color] = "Red" && Product[Weight] > 10
	)
) 

Il s’agit donc d’un

SELECT *
FROM Product
WHERE Color= 'Red' AND Size > 10

Faisons maintenant une projection restreinte et n’affichons que les bonnes colonnes. La manière de le faire est d’utiliser la fonction Summarize

Evaluate
(
	Summarize
	(
		Filter
		(
			Product,
			Product[Color]="Red"
		),
		Product[Product ID],
		Product[Model Name]
	)
) 

Attention. Ceci est en réalité équivalent à un SELECT DISTINCT car Summarize applique un DISTINCT. La raison est très simple: DAX n’est pas optimisé pour des projections de certaines colonnes seules sans DISTINCT. Pour faire une vraie projection d’algèbre relationnelle sur le niveau de granularité le plus fin, il faut donc avoir une ou plusieurs colonnes UNIQUE dans les colonnes projetées. En ce qui concerne l’aliasing des colonnes, il n’est pas facile à mettre en œuvre. Il faut utiliser l’astuce de Marco Russo qui consiste à projeter une ou plusieurs colonnes qui donnent l’unicité, et à aller chercher les autres colonnes via des Calculate dans un AddColumns.

Oula je suis allé un peu vite pardon. AddColumns permet d’ajouter des colonnes, par exemple calculées, et Calculate évalue simplement une expression dans le contexte actuel.

Evaluate
(
	AddColumns
	(
		Summarize
		(
			Filter
			(
				Product,
				Product[Color]="Red"
			),
			Product[Product ID]
		),
		"Nom du produit", Calculate(Values(Product[Model Name]))
	)
) 

Pas super intuitif hein. Mais qui se préoccupe d’aliaser les colonnes. Sérieusement.

Que serait un rapport sans agrégations ?

C’est bien la fonction Summarize qui va jouer ce rôle d’agrégateur. En réalité elle permet de définir dans une seule fonction :
– La table
– Les colonnes de groupements
– Les agrégations d’une requête

Par exemple :

Evaluate
(
	Summarize
	(
		Filter
		(
			Product,
			Product[Color]="Red"
		),
		Product[Model Name],
		"Poids Moyen", Average(Product[Weight])
	)
) 

exécute bien la requête équivalente à

SELECT
[Model Name], AVG([Weight]) AS [Poids]
FROM Product
WHERE Color= 'Red' AND Size > 10
GROUP BY [Model Name]

La plupart des agrégations communes existent. Elles sont simplement parfois renommées et DAX n’étant pas super polymorphique vous en verrez des déclinaisons selon le type attendu en argument … La projection exécutée par Summarize quant à elle est simple à comprendre: les colonnes récupérées sont les groupements et les agrégations. Quant au HAVING, c’est un filtre comme les autres. Du point de vue de DAX, tout n’est que table imbriquées. Si je souhaite faire un filtre sur poids, il me suffit de faire :

Evaluate
(
	Filter
	(
		Summarize
		(
			Filter
			(
				Product,
				Product[Color]="Red"
			),
			Product[Model Name],
			"Poids Moyen", Average(Product[Weight])
		),
		[Poids Moyen] > 10
	)
) 

Cette requête est donc équivalent au SQL suivant: le premier qui demande pourquoi le HAVING ne voit pas les alias est prié d’aller relire tout le standard SQL ANSI au coin.

SELECT
[Model Name], AVG([Weight]) AS [Poids]
FROM Product
WHERE Color= 'Red' AND Size > 10
GROUP BY [Model Name]
HAVING AVG([Weight])> 10

Sans oublier les tris !

J’en aurais presque oublié le tri. Comme dans SQL c’est la dernière instruction dans l’ordre d’évaluation « logique ». Et cela s’appelle aussi Order By. Ca c’est sympa.

Evaluate
(
	Summarize
	(
		Filter
		(
			Product,
			Product[Color]="Red"
		),
		Product[Model Name],
		"Poids", AVERAGE(Product[Weight])
	)
)
Order By [Poids] DESC

Sans aucun étonnement, voilà le SQL généré. A noter que nous avons maintenant un statement SQL monotabulaire « complet ».

SELECT
[Model Name], AVG([Weight]) AS [Poids]
FROM Product
WHERE Color= 'Red' AND Size > 10
GROUP BY [Model Name]
HAVING AVG([Weight])> 10
ORDER BY [Poids] DESC

Jointures et relations entre tables : tout – ou presque – est implicite

A la différence de SQL, les jointures sont implicites en DAX. Comme dans un cube, un modèle tabulaire a ses relations définies dans le Dimension Us… pardon dans le designer de relations. Bon en réalité il y a des cas où il faudra les expliciter. Mais on en est pas là. Cette implicitesse… implicité… implicitude signifie que si j’écris la requête suivante :

Evaluate
(
	AddColumns
	(
		Filter
		(
			Product,
			Product[Color] = "Red"
		),
		"Sous catégorie",RELATED('Product Subcategory'[Product Subcategory Name])
	)
)
Order By [Model Name]

La fonction RELATED va juste dire de faire une jointure externe gauche vers la table sous catégorie, en se basant sur la relation définie dans mon modèle – à savoir ‘Product'[Product Subcategory ID] = ‘Product Subcategory‘[Product Subcategory ID] et de ramener cette dernière. On ne peut pas spécifier la condition de jointure, et c’est en cela qu’on se rapproche d’un cube. Mais juste un peu. Bon en SQL ça donne ça:

SELECT p.*, psc.[Product Subcategory Name]
FROM [Product] p
LEFT JOIN [Product Subcategory] psc
ON psc.[Product Subcategory ID]=p.[Product Subcategory ID]
ORDER BY [Model Name]

Cela marche évidemment aussi pour des relations à plus d’une indirection. Je peux bien évidemment aller chercher RELATED(‘Product Category'[Product Category Name]) dans la table catégorie, à laquelle je suis relié par une table intermédiaire (sous catégorie). Qui a parlé de relation Referenced? En revanche cette requête échoue misérablement :

Evaluate
(
	AddColumns
	(
		Filter
		(
			Product,
			Product[Color] = "Red"
		),
		"Année",RELATED('Date'[Calendar Year])
	)
)
Order By [Model Name]

En effet les liens existent certes entre temps et produit (J’ai une many to many entre mes produits et mes dates, qui est la table de faits des ventes) mais ce ne sont pas des liens 1:N comme exigé par la fonction RELATED. Dit autrement Related est un peu comme un Lookup en SSIS : il va chercher une valeur unique basée sur des relations orientées vers la table cible. En utilisant des agrégats c’est encore plus simple : dans la fonction SUMMARIZE les Calculate sont implicites, les agrégats sont donc calculés en suivant les relations.

Evaluate
(
	Summarize
	(
		'Internet Sales',
		Product[Model Name],
		"Somme des ventes Internet", Sum('Internet Sales'[Sales Amount])
	)
)
Order By [Model Name] 

Attention par contre! La table “de base” est importante! C’est elle qui détermine de quel côté se situe la jointure externe. Si je fais comme ci-dessous j’affiche tous les produits, même ceux sans ventes (LEFT OUTER JOIN)

Evaluate
 (
	Summarize
	(
		Product,
		Product[Model Name],
		"Somme des ventes Internet", Sum('Internet Sales'[Sales Amount])
	)
)
Order By [Model Name] 

On comprend donc que dans le cas d’une application analytique, la première table d’un Summarize est très généralement une des tables de faits: c’est depuis elle que les autres arguments du GROUP BY vont venir s’afficher. Pour les filtrages, on peut encore utiliser la fonction Filter vue précédemment. Le problème de Filter, c’est qu’elle est à la base faite pour filtrer les colonnes d’une même table. Donc la solution immédiate c’est d’invoquer Related pour récupérer les valeurs et filtrer.

Evaluate
(
	Filter
	(
		'Internet Sales',
		Related('Product'[Color])="Red"
		&&
		Related('Geography'[Country Region Name])="France"
	)
)

Un peu fastidieux et répétitif. Pour remplacer cela il existe la fonction CalculateTable, qui va appliquer à un ensemble infini de filtres, comme suit. J’ai donc ici les ventes de produits rouges en France.

Evaluate
(
	CalculateTable
	(
		'Internet Sales',
		'Product'[Color]="Red",
		'Geography'[Country Region Name]="France"
	)
)

Customisons ça avec un Summarize et quelques autres colonnes, ainsi que quelques agrégats et voilà déjà un beau rapport non ?

Evaluate
(
	Filter
	(
		CalculateTable
		(
			Summarize
			(
				'Internet Sales',
				'Date'[Calendar Year],
				'Product Category'[Product Category Name],
				'Product Subcategory'[Product Subcategory Name],
				"Nombre de ventes", CountRows('Internet Sales'),
				"Total des ventes", Sum('Internet Sales'[Sales Amount])
			),
			Filter
(
'Product','Product'[Color]="Red"
||
'Product'[Color]="Yellow"
),
			'Geography'[Country Region Name]="France"

		),
		[Total des ventes] > 5000
	)
)
Order By [Calendar Year],[Product Category Name]

Ce qui donne en SQL:

SELECT
	s.*,
	d.[Calendar Year],
	pc.[Product Category Name],
	psc.[Product Subcategory Name],
	COUNT(*) AS [Nombre de ventes],
	SUM(s.[Sales Amount]) AS [Total des ventes]
FROM [Internet Sales] s
LEFT JOIN [Date] d
ON s.[Order Date Key] = d.[Date Key]
LEFT JOIN [Product] p
ON p.[Product ID] = s.[Product ID]
LEFT JOIN [Product Subcategory] psc
ON p.[Product Subcategory ID] = psc.[Product Subcategory ID]
LEFT JOIN [Product Category] pc
ON pc.[Product Category ID] = psc.[Product Category ID]
LEFT JOIN [Customer] c
ON s.[Customer ID]= c.[Customer ID]
LEFT JOIN [Geography] g
ON g.[Geography ID]= c.[Geography ID]
WHERE p.Color IN ('Red','Yellow') AND g.Country='France'
GROUP BY
	s.*,
	d.[Calendar Year],
	pc.[Product Category Name],
	psc.[Product Subcategory Name]
HAVING SUM(s.[Sales Amount])>5000
ORDER BY [Calendar Year],[Product Category Name]

Je pense que ces quelques fonctions constituent une bonne intro au DAX, vous allez déjà pouvoir sortir quelques rapports. La prochaine fois on s’attaque au plus difficile : le DAX pour les fans de MDX, que j’aurais pu appeler où « Comment on fait un à #@%$ de YearToDate ?! » où « Où qu’il est Exists ? ». Bon weekend!