Dynamisches Top n in SQL 2000

27. Juni 2011

Ab SQL Server 2005 kann man TOP n über SELECT TOP (@n) … erledigen:

declare @n integer
set @n = 2

select top (@n) a
from
(select 1 as a
union all
select 2
union all
select 3
union all
select 4
union all
select 5
union all
select 6
) as t
order by a desc

Das liefert als Ergebnis

a
———–
6
5

(2 row(s) affected)

In früheren Versionen geht das leider nicht. Da hilft die Verwendung von set rowcount, das die Anzahl der zurückgegebenen Zeilen definiert:

declare @n integer
set @n = 2

set rowcount @n

select distinct  a
from
(select 1 as a
union all
select 2
union all
select 3
union all
select 4
union all
select 5
union all
select 6
) as t
order by a desc

Das liefert das gleiche Ergebnis wie oben.

Allerdings muss man danach wieder

set rowcount 0

absetzen, damit alle nachfolgenden SQL-Statements in dieser Session wieder alle Datensätze zurückliefern.

Performanceprobleme bei SSAS-Dimensionsabfragen aus SSRS

12. März 2011

Nachdem wir in einem Projekt unseren Test-Datenbestand (12 Mio Fakten) auf den Produktiv-Bestand (73 Mio Fakten) erweitert hatten, zeigten die Berichte (SSRS 2008 R2) massiv schlechte Antwortszeiten in bestimmten Berichten auf unseren Cube (SSAS 2008 R2) - jeweils nach der Cubeaufbereitung. Somit war klar, dass Ursache war, dass einige der im Bericht verwendeten Abfragen nicht im Cache waren, da dieser ja durch die inkrementelle Dimensionsaufbereitung und Aufbereitung einiger Cubepartitionen gelöscht wird.

Naheliegende Strategien waren:

  • Aufteilung in mehr Partitionen unter Angabe der Slice-Property
  • Verbesserte Aggregationen über Usage based Aggregation Design
  • Cache Warming - Strategien

All das brachte uns aber nicht wirklich weiter.

Letztendlich stellte sich aber heraus, dass die Ursache gar nicht in den (tlw. komplexen) Abfragen zur Ermittlung der Fakten zu suchen war, sondern in “Dimensions-Abfragen”. Damit meine ich folgendes: Unsere Berichte werden (aus einer eigenen Web-Applikation heraus) mit Parametern aufgerufen, die IDs sind (eigentlich die Member Unique Names, also z.B. Datum.Jahr.&[2010]). Dabei haben die Parameter (aus Performancegründen) keine Datasets, die alle verfügbaren Werte enthalten. Nun wollen wir aber in dem Bericht natürlich auch den übergebenen Wert im Klartext anzeigen, also in obigem Beispiel das Jahr 2010. Deswegen mussten also einfache Abfragen her, die aus dem Member Unique Name den Klartext (Member Caption) ermitteln.

In unserem Projekt hatten wir uns darauf geeinigt, wenn möglich den SSRS-Designer für SSAS-Abfragen zu verwenden, um eine gute Wartbarkeit (ohne tiefere MDX-Kenntnisse) zu erreichen.

Damit gibt es zwei einfache Arten, dieses Problem zu lösen:

Die erste Art, hat eine Spalte - das Jahr - und zusätzlich den (MultiSelect) Report Parameter als Parameter für die Abfrage:

Wizard für die langsame Abfrage
(Bild zum Vergößern anklicken!)

Als MDX ergibt sich:

SELECT { } ON COLUMNS, { ([Datum].[Jahr].[Jahr].ALLMEMBERS ) } DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME ON ROWS FROM ( SELECT ( STRTOSET(@DatumJahr, CONSTRAINED) ) ON COLUMNS FROM [Verkaeufe]) CELL PROPERTIES VALUE

Man kann auch die Abfrage etwas erweitern:

Man fügt ein berechnets Measure (in diesem Fall namens “UniqueName”) mit dem MDX

[Datum].[Jahr].CurrentMember.UniqueName

hinzu:

Wizard der schnellen Abfrage
(Bild zum Vergößern anklicken!)

Damit ergibt sich folgendes MDX:

WITH MEMBER [Measures].[UniqueName] AS [Datum].[Jahr].CurrentMember.UniqueName SELECT NON EMPTY { [Measures].[UniqueName] } ON COLUMNS, NON EMPTY { ([Datum].[Jahr].[Jahr].ALLMEMBERS ) } DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME ON ROWS FROM ( SELECT ( STRTOSET(@DatumJahr, CONSTRAINED) ) ON COLUMNS FROM [Verkaeufe]) CELL PROPERTIES VALUE, BACK_COLOR, FORE_COLOR, FORMATTED_VALUE, FORMAT_STRING, FONT_NAME, FONT_SIZE, FONT_FLAGS

Anmerkung: Das dabei enstehende versteckte Dataset zum Befüllen der “verfügbaren Werte” des Parameters löschen wir, da wir ja keine “verfügbaren Werte” anzeigen wollen (unsere Dimension hatte zu viele Einträge).

Beide Abfragen scheinen dasselbe zu tun und mit der selben Performance. Dies ist aber ein Trugschluss!

Leeren wir zunächst (und vor Ausführung eines neuen Test-Statements) den SSAS-Cache mit

<ClearCache xmlns=”http://schemas.microsoft.com/analysisservices/2003/engine”>
<Object>
<DatabaseID>SimpleCube</DatabaseID>
<CubeID>Verkaeufe</CubeID>
</Object>
</ClearCache>

(siehe dazu auch: http://www.ssas-info.com/analysis-services-faq/27-mdx/133-mdx-how-do-i-clear-analysis-services-ssas-database-cache)

Dann betrachten wir die Darstellung im SQL Server Profiler (mit den Standard-Einstellungen für den SSAS):

Die erste Abfrage ergibt folgendes Bild:

SQL Profiler Ergebnis der langsamen Abfrage
(Bild zum Vergößern anklicken!)

Schauen wir uns die einzelnen Zeilen an:

  • Session Initialize - spricht für sich
  • Query Begin: Hier fängt die Abfrage an - im unteren Bereich sieht man das MDX und die Parameterwerte für die @Parameter im MDX
  • Progress Report Begin: Daten aus einer Partition werden gelesen (!), d.h. von der Festplatte in den Speicher übernommen.
  • Progress Report End: das dazugehörige Ende
  • Query SubCube: die gelesenen Daten werden verwendet, um die Abfrage zu beantworten (in diesem Fall Non-Cache)
  • Query End: Das Ende der Abfrage

Die zweite Abfrage ergibt ein anderes Bild:

SQL Profiler Ergebnis der schnellen Abfrage
(Bild zum Vergößern anklicken!)

Hier sehen wir, dass kein Zugriff auf eine Partition erfolgt!

Der Zugriff auf die Partition ist natürlich schädlich, da er bei großen Datenmengen lang dauern kann, zumal er gar nicht benötigt wird, da wir ja nur Dimensions-Element-Bezeichnungen abfragen wollen.
Es ist nicht erklärbar, warum SSAS hier dennoch auf die Fakten zugreift.
Deswegen ist die zweite Abfrage auf jeden Fall vorzuziehen!

Noch ein interessante Anmerkung zum Schluss: Wenn man die erste Abfrage im SQL Server Management Studio ausführt, ist sie auch nicht langsam. Da im Management Studio keine parametrisierten Abfragen möglich sind, muss man dazu die Parameter durch die entsprechenden Strings ersetzen, und erhält somit folgendes MDX:

SELECT { } ON COLUMNS, { ([Datum].[Jahr].[Jahr].ALLMEMBERS ) } DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME ON ROWS FROM ( SELECT ( STRTOSET(”{ [Datum].[Jahr].&[2001],[Datum].[Jahr].&[2008] }”, CONSTRAINED) ) ON COLUMNS FROM [Verkaeufe]) CELL PROPERTIES VALUE

Führt man dieses MDX aus (nachdem man den Cache geleert hat), so erhält man folgendes Bild:

Ergebnis des Profilers der langsamen Abfrage ausgeführt im Management Studio
(Bild zum Vergößern anklicken!)

Man sieht hier also auch nur ein Query Begin und ein Query End, also auch keinen Zugriff auf die Cube-(bzw. Partitions-)Daten.

Dies hatte uns die Fehlersuche erschwert, da wir natürlich zunächst auf der Suche nach dem verantwortlichen Statement alle Statements im SQL Server Profiler mitgeschnitten und dann einzeln im Management Studio ausgeführt hatten, was - wie eben gesehen - das problematische Statement leider nicht offenbart.

Säulendiagramme mit Datum auf der x-Achse

23. Februar 2011

Bekanntermaßen kann man in Reporting Services alle Standard-Charts darstellen.

Bei den Säulen-Diagrammen hat man sogar - was ich heute zeigen möchte - die Möglichkeit, auf der x-Achse Datumswerte anzuzeigen. Dies ist z.B. in (Pseudo-) Gantt-Charts interessant.

Für unser Beispiel wollen wir die Laufzeit von zwei Phasen eines Projekts darstellen:

Phase 1 läuft vom 1.3.2011 bis zum 20.3.2011, wobei die Zeit ab dem 17.3.2011 rot dargestellt werden soll.
Phase 2 läuft vom 15.3.2011 bis zum 7.4.2011, wobei die Zeit ab dem 1.4.2011 rot dargestellt werden soll.

Das Ziel sieht somit so aus:

Ziel der 2 Phasen mit Datum auf x-Achse
(Zum Vergrößern anklicken)

Um dies zu erreichen, geben wir folgendes im Chart an:

  • als Kategorie: die Phase
  • als Reihe: die Farbe
  • als Wert: das Datum

Allerdings ist das Datum nicht einfach einzugeben. Wir müssen vielmehr folgendes einstellen (examplarisch an der Phase 1 gezeigt):

  • ein transparenter Balken bis zum 1.3.2011
  • ein grüner Balken bis zum 17.3.2011, also 16 Tage breit
  • ein roter Balken bis zum 20.3.2011, also 3 Tage breit

Für den ersten Balken können wir als Wert einfach den 1.3.2011 angeben. Für die darauffolgenden Balken müssen wir aber das Datum als Wert angeben, das die Anzahl der Tage auf den 30.12.1899 addiert, also

  • für einen Balken mit 1 Tag Länge: 31.12.1899
  • für einen Balken mit 2 Tagen Länge: 1.1.1900
  • für einen Balken mit 2,5 Tagen Länge: 1.1.1900 12:00
  • für einen Balken mit 3 Tagen Länge: 2.1.1900
  • für einen Balken mit 16 Tagen Länge: 15.1.1900
  • usw.

Natürlich müssen wir darauf achten, dass die Reihenfolge der Balken stimmt. Deswegen muss die Reihe entsprechend sortiert sein.

Außerdem werden wir die x-Achse so formatieren, dass sie die Datumswerte schön anzeigt.

Nun haben wir alles beisammen, um das Projekt zu schaffen:

Als SQL wählen wir:

SELECT ‘Phase 1′ as Phase, ‘transparent’ as Farbe, 1 as sort, convert(Datetime, ‘1.3.2011′, 104) as wert
UNION ALL
SELECT ‘Phase 1′ as Phase, ‘green’ as Farbe, 2 as sort, convert(Datetime, ‘15.1.1900′, 104) as wert
UNION ALL
SELECT ‘Phase 1′ as Phase, ‘red’ as Farbe, 3 as sort, convert(Datetime, ‘2.1.1900′, 104) as wert
UNION ALL
SELECT ‘Phase 2′ as Phase, ‘transparent’ as Farbe, 1 as sort, convert(Datetime, ‘15.3.2011′, 104) as wert
UNION ALL
SELECT ‘Phase 2′ as Phase, ‘green’ as Farbe, 2 as sort, convert(Datetime, ‘16.1.1900′, 104) as wert
UNION ALL
SELECT ‘Phase 2′ as Phase, ‘red’ as Farbe, 3 as sort, convert(Datetime, ‘5.1.1900′, 104) as wert

Und im Chart muss folgendes eingestellt werden:

Es ist ein Stacked Bar Chart:

Stacked Bar Chart

Die Grundeinstellungen sieht man hier:

Einstellungen des Charts

Die genauen Eigenschaften der Kategorie (Phase) sind:

General Eigenschaften der Category
(Zum Vergrößern anklicken)

und

Sorting Category
(Zum Vergrößern anklicken)

Die genauen Eigenschaften der Reihe (Farbe) sind:

Generelle Eigenschaften der Reihe
(Zum Vergrößern anklicken)

und

Sortier-Eigenschaften der Reihe
(Zum Vergrößern anklicken)

Die genauen Eigenschaften der Werte sind:

Einstellungen der Werte 1
(Hier darauf achten, dass nicht count(Wert) dort steht (wie es SSRS beim Anklicken erstellt), sondern nur wert!

und

Fülleigenschaften der Werte
(Hier darauf achten, dass unter Farbe der Wert “=Fields!Farbe.Value” steht, zu erreichen über das fx-Symbol)

Dann muss nur noch die x-Achse korrekt eingestellt werden:

Optionen der Achse
(Zum Vergrößern anklicken)

Dabei sind in diesem Fall als Minimum “=DateSerial(2011, 2, 27)” und als Maximum “=DateSerial(2011,4,10)” eingestellt. Natürlich sollte man das “in Wirklichkeit” aus den Daten über min / max und ggf. dateadd von Tagen berechnen.

Als Datumsformat ist das deutsche Format eingestellt:

Datumsformat der x-Achse

Um die kleinen Zacken pro Tag (ohne Datumsanzeige) zu haben (wie man sie oben in dem Ziel-Chart sehen kann), habe ich noch die minor tick marks auf 1 gesetzt:

Minor Tick Marks der x-Achse
(Zum Vergrößern anklicken)

Die vollständige rdl-Datei kann hier heruntergeladen werden.

Dieses Projekt lässt sich zu einem Pseudo-Gantt-Chart ausbauen, in dem dann (nur) die senkrechten Linie und Pfeile fehlen, was für eine erste Orientierung allerdings ausreichend sein dürfte.

Natürlich lassen sich auch andere Visualisierungen damit realisieren, z.B. Darstellung von Up-/Down-Time von Maschinen etc.

Analysis Services Formatierungen (inkl. Excel-Bug bei $)

6. Oktober 2010

In Analysis Services können “echte” Measures und berechnete Measures formatiert werden (sprich mit Tausender-Trenner, Nachkommastellen und sonstigen Bezeichnern wie cm oder € verschönert werden). Das erhöht die Lesbarkeit und verhindert Verwechslungen (”Ist die Dauer in Stunden oder Minuten?”)

Als Formatstrings können die gängen Bezeichnungen verwendet werden:

  • , bedeutet einen Tausendertrenner
  • . bedeutet den Dezimalpunkt
  • 0 bedeutet, dass diese Stelle immer belegt ist
  • # bedeutet, dass diese Stelle angezeigt wird, falls nötig
  • In Anführungszeichen (”) können beliebige Texte eingegeben werden

So ist #,##0.00 “€” meine Standardformatierung in €, also zum Beispiel 17,33 € oder 1.522,12 € (auf deutschen Clients).

Interessanter Weise schlägt das BIDS auf deutschen Clients #.##0,00 vor. Das ist aber falsch und wird leider nicht zum Ziel führen.

Measure

Bei berechneten Measures gilt an sich das gleiche. Allerdings müssen die Strings mit ” umschlossen werden. Falls in dem Formatstring selbst ein Anführungszeichen enthalten ist, muss es verdoppelt werden (wie in der alten VB-Syntax).

Obiger Formatstring ist dann “#,##0.00 “”€”"”

berechnetesMeasure

berechnetesMeasure2

Verwendet man Excel als Client, ist ein Bug in Excel zu beachten:

Ich gehe davon aus, dass das Excel ein deutsches Excel ist. Obiger Formatstring funktioniert dann wunderbar. Auch der Formatstring “#,##0.00 “”£”"” funktioniert wunderbar. Allerdings funktioniert “#,##0.00 “”$”"” nicht, statt dessen zeigt Excel € an. Ich erkläre mir das so: Diese Formatierung entspricht der Formatierung “Währung”, die auf deutschen Rechnern eben als € umgesetzt wird. Hier hilft vor oder hinter das Währungssymbol $ ein Leerzeichen einzufügen, also z.B. “#,##0.00″” $”"”