Un peu de MDX de bon matin…

Je pique exceptionnellement le flambeau à François pour moi aussi vous causer MDX. Évidemment ça ne sera pas aussi pointu (voir piquant) que lui, mais pour une fois j’ai quelque chose à dire sur ce sujet…

A la base mon besoin était assez simple : créer un graph dans SSRS avec une date de départ et une date de fin fixées par des attributs de dimension de SSAS.

Typiquement : j’ai une dimension projet, dans cette dimension j’ai un attribut date de départ théorique (DDT), et un attribut date de fin théorique (DFT). Je veux que peu importent mes faits, mon graph couvre cette période théorique :

MDX - Le Besoin

A mon sens la grande méthode pour faire ça c’est de créer un set de dates allant de la DTD à la DTF. Ce set doit évidemment être composé d’éléments de la dimension Temps utilisée dans le groupe de mesure concerné, il va donc falloir passer des attributs de la dimension projet ([Projet].[DDT].&[…] et [Projet].[DDF].&[…]) à la dimension temps ([Temps].[Date].&[…]).

Le MDX étant un langage particulièrement efficace en termes de manipulation de chaînes de caractères (hur hur), ça va être un bonheur… Trêve de sarcasme, on peut utiliser quelques fonctions VBA en MDX qui vont ici nous sauver la mise (à adapter en fonction des formats de vos dates):

MDX - La requête qui en cast!

Ce n’est pas ultra élégant, mais ça marche… pas !

Le message d’erreur :  La fonction Axis1 attend une expression d’ensemble de tuples pour l’argument . Une expression de chaîne ou numérique a été utilisée.

Notez que la manipulation de projection des dates sur la dimension Temps marche, elle. C’est autre chose qui coince.

Le problème c’est qu’au moment où TimeSet est évalué, on n’a pas de CURRENTMEMBER sur la dimension Projet. En effet, en même temps (ON 1) qu’on essaye de résoudre TimeSet, on parcourt la dimension Projet, le CURRENTMEMBER n’est donc pas figé. On reçoit donc ALL dans le MEMBER_VALUE, que le STRTOMEMBER n’arrive pas à mapper correctement sur la dimension Temps. C’est le drame.

 

Tout ça parce que comme en SQL, en MDX le moteur interprète la requête dans un certain ordre. En faisant un gros raccourci, ici il commence par le FROM, puis le WHERE (slicer), puis les axes en itérant sur le 1 pour résoudre le 0. Dans une étape, tous les éléments sont interprétés en même temps : ça coince effectivement sur le TimeSet.

J’ai du mal à trouver des articles intéressants sur ce sujet, n’hésitez pas à soumettre les vôtres.

Pour comprendre le problème, on peut repartir d’une requête MDX plus simple, et basée sur AdventureWorks (je ne caste plus ma Start Date sur la dimension temps, je veux juste la faire apparaître en ON 1 comme dans mon cas réel):

WITH

    MEMBER Test1 AS [Product].[Start Date].CURRENTMEMBER.MEMBER_UNIQUE_NAME

 

SELECT

        [Measures].[Order Count] ON 0,

        STRTOMEMBER(Test1)

        *

        [Product].[Product].[Product].MEMBERS ON 1

 

FROM    [Adventure Works]

Le résultat, ma date est remplacée par ALL (« Tous les Produits ») dans le STRTOMEMBER(Test1) :

Mauvaise pioche

Si maintenant je filtre mon produit en slicer (typiquement l’endroit où on mettra le paramètre du rapport pour SSRS), la requête devient valide :

WITH

    MEMBER Test1 AS [Product].[Start Date].CURRENTMEMBER.MEMBER_UNIQUE_NAME

 

SELECT

        [Measures].[Order Count] ON 0,

        STRTOMEMBER(Test1) ON 1

       — *

       — [Product].[Product].[Product].MEMBERS ON 1

 

FROM    [Adventure Works]

WHERE    [Product].[Product].&[447]

La c'est bon!

Ici il existe un CURRENTMEMBER au moment où on évalue Test1, tout roule. La solution est trouvée pour mon rapport, à moi de placer mon STRTOMEMBER(@Parameter) dans le WHERE.

Mais oui mais vous voulez itérer en même temps sur le TimeSet/Test1 et les projets/produits ? Il va falloir siouxer et passer l’attribut dans une mesure.

WITH

    MEMBER [Measures].Test1 AS [Product].[Start Date].CURRENTMEMBER.MEMBER_UNIQUE_NAME

 

SELECT

      {[Measures].[Order Count],Test1} ON 0,   

        [Product].[Product].[Product].MEMBERS ON 1

 

FROM   [Adventure Works]

Ce qui donne:

Mouais

Si ça marche pour le cas simple, on perd le cas d’application premier avec le TimeSet. Je n’ai malheureusement pas de solution à l’instant T. Si ça me vient je compléterai.

On a donc vu deux choses : comment passer d’un attribut d’une dimension à une autre grâce aux commandes VBA, et l’ordre d’exécution d’une requête MDX et comment il peut casser vos jolies sets et membres calculés. J’aimerai en rajouter une troisième, la propagation des contraintes sur les attributs dans les dimensions. François en avait parlé aux derniers JSS, j’en remets rapidement une couche ici.

Si quand vous passez votre filtre en WHERE vous utilisez la clef de la dimension, ça marche :

WITH

    MEMBER Test1 AS [Product].[Start Date].CURRENTMEMBER.MEMBER_UNIQUE_NAME

 

SELECT

        [Measures].[Order Count] ON 0,

        STRTOMEMBER(Test1) ON 1

 

FROM    [Adventure Works]

WHERE    [Product].[Product].&[447]

Ca marche!

Par contre si vous utilisez un attribut de plus haut niveau, ça ne marchera pas :

WITH

    MEMBER Test1 AS [Product].[Start Date].CURRENTMEMBER.MEMBER_UNIQUE_NAME

 

SELECT

        [Measures].[Order Count] ON 0,

        STRTOMEMBER(Test1) ON 1

 

FROM    [Adventure Works]

WHERE    [Product].[Model Name].&[Cable Lock]

Ca ne marche plus :/

En effet, si on affiche les relations entre les attributs de la dimension (via SSDT BI), on verra que le Model Name ne contraint pas la Start Date, puisque si les contraintes peuvent remonter l’arbre (Model Name vers Product), elles ne le redescendent pas à partir de là (Product vers Start Date).

La classe ce SSDT BI 2012!A vous d’utiliser les bons attributs, et de bien construire vos cubes, pour que les requêtes fonctionnent bien.

Utilisation du VBA, ordre de résolution des requêtes, propagation des contraintes sur les attributs… Pfiou, c’était du lourd tout ça ! Heureusement que le DAX arrive pour simplifier tout ça (hur hur).

Spéciale dédicace à Jordan Mootoosamy, qui a bien souffert avec moi sur cette requête 😉

BI.Quality : tests automatisés pour comparer des données entre SSAS et SQL Server

Comme je vous le disais tantôt, l’amélioration continue des équipes et des solutions passent par une automatisation des activités qui peuvent l’être. Ce n’est pas moi qui l’invente, c’est à la base de l’Agilité et du Lean. Notez que ce n’est pas une fin en soi, mais plutôt dans l’objectif de tendre vers un temps de production le plus court possible, toujours en qualité optimale.

Et une des principales manières d’avancer sur sujet c’est bien par l’automatisation des tests. C’est la raison pour laquelle je teste depuis peu BI.Quality, l’outil de test automatique gratuit développé par ORAYLIS et disponible sur le CodePlex.

BI.Quality

Ce qui est particulièrement intéressant avec cet outil c’est la possibilité de comparer des datasets en provenance de SSAS, de SQL Server, ou d’un fichier CSV. Vous comprenez l’intérêt immédiatement : avec ça on va pouvoir comparer de manière automatique son cube (via requêtes MDX) avec son DWH et/ou son ODS (via requêtes SQL). On pourra également comparer le résultat d’une même requête contre plusieurs environnements (production, intégration, développement…) pour valider que tout est bien synchro une fois la livraison terminée.

Joie !

Alors ok, l’interface ne fait pas rêver, et quasiment toute la « programmation » se fait dans des fichiers XML, donc à la main dans NotePad++. Mais la fonction rendue est tellement excellente qu’on apprend à vivre avec.

NUnit_1

Je vous fais un guide de démarrage rapide, vous allez voir c’est assez simple :

  1. Télécharger et installer NotePad++, si ce n’est pas déjà fait !
  2. Télécharger et installer NUnit (framework de test, c’est lui qui exécutera les tests créés dans BI.Quality), la version courante c’est la NUnit-2.6.2.msi
  3. Télécharger et installer BI.Quality (pas de panique, il termine sans prévenir, c’est un peu artisanal)
  4. Télécharger et lire la documentation :p
  5. Suite à ça, on dispose dans le menu démarrer :
    1. D’un répertoire BI.Quality, qui contient principalement un ZIP contenant la solution template, qui sera le modèle de départ pour tous les projets de tests. A dézipper là où vous le souhaitez pour chaque nouveau projet.
    2. D’un répertoire NUnit, qu’il va falloir associer avec votre projet de test (le répertoire dézippé), dans NUnit : File> Open Project > …\Lib\BI.Quality.dll

Une solution de test BI.Quality c’est donc un dossier composé de 4 sous-répertoires (le contenu du ZIP) :

  • \Bin\ et \Lib\ : on ne touche pas
  • \Connections\ : on va définir nos connexions là-dedans, un fichier XML correspondant à une connexion. On ne peut y utiliser que des belles chaînes de connexions OLE DB (SSAS, SQL Server < 2008R2, SQL Server 2012, à tester sur Excel, Access et SharePoint) :

BI.Quality_Connections

  • \Queries\ : on va définir nos tests là-dedans, un sous-répertoire correspondant à un test, avec :
    • des fichiers SQL, MDX ou CSV qui définissent les requêtes à utiliser dans le test
    • un fichier XML qui définit le test en lui-même

BI.Quality_Query

Franchement c’est pas sauvage non ? Je définis 2 sets de données, les <Query/>, que je compare dans un test <AssertTable/>.

Alors il existe plein de tests possibles, avec plein d’options, à voir dans le PDF de documentation ainsi que dans l’ensemble de tests livrés dans le template (1-Tutorial, 2-TechnicalTests, 3-BestPractices) que vous pouvez d’ailleurs enlever de votre projet si vous voulez avoir une solution bien propre.

Une fois que c’est fait, on retourne dans NUnit (on charge le projet si ce n’est pas déjà fait : File> Open Project > …\Lib\BI.Quality.dll), et on peut exécuter ses tests d’un simple RUN :

NUnit_2

Si la partie « Configuration Test » est gérée toute seule, NUnit va parser les XMLs de définition des connexions et des tests pour valider leur format, la partie « Query Test » est bien celle pilotée par vos tests du répertoires \Queries\ . Notez que si vous ne passez pas le « Configuration Test », c’est que vos XMLs sont mal montés : direction NotePad++ pour corriger tout ça. Si tout va bien, c’est parti pour vos tests à vous 🙂

Juste une petite remarque en passant : je n’ai définitivement pas réussi à ajouter un Delta sur un AssertTable, n’hésitez donc pas à faire plutôt des ROUNDs dans vos requêtes SQL ou MDX, si par exemple vous changez de précision entre les 2 sources. [MàJ 2013-08-19] En fait il est possible de définir un Delta dans un AssertTable, mais en utilisant une virgule plutôt qu’un point dans la valeur: delta=« 0,1%« . Trop bien 🙂

Et une deuxième petite remarque : pensez bien à recharger vos tests si vous les modifiez (File> Reload Project ou Reload Test) sinon ce ne sera pas pris en compte par NUnit.

D’un point de vue stratégie de tests, je suis partie sur les éléments suivants :

  • D’abord des agrégations de haut niveau (mon CA par an sur les 5 grands pôles d’activité), qui valident que le total général est le même partout
  • Des tests sur chaque dimension, indépendamment des faits, pour valider qu’on n’oublie personne en route et que les hiérarchies tiennent la route
  • Des tests portant sur les valeurs des mesures clefs pour chaque code atomique de chaque dimension (autant de tests que de dimensions). On a vérifié les hiérarchies dans l’étape précédente, il suffit donc de valider que chaque code dispose des bons montants unitairement pour chaque dimension pour valider quasiment tout
  • Des scénarios de référence (SQL/MDX vs valeurs en dur dans des CSV). Si les valeurs historiques (2009,2010…) n’évoluent plus, on peut se faire quelques extraits, les stocker en CSV, et les comparer régulièrement contre le cube ou le DWH. Histoire de prendre en flagrant délit la régression sur l’historique
  • Les requêtes des rapports SSRS, qu’on peut valider contre des valeurs de références ou entre plusieurs environnements
  • Enfin, toutes les requêtes issues des analyses d’anomalie, écrites pour renvoyer du vide si tout va bien

Si on maintient bien à jour sa base de tests, et qu’on exécute le projet régulièrement, il va devenir vraiment difficile de livrer du code défectueux !

Je conclue en vous livrant l’avis de Chris Webb sur l’outil, et en vous recommandant chaudement de l’essayer sur votre prochain projet ou prochaine recette. C’est simpliste, certes, mais ça fait le job, et pouvoir tous les jours exécuter toute sa batterie de test en 1 clic c’est juste magique ! Seul petit bémol: la dernière mise à jour du projet date de fin 2010, le dernier commentaire des admins de fin 2012… Alors allez le télécharger, histoire de bien montrer qu’on a besoin d’eux 😉

PowerPivot, BISM, Analysis Services et Marco Russo

Comme je vous le disais tantôt, la semaine dernière j’ai assisté au séminaire de 2 jours de Marco Russo sur PowerPivot.

Ce n’est pas difficile, c’est la meilleure formation a laquelle j’ai eu la chance d’assister, quel que soit le sujet. Que ce soit sur le contenu (précis, clair et exhaustif) ou la présentation (pédagogie, connaissance du sujet, passion), Marco Russo a fait un score parfait. Jusqu’à présent je voyais PowerPivot comme un gadget futur remplaçant d’Access, il m’a bien ouvert les yeux sur ses énormes capacités et le fait que ce soit bien le futur de SSAS. Job well done 🙂

Et hasard du calendrier, c’est justement la semaine dernière que Microsoft a fait le point sur la roadmap de sa plateforme BI pour les années à venir:

En cherchant un peu vous trouverez surement beaucoup d’autres articles sur le sujet (certains en français) et tout autant de discussions.

Moi j’ai eu de la chance, Marco Russo a pris le temps de nous expliquer en direct ce qu’il allait se passer. Chouette non? 😉

Pour la faire en court: SSAS va changer d’architecture pour s’organiser autour d’un modèle dénommé « BISM » (Business Intelligence Semantic Model – Modèle sémantique décisionnel).

Ce BISM comprendra 3 niveaux, chaque niveau pouvant être implémenté de plusieurs façons différentes:

  • Un modèle de données : en multidimensionnel (cf SSAS actuel) ou tabulaire (cf PowerPivot ou une base SQL classique)
  • Un langage de requête : MDX ou DAX
  • Un moteur d’accès aux données : MOLAP (SSAS actuel), VertiPaq (cf PowerPivot) ou un accès direct aux données d’où qu’elles viennent

Notez que lorsqu’on parle ici de modèle de données, on parle de l’implémentation technique (de l’encodage dans une solution informatique) du modèle de données fonctionnel (schéma en étoile, en flocon, ou autre). Mon premier est une suite de 0 et de 1 qui répondent à des contraintes informatiques, mon second est un dessin sur un papier qui répond à des contraintes métiers. Je précise parce qu’au début j’étais perdu: je me demandais à quoi pouvait bien ressembler un modèle de données BI tabulaire? Marco Russo m’a donné la réponse : à un schéma en étoile!

En image, le BISM et ses 3 éléménts ça donne ça:

BISM : l'avenir de SSAS

BISM : l'avenir de SSAS

La première bonne nouvelle c’est que bien que pour la v1.0 les deux piles (Multidim/MDX/MOLAP et Tabulaire/DAX/Vertipaq) soient étanches – on choisit soit une pile soit l’autre, dans le futur on devrait pouvoir choisir de construire la pile que l’on veut avec n’importe quelle brique (Tabulaire/MDX/Vertipaq par exemple). Le choix se faisant alors uniquement en fonction des contraintes métiers / utilisateurs et non de contraintes techniques.

La deuxième bonne nouvelle c’est que si on peut requêter en MDX et en DAX les 2 modèles de données, alors on devrait pouvoir utiliser n’importe quel front end (SSRS, Crescent, PowerPivot…) indépendamment de l’architecture de stockage. Cela lève le doute sur la compatibilité descendante entre Crescent (futur SSRS en DAX) et les cubes SSAS actuels, ça rassure!

Evidemment reste la question de comment faire le choix entre les 2 architectures, et là aussi Marco Russo nous a donné quelques éléments de réponses:

  • Avant tout, que ce soit pour le multidim ou le tabulaire, le schéma en étoile reste la méthode de modélisation des données.
  • Le multidimensionnel sera l’outil de choix pour travailler sur des données fortement hiérarchisées, utilisant des calculs mathématiques sur ces hiérarchies (par exemple sur le temps), avec des relations complexes (many to many) et où l’importance des données réside dans les chiffres (qui a dit finance dans le fond?)
  • Le tabulaire (avec Vertipaq et la compression en colonne) est ultra efficace sur les calculs en distinct et distinct count, même sur des très très gros volumes de données, il manipule bien les chaînes de caractères et est beaucoup plus facile à comprendre pour les utilisateurs (on dirait Access!)

En terme de proportions, Marco Russo nous a dit qu’il voyait les futurs projets ventilés en:

  • 30% ne pouvant être réalisés qu’avec du Multidim
  • 10% ne pouvant être réalisés qu’avec du Tabulaire
  • 60% au choix, avec beaucoup de Tabulaire puisque plus facile à implémenter

Personnellement je retrouve l’enthousiasme sur le futur de la BI Microsoft. Même si on est encore un peu déshabillé point de vue reporting, l’offre Microsoft est vraiment très belle point de vue moteur. Entre le dB Engine SQL, MOLAP et Vertipaq, on a de très belles technologies, partiellement recouvrantes mais pas redondantes, qui ont du sens et qui pulsent point de vue performance. Avoir des bons outils c’est essentiel pour bien faire son job, là je me sens bien équipé. C’est cool 🙂

Utilisation des attributs d’un cube dans SSRS

L’utilisation des attributs de membres d’un cube SSAS dans un rapport SSRS ne se fait pas de manière transparente.

En effet, alors que les attributs sont visibles dans l’éditeur graphique de source de données SSAS de Reporting Services, ils ne sont pas sélectionnables.

Pour y avoir accès dans un rapport, il est nécessaire de basculer en mode Requête MDX, et de modifier sa requête de la manière suivante:

SELECT

 NON EMPTY { [Measures].[...]} ON COLUMNS,
 NON EMPTY { ([Dimension].[Hierarchy].[Level].ALLMEMBERS * [Dimension].[Hierarhcy].[Level].ALLMEMBERS * ... ) }
 DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME, [Dimension].[Hierarchy].[Level].[Attribute]
 ON ROWS

FROM    [Cube]
WHERE  ...

Les mots clefs DIMENSION PROPERTIES permettent de passer les valeurs des attributs directement dans le résultat de la requête MDX, les rendant accessibles dans l’onglet mise en page de SSRS.

Ainsi, dans le rapport, la syntaxe à utiliser sera :

Fields!######("Nom de l'attribut")

Sources: Braulio Malaga (Avanade) / MSDN

Reporting Services, des fois je te hais. Vraiment.

Et une journée de perdue, une!

En même temps c’est de ma faute, je n’ai pas vérifié les versions de Visual Studio installées sur les postes des développeurs en arrivant, et ça n’a pas raté, merci les versions d’outre-tombe!

J’ai eu droit au bug catastrophe dans SSRS : les requêtes MDX qui disparaissent dans les datasets des rapports.

Pour faire court, BIDS se mélange parfois les pinceaux dans les RDL. Ce faisant il oublie de fermer les tags qui marquent le fait qu’un dataset SSAS utilise une requête MDX spécifique et pas le designer de base… Ce qui spécifiquement déclenche le bug, je ne saurais pas le dire…

Quand ça arrive, il n’arrive évidemment pas à ouvrir la requête peaufinée aux petits oignons dans son designer tout étriqué, et donc il plante. Et quand il plante, il efface la requête…

Hum, hum…

Et malheureusement, quand ça arrive, ça ne se manifeste que lorsqu’on ouvre un dataset. Si on ne touche pas aux datasets et qu’on ne fait que du design/aperçu,  le bug n’apparaît pas!

Hum, hum…

Donc au programme de la journée nous avons:

  • Installation du SP1 de Visual Studio sur tous les postes
  • Extraction dans NotePad++ des requêtes MDX depuis les RDL ressortis du SVN
  • Insertion des requêtes dans tous les rapports, remappage de tous les paramètres (oh joie)
  • Allumage d’un cierge en espérant que l’installation du SP1 suffira à régler le problème…

Chouette! ><

Update 05/09/2010 : Le cierge a fait son effet, on a pu se remettre au boulot!

Au final c’était un seul des postes qui était responsable, et pour lui on a été obligé de faire une réinstall complète. Et lors de l’installation du SP3 de SQL Server, un des KB (le KB955706) ne passait pas. On a dû la réinstaller à la main. Passé ça, tout est rentré dans l’ordre.

Accéder aux attributs d’un cube dans SSRS

L’utilisation des attributs de membres d’un cube SSAS dans un rapport SSRS ne se fait pas de manière transparente.

En effet, alors que les attributs sont visibles dans l’éditeur graphique de source de données SSAS de Reporting Services, ils ne sont pas sélectionnables.

Pour y avoir accès dans un rapport, il est nécessaire de basculer en mode Requête MDX, et de modifier sa requête de la manière suivante:

SELECT

 NON EMPTY { [Measures].[...]} ON COLUMNS,
 NON EMPTY { ([Dimension].[Hierarchy].[Level].ALLMEMBERS * [Dimension].[Hierarhcy].[Level].ALLMEMBERS * ... ) }
 DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME, [Dimension].[Hierarchy].[Level].[Attribute]
 ON ROWS

FROM    [Cube]
WHERE  ...

Les mots clefs DIMENSION PROPERTIES permettent de passer les valeurs des attributs directement dans le résultat de la requête MDX, les rendant accessibles dans l’onglet mise en page de SSRS.

Ainsi, dans le rapport, la syntaxe à utiliser sera : Fields!######(« Nom de l’attribut »)

Sources:
Blog de Braulio Malaga (Avanade)
MSDN