Motivation
In der Regel lesen Cubes (tabulare Modelle) Daten aus einem DWH, also meist relationalen Datenbanken. Es kann aber sinnvoll sein, das Ergebnis eines Cubes als Quelle für einen anderen Cube zu verwenden. In einem Kundenprojekt benötigten wir das, weil die Preisberechnung in einem Cube implementiert war und in einem anderen Cube verwendet werden sollte.
Dann hat man also ein oder mehrere DAX-Statements, die man in den Cube laden will. Während die Verwendung eines DAX-Statements gradlinig funktioniert, ist es schwer, mehrere DAX-Statements (mit dem gleichen Cube als Quelle) zu verwenden.
Dieser Post zeigt, wie es funktioniert.
Problem
Nehmen wir für unser Beispiel an, dass wir zwei DAX-Abfragen von einem bestehenden Cube in einen neuen Cube importieren wollen. Um ein einfaches Beispiel zu haben, verwenden wir als Abfrage einfach:
evaluate {1}
Dies liefert eine Tabelle mit einer Spalte („[Value]“) und einer Zeile, die den Wert 1 hat.
Um diese Abfrage in den neuen Cube zu laden, gehen wir ganz normal im Visual Studio vor – genauso wie man es in Power BI auch machen würde:
Wir definieren eine neue Datenquelle vom Typ „SQL Server Analysis Services-Datenbank“:
Um diese Daten als Tabelle im Cube zu haben, muss man auf der Datenquelle rechte Maustaste > „Import New Tables“anklicken. Das Power-Query-Fenster öffnet sich:
Über „Import“ wird diese Abfrage automatisch ausgeführt und man erhält eine neue Tabelle mit Namen „Table“, die nur eine Spalte „[Value]“ hat und dort eine Zeile mit 1:
Versucht man aber nun analog eine zweite Abfrage (mit evaluate {2}
) auszuführen, scheint es zunächst zu funktionieren. Allerdings wird keine zweite Datenquelle angelegt, sondern die bestehende verändert, so dass bei einem Refresh die oben gezeigte Tabelle eine 2 statt einer 1 zeigt.
Es ist nicht möglich, zwei unterschiedliche DAX-Abfragen auf dieselbe Quelle abzufeuern
Man muss es also anders probieren.
Lösungsidee
Bei meinem Blogeintrag zur Datumsdimension in SSAS hatte ich bereits die M-Syntax Value.NativeQuery
verwendet. Dies werden wir hier mit der Datenquelle SSAS verwenden.
Dazu gehen wir wie folgt vor:
Zunächst löschen wir die Datenquelle mit dem DAX-Statement und erstellen eine neue, allerdings ohne Eingabe eines DAX-Statements.
Im zweiten Fenster geben wir nur die Connection an:
Um auszuprobieren, ob sie funktioniert, gehen wir mit der rechten Maustaste auf diese Verbindung und klicken auf „Import new tables“:
Wir sollten darauf achten, dass wir eine kleine Tabelle auswählen. Dann klicken wir auf „Load“.
Damit wird eine neue Tabelle namens „Model“ mit dieser Struktur angelegt. Das M hinter der Abfrage finden wir in den Eigenschaften bei Source Data:
Das M-Statement sieht so aus:
let Source = #"Name der Datenquelle", Model1 = Source{[Id="Model"]}[Data], Model2 = Model1{[Id="Model"]}[Data], #"Added Items" = Cube.Transform(Model2, { {Cube.AddAndExpandDimensionColumn, "[DimChannel]", {"[DimChannel].[ChannelDescription].[ChannelDescription]", "[DimChannel].[ChannelKey].[ChannelKey]"}, {"DimChannel.ChannelDescription", "DimChannel.ChannelKey"}} }) in #"Added Items"
Wenn wir das Statement nun so abändern, haben wir die Problemstellung gelöst:
let Source = #"Name der Datenquelle", Tabelle = Value.NativeQuery(Source, "evaluate {1}") in Tabelle
Nach dem Löschen der alten zwei Spalten und einem Refresh, sieht das Ergebnis wie gewünscht aus:
Um nun zu zeigen, dass die zweite Abfrage auch funktioniert, erstellen wir einfach eine zweite Partition mit der gleichen M-Abfrage, wobei wir nur 1 durch 2 ersetzen:
Nach einem Refresh auf die Tabelle haben wir das Ergebnis:
Damit sind wir fertig!
Ich habe absichtlich das Beispiel der Partitionen verwendet, da dies ein klassisches Anwendungsszenario ist, bei dem mehrere Abfragen auf die gleiche Quelle abgefeuert werden müssen.
Optimierungen
Der Nachteil an dieser Implementierung ist, dass das DAX-Statement immer komplett geschrieben werden muss.
Wenn man sich aber nun vorstellt, dass es ein kompliziertes DAX-Statement ist, das sich je Partition nur um eine Zahl unterscheidet (in unserem Beispiel eine Zahl von 0 bis 99), dann möchte man das DAX-Statement nicht 100x schreiben – weil das natürlich sehr schlecht zu warten ist.
Deswegen sehen bei uns die M-Befehle so aus:
let Source = #"Name der Datenquelle", P = Value.NativeQuery( Source, Text.Combine({Vorne, "1", Hinten}, "") ) in P
Wir sehen, dass wir zwei Variablen (Vorne und Hinten) verwenden. In der gezeigten Partition wird dann die Zahl 1 zwischen Vorne und Hinten eingetragen.
In unserem Beispiel aus dem Blog wäre
Vorne = "evaluate {"
und
Hinten = "}"
Jetzt ist nur noch die Frage, wo wir diese Variablen hinterlegen können. Das sind die Expressions:
Um diese zu erstellen, klicken wir mit der rechten Maustaste auf „Expressions“ und dann auf „Edit Expressions“:
Dann legen wir eine leere Query an:
Die Query können wir umbenennen über die Name-Property. Den Wert der Variablen tippen wir oben einfach ein:
In unserem echten Anwendungsfall haben wir den Text (hier evaluate {
) nicht einfach eingetragen, sondern mit = zugewiesen, also = "evaluate {"
geschrieben. Dann muss man Anführungszeichen, die in dem DAX-Statement vorkommen, verdoppeln, da der Interpreter sonst denkt, der String wäre zu Ende.