Benutzerdefinierte Sortierung in DAX

Von Lukas Hillesheim, 21. Juli 2023
Blog Article & Copyright by Lukas Hillesheim
In der DAX-Sprache kann Sortierung erst am Schluss - bei der Ausgabe der Daten - angewandt werden, nicht jedoch zwischendurch, um beispielsweise Ergebnisse von Teil-Operationen als sortierte Menge in Variablen abzulegen. Der folgende Artikel zeigt, wie dieses Ziel mit der Hilfe von GENERATE und GENERATESERIES erreicht werden kann.

Der Artikel verwendet ein Beispiel mit dem folgenden Aufbau:

  • - Schritt 1: Eine Variable "set0_RankedCities" wird mit einer Liste von Städte-Namen, deren Umsatz und einer entsprechenden Rank-Information gefüllt.
  • - Schritt 2: Der Inhalt von "set0_RankedCities" wird in eine weitere Variable namens "set1_SortedCities" kopiert. Der Kopiervorgang erfolgt im Rahmen einer ForEach-Schleife, die pro Schleifen-Durchgang das Element mit dem aktuellen Rank findet und in das Ergebnis einfügt. Da die Schleife eine Suche der Ranks in aufsteigender Reihenfolge bewirkt, ergibt sich im Ergebnis eine Sortierung nach Rank. Die zweite Variable enthält somit den sortierten Inhalt der ersten Variablen.
  • - Schritt 3: Der Inhalt der zweiten Variablen, "set1_SortedCities", wird in einer Spalte als konkatenierter String ausgegeben. Beachten Sie, dass der in sich sortierte String nicht mit ORDER BY generiert werden könnte.

1. Erzeugen einer Liste mit Rank-Information

Ausgangspunkt ist eine unsortierte Liste mit Städten, deren Umsatz und eine einer entsprechenden Rank-Information. Die Rank-Information implementiert das bentzerdefinierte Sortierungs-Kriterium und wird später wie ein Index verwendet. Anbei DAX-Code und Output:
Figure 1 - Unsorted List of Cities incl. Amount and Rank
Figure 1 - Unsorted List of Cities incl. Amount and Rank
Liste der Städte mit Rank (jedoch nicht sortiert nach dem Rank):
Figure 2 - Unsorted List - Output
Figure 2 - Unsorted List - Output

2. Erzeugen einer sortierten Liste

Die sortierte List wird mit folgendem Ausdruck erzeugt:
Figure 3 - Sorted List
Figure 3 - Sorted List
Die Grundidee besteht darin, eine ForEach-Schleife in DAX nachzubilden und durch das erste Argument - die Collection - zu steuern, in welcher Reihenfolge die Elemente ausgewählt werden. Pro Schleifendurchgang soll eine bestimmte Zeile aus der Quell-Menge extrahiert werden und in das Ergebnis - die Variable "set1_SortedCustomers" - eingefügt werden.
  • - GENERATE ist das DAX-Pendant zu einer ForEach-Schleife
  • - GENERATESERIES wird als erstes Argument für GENERATE verwendet und ist dafür zuständig, den ersten Teil der Schleife, die Collection, festzulegen
  • - FILTER wird als zweites Argument für GENERATE verwendet und entspricht dem Block, der pro Schleifendurchgang ausgeführt wird

Vergleich zwischen GENERATE und einer OOP-basierten Schleife:

  • GENERATE ( // ForEach (...)
  • <set1>, // ForEach ( <item> in <collection> )
  • <set2> ) // ForEach ( <item> in <collection> ) { <operation> }
Im konkreten Beispiel soll über eine Integer-Liste iteriert werden. Die Integer-Liste soll die Liste der Rank-Values widerspiegeln. Beispielsweise enthält die Liste der Städte 8 Elemente, die entsprechend von 1 bis 8 gerankt sind. Daher soll das erste Argument für GENERATE eine Liste der Ganzzahlen von 1 bis 8 sein. Diese Liste wird mit GENERATESERIES erzeugt.
Figure 4 - GENERATESERIES
Figure 4 - GENERATESERIES
Figure 5 - GENERATESERIES Output
Figure 5 - GENERATESERIES Output
Beachten Sie, dass das Ergebnis von GENERATESERIES eine Spalte namens [Value] enthält. Auf diese Spalte wird in späteren Code-Zeilen verwiesen.
Die Ganzzahl-Liste soll bei 1 starten. Der Upperbound - der Endwert - soll dynamisch festgelegt werden und der Anzahl der Elemente in der Liste der Städte entsprechen. Hierfür wird COUNTROWS verwendet.
Figure 6 - GENERATESERIES with Dynamic Upperbound
Figure 6 - GENERATESERIES with Dynamic Upperbound
Das zweite Argument von GENERATE ist ebenfalls ein Mengen-Ausdruck, der pro Schleifendurchgang evaluiert wird. Mit Hilfe von FILTER werden aus der Liste der Städte alle Städte extrahiert, deren Rank identisch mit dem Wert des Schleifenzählers sind. Da die Rank-Werte auf Grund der Option "DENSE" eindeutig sind, wird pro Schleifendurchgang exakt eine Stadt gefunden und in das Ergebnis eingefügt.
Figure 7 - FILTER used as Loop-Block
Figure 7 - FILTER used as Loop-Block

3. Ausgabe der sortierten Liste als konkatenierter String

Die Ausgabe einer sortierten Liste gehört nicht zum Lösungs-Ansatz, sondern soll quasi als Beweis veranschaulichen, dass die Sortierung VOR der Ausgabe passiert:
Figure 8 - Concatenated String
Figure 8 - Concatenated String
Figure 9 - Concatenated String Output
Figure 9 - Concatenated String Output

4. Kompletter Code

Figure 10 - Complete Code
Figure 10 - Complete Code