Archiv der Kategorie: MS Analysis Services

Microsoft Analysis Services 2005, 2008, 2008 R2, 2012, 2014

Migration SSAS auf 2016: Übernahme der Display Folder

In SSAS 2016 werden endlich Display Folder nativ unterstützt. Bisher war das nur durch die Verwendung des BIDS Helper-Projekts (s. https://bidshelper.codeplex.com/ bzw. https://bideveloperextensions.github.io/) möglich.

Nun stellt sich aber die Frage, wie man bei einer Migration von einer früheren SSAS-Version auf SSAS 2016 die dortigen Display Folder überträgt, da sie leider bei einer „normalen“ Migration via Visual Studio verloren gehen, da Visual Studio die BIDS-Helper-spezifischen Einstellungen (BIDS Helper speichert diese Informationen als Annotations in einem eigenen Format) nicht kennt.

Bei der Suche nach einer Antwort fand ich die Seite http://www.kapacity.dk/migrating-ssas-tabular-models-to-sql-server-2016-translations-and-display-folders/.
Dort wird – kurz zusammengefasst – vorgeschlagen, das neue Objekt-Modell zu verwenden, um die Display Folder zu schreiben. Das (jetzt in SSAS 2016 sehr einfache) Objekt-Modell habe ich bereits in meinem letzten Blog-Eintrag beschrieben. Der Artikel schlägt vor, die Annotations vom BIDS Helper zu parsen und daraus die zu verwendenden Display Folder zu ermitteln.
Diese Display Folder werden dann in den – bereits bereitgestellten – Cube geschrieben. Um daraus wieder eine Solution zu erhalten, muss man dann nur noch eine neue Solution aus diesem Cube erstellen.

Ich halte den Weg sehr elegant, das Objektmodell zu verwenden. Allerdings halte ich das Parsen der Annotations für unelegant und fehleranfällig. Deswegen gehe ich einen anderen Weg:
Nachdem wir den migrierten Cube deployt haben, haben wir ja zwei Cubes in unserer Umgebung:
A: der alte Cube (z.B. SSAS 2012 / 2014) mit Display Folders (BIDS)
B: der neue Cube SSAS 2016 ohne Display Folder

Nun habe ich ein einfaches SSIS-Paket erstellt, das über DMVs (Dynamic Management Views) auf den alten Cube A zugreift und für alle Measures und (echte oder berechnete) Columns die Display Folder ausliest und sie dann über das Objektmodell, wie im vorletzten Beitrag beschrieben, in den neuen Cube schreibt.

Dabei verwende ich folgende DMVs:

  • $SYSTEM.MDSCHEMA_MEASURES für Measures: Dort interessieren mich alle Measures (MEASURE_NAME) und ihre Display Folder (MEASURE_DISPLAY_FOLDER), sowie der MEASUREGROUP_NAME. Im Tabular Model entspricht der MEASUREGROUP_NAME dem Tabellennamen. Als Cube filtern wir auf „Model“.
  • $System.MDSCHEMA_HIERARCHIES für Columns: Hier interessieren mich HIERARCHY_CAPTION, HIERARCHY_DISPLAY_FOLDER. Als Cube filtern wir auf alles außer „Model“. Dies funktioniert nur in den Versionen 2012 und 2014 (aber das ist hier ja genau der Fall). Dann tauchen nämlich alle Tabellen als CUBE_NAME auf – mit vorangestellten $ (also $Currency für Tabelle Currency). Jede Spalte steht dann in HIERARCHY_CAPTION.

Zu beachten ist, dass teilweise in deutschen Versionen der Cube nicht „Model“ sondern „Modell“ heißt. Dann müssen die Abfragen natürlich entsprechend angepasst werden.

Der Code zum Aktualisieren ist dann sehr einfach:
Der neue Cube (und Server) sind Variablen im SSIS-Paket.
Der Code verbindet sich darauf und holt dann das Model in die Variable _model.
Letzlich ist der Code dann nur:
Table table = _model.Tables[tableName];
if (table.Measures.Contains(measureName))
{
Measure m = table.Measures[measureName];
m.DisplayFolder = displayFolder;
}

Und zum Abschluss (also im PostExecute) werden die Änderungen auf den Server gespielt:
_model.SaveChanges();
Für Columns funktioniert es analog.

Für Details zu den einzubindenden DLLs, s. mein früherer Blog-Eintrag.

Im Übrigen habe ich auf einen Fehler verzichtet, falls ein Measure oder eine Column nicht im neuen Cube B vorhanden ist. Bei uns hatte das den Hintergrund, dass wir etliche Spalten / Measures für Fremdwährungen hatten, die wir per Code erstellten, so dass sie in A vorhanden, in B aber (noch) nicht vorhanden waren. Diese nicht in B gefundenen Spalten/Measures werden von dem jeweiligen Skript ausgegeben.

Zusammenfassend gehe ich also wie folgt vor:
1. Migration der Solution auf SSAS 2016 – dadurch gehen die Display Folders verloren
2. Deploy auf einen Entwicklungs-SSAS
3. Kopieren der Display Folder von der alten Cube-Instanz auf die neue
4. Erstellen einer neuen Solution aus dem nun fertigen Cube.

Das beschriebene ETL-Paket habe ich hier als zip inkl. Solution oder hier nur als dtsx-File hochgeladen. Um es zu verwenden, muss man die beiden Variablen mit den Infos zum 2016er Cube und die Connection auf den 2012er Cube anpassen.

SSAS 2016: Tabular Cube verarbeiten in SSIS

Nachdem sich in SSAS 2016 im Hintergrund einiges geändert hat, wird die Verarbeitung eines Cubes nun mittels JSON-Syntax und nicht mehr XMLA ausgeführt, z.B.
{
"refresh": {
"type": "automatic",
"objects": [
{
"database": "AMO2016Test"
}
]
}
}

Damit wird der Cube AMO2016Test verarbeitet.
(Ein solches Skript kann man sich wie gehabt im Management Studio über den Skript-Button in den Dialogen erzeugen lassen)

Grundsätzlich verwenden wir SSIS, um solche automatisierten Tasks durchzuführen (damit wir Standard-Features wie Protokolierung, Fehlerhandling etc. nutzen können).
Die Standard-Verarbeitungs-Komponente kann man nicht verwenden, da sie XMLA-basiert ist.

Aber man kann ganz einfach obigen Code in einer Execute-SQL-Task ausführen lassen, wenn man als Connection eine OLE-DB-Connection auf den Cube angelegt hat.
Es darf einen also nicht wundern, dass man einen JSON-Code in der Execute-SQL-Task ausführt. Da aber der Code einfach durchgereicht wird, ist klar, dass es funktioniert.

SSAS 2016 Tabular: Spalten, Measures etc. via C# erstellen

In einem Projekt erstellen wir Measures dynamisch: Für Währungen, die wir in einer relationalen Tabelle eintragen, werden automatisch Währungsumrechnungen aller Umsätze durchgeführt und dann im Cube automatisch angezeigt.

In SSAS vor der Version 2016 war das sehr kompliziert, da AMO (also das Objekt-Modell, auf das man via C# zugreifen konnte) noch dem MOLAP-Modell entspricht. Dort gab es also das Konzept z.B. berechneter Spalten nicht nativ. Deswegen gab es unter CodePlex ein AMO2Tabular-Projekt, mit dem versucht wurde, den Zugriff gekapselt zu ermöglichen.

In SSAS 2016 ist alles nun viel einfacher.

Hier ein Beispiel-Code (den wir in Integration Services eingebunden haten, da wir unsere Automatisierung als Teil unserer täglichen ETLs entwickelten):

Zunächst müssen Referenzen definiert werden:

  • AnalysisServices.Server.Tabular.dll
  • AnalysisServices.Server.Core.dll

Diese DLLs habe ich aus dem GAC genommen:
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.AnalysisServices.Tabular\v4.0_14.0…..
//using System.Data; - sonst ist DataColumn nicht mehr eindeutig
using Microsoft.AnalysisServices.Tabular;

//Diese Variablen entsprechend setzen
string serverName = @"<<meinServer>>";
string spaltenName = "Spalte";
string databaseName = "AMO2016TEST";
string tableName = "Tabelle";

string serverConnectionString = string.Format("Provider=MSOLAP;Data Source={0}", serverName);

//mit dem Server verbinden
Server server = new Server();
server.Connect(serverConnectionString);

//die Tabelle finden
Database db = server.Databases[databaseName];
Model model = db.Model;
Table table = model.Tables[tableName];

//physische Spalte hinzufügen
if (!(table.Columns.Contains(spaltenName)))
table.Columns.Add(
new DataColumn()
{
Name = spaltenName,
DataType = DataType.Int64,
SourceColumn = spaltenName,
Description = "test",
DisplayFolder = "neu",
IsHidden = false,
IsNullable = true,
FormatString = "0",
SortByColumn = table.Columns["SourceValue"],
//die neue Spalte wird nach der Spalte SourceValue sortiert
IsUnique = false,
}
);

//berechnete Spalte hinzufügen
if (!(table.Columns.Contains(spaltenName + "mal2")))
table.Columns.Add(
new CalculatedColumn()
{
Name = spaltenName + "mal2",
DataType = DataType.Int64,
Expression = "2*[" + spaltenName + "]",
//Expression enthält die DAX-Formel
Description = "Das doppelte der Spalte " + spaltenName,
DisplayFolder = "neu",
IsHidden = false,
IsNullable = true,
FormatString = "0",
SortByColumn = table.Columns["SourceValue"],
}
);

//Measure hinzufügen
if (!table.Measures.Contains("Sum_" + spaltenName))
table.Measures.Add(
new Measure()
{
Name = "Sum_" + spaltenName,
Expression = "SUM('” + tableName + '[" + spaltenName + "])",
Description = "Die Summe aller " + spaltenName,
DisplayFolder = "neu",
FormatString = "#,0.00",
IsHidden = false,
}
);

//Änderungen speichern
model.SaveChanges();

DAX: Uhrzeit-Measures

In meinem letzten Projekt hatten wir interessante Kennzahlen:

Es ging um Startzeiten von bestimmten Prozessen:

Gegeben war ein datetime-Feld “Beginn”.

Jetzt waren folgende Kennzahlen gewünscht:

  • Was ist der früheste Beginn?
    Zum Beispiel: In der KW9 um welche Uhrzeit haben folgende Maschinen jeweils begonnen?
    Maschine A: 8:00 Uhr
    Maschine B: 8:30 Uhr
    Maschine C: 7:30 Uhr
  • Wann war der durchschnittliche Beginn in einer Woche?
    Zum Beispiel:
    Mo 8:00 Uhr
    Di 9:00 Uhr
    Mi 8:30 Uhr
    Do 7:00 Uhr
    Fr 10:00 Uhr
    ergibt einen Durchschnitt von 8:30 Uhr

Wir haben es wie folgt implementiert:

Zunächst haben wir zwei berechnete Spalten definiert:

BeginnDatum als date(year([Beginn]); month([Beginn]); DAY([Beginn]))
BeginnUhrzeit als [Beginn]-[BeginnDatum]

Damit erhalten wir die Uhrzeit ohne Datum.

Damit ist die erste Kennzahl ganz einfach:

Erster Beginn:=MIN([BeginnUhrzeit])

Und der Durchschnitt ist auch nicht schwer:

Ø Erster Beginn:=Averagex(Values(‚Fakten_Operationen'[BeginnDatum]); [Erster Beginn])

Dabei ist der erste Parameter der Averagex-Funktion die Menge der Werte, nach denen die Kennzahl berechnet werden muss und worüber dann der Durchschnitt gebildet wird.

Deswegen haben wir hier die Datumswerte mit Values(‚Fakten_Operationen'[BeginnDatum]) verwendet.

Dies lässt sich natürlich einfach verallgemeinern.

SSAS Tabular DAX: DATEDIFF-Ersatz

In (tabular) Cubes möchte man möglicherweise Berechnungen durchführen, bei denen die Anzahl der Tage zwischen zwei Datumswerten ermittelt werden sollen.

Überraschender Weise gibt es keine DateDiff-Funktion in DAX.

Zwar kursieren im Web einige Beispiele für einen Ersatz, allerdings gefallen sie mir nicht.

Hintergrundwissen

Deswegen schauen wir uns zunächst näher an, wie in (tabular) Cubes Datumswerte gespeichert werden:

Datumswerte werden intern als Zahlen gespeichert, wobei der ganzzahlige Anteil die Anzahl der Tage seit 30.12.1899 angibt und der Nachkommaanteil die Uhrzeit.

Wir können das ganz leicht im Cube sehen, indem wir ein Datum mit 1.0 (deutsch 1,0) multiplizieren (im DAX reicht *1. bzw. *1, – je nachdem welche Lokalisierung eingestellt ist ) – oder über value(…) in eine Zahl konvertieren.

Ich habe einen kleinen Cube erstellt – mit folgender Datenquelle:

select convert(datetime, ‚17.8.2011 17:53:12‘, 104) as DatumUhrzeit, convert(date, ‚17.8.2011‘, 104) as Datum
UNION ALL
select convert(datetime, ‚1.3.2000 06:13:27‘, 104), convert(date, ‚1.3.2000‘, 104)
UNION ALL
select convert(datetime, ‚5.3.2000 01:13:27‘, 104), convert(date, ‚5.3.2000‘, 104)
UNION ALL
select convert(datetime, ‚1.1.1900 5:30:00‘, 104), convert(date, ‚1.1.1900‘, 104)
UNION ALL
select convert(datetime, ‚31.12.1899 17:30:00‘, 104), convert(date, ‚31.12.1899‘, 104)
UNION ALL
select convert(datetime, ‚27.12.2014 20:22:55‘, 104), convert(date, ‚27.12.2014‘, 104)
UNION ALL
select getdate(), convert(date, getdate())

Dann habe ich jeweils eine berechnete Spalte – wie oben beschrieben – hinzugefügt. Das Ergebnis:

image

DateDiff von Dates

Um nun die Differenz zwischen 2 Date-Spalten zu ermitteln, muss man sie nur voneinander abziehen. Das Ergebnis hat allerdings den Datentyp DateTime, weswegen man das Ergebnis noch in ein int verwandeln muss:

([Datum2] –[Datum1]) * 1

oder

int([Datum2]-[Datum1])

Um das zu demonstrieren, habe ich meine Quelle angepasst und das Ergebnis sieht so aus:

image

DateDiff von DateTimes

Wenn wir aber 2 DateTimes haben, dürfen wir sie nicht einfach voneinander abziehen (also int([DatumUhrzeit2]-[DatumUhrzeit1])), da die Uhrzeiten keine rolle spielen sollen. Stattdessen müssen wir die Datumswerte zunächst nach int konvertieren (dabei rundet DAX immer ab) und dann subtrahieren:

int([DatumUhrzeit2])-int([DatumUhrzeit1])

image

Wir sehen, dass die falsche Methode immer dann einen zu niedrigen Wert ausweist, wenn das abgezogene Datum eine größere Uhrzeit als der Minuend aufweist.

Erweiterungen: Alter

Falls mein ein Alter bis heute berechnen will, kann man das mit der gleichen Logik machen. Die DAX-Funktionen sind:

  • now() liefert das heutige Datum inkl. aktueller Uhrzeit
  • today() liefert nur das heutige Datum

Diese Funktionen werden im übrigen beim Process aufgerufen und nicht beim Auswerten in einem Frontend-Programm

Erweiterungen: DateDiff in Sekunden

Ganz analog kann man natürlich vorgehen, um die Differenz in anderen Einheiten auszurechen:

Man multipliziert einfach die DateTime-Spalte mit dem entsprechenden Wert:

  • * 24 für Stunden
  • * 24 * 60 für Minuten
  • * 24 * 60 * 60 für Sekunden

und konvertiert dann die Zahl in ein int.

Allerdings kann so eine Differenz in Monaten bzw. Jahren nicht berechnet werden. Das müsste man zunächst wohl definieren (Was bedeutet 17.1.2015 18:33 – 18.3.2012 17:55 in Monaten?) und dann ggf. über if-Statements selbst berechnen.

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

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″“ $“““

Measure-Meta-Informationen des Cube auslesen: AMO oder ADOMD

Um an die Meta-Informationen des Cubes heranzukommen, gibt es mehrere Zugriffsmöglichkeiten, einmal mit AMO (Analysis Services Management Objects) oder ADOMD (das hauptsächlich für die Ausführung von MDX-Abfragen verwendet wird).

Beigefügt habe ich eine C#-Sollution, die über ADOMD eine CSV-Datei mit allen Measures erzeugt.

Wenn Zeit ist, werde ich diesen Artikel später noch erklären. Vorerst nur soviel:

Der Versuch mit ADOMD auf die Daten zuzugreifen, ist gescheitert, weil dort die berechneten Measures, die ich ebenfalls dokumentieren wollte, nicht einzeln abfragbar sind. Ich habe in einem Blogeintrag von letztem Oktober bereits beschrieben, wie man mit AMO an die MDX-Skripte herankommt. In diesen Skripten gibt es auch CREATE MEMBER-Skripte, die dann durch den Cube in berechnete Measures umgesetzt werden. Wenn man also AMO verwenden will, müsste man diese Skripte parsen (hierzu ein interessanter Blog-Eintrag auf geekswithblogs.net). Wenn man nur an dem Inhalt interessiert ist und die berechneten Measures nicht ändern will, kann man aber darauf getrost verzichten und – wie ich auch in der beigefügten Sollution gemacht habe – stattdessen ADOMD verwenden.

Berechnete Elemente in MDX-Abfragen mit Anwendung in Reporting Services 2005

Lange habe ich gesucht, um ein sinnvolles Beispiel für ein berechnetes Dimensions-Element zu finden (berechnete Measures sind ja ständig zu finden).

Ein Beispiel ist eine Matrix in Reporting Services 2005 (in 2008 ist mit der Tablix ja alles (bzw. vieles) besser).

Ich habe über das Problem bereits zwei Artikel auf sqlservercentral.com veröffentlicht (Reporting Services: Adding extra columns / rows to a matrix und Reporting Services: Read Data from SSAS and SQL Server in One Dataset). Heute möchte ich das Problem auf eine andere Weise lösen.

Nehmen wir an, wir wollen in den Spalten alle Monate sehen und die Summe und einen Planwert und die Differenz zwischen Summe und Planwert. Wenn wir es jetzt schaffen, das ganze in einem MDX-Statement zu laden, so kann auch die Matrix von SQL Server Reporting Services 2005 das anzeigen.

Alle Monate in MDX anzuzeigen, ist einfach. Das Beispiel zeigt je Produkt und Monat den Verkaufswert an (ein konstruiertes, vereinfachtes Beispiel – ich habe sogar auf die Monatsnamen verzichtet und zeige die Monate im Format JJJJMM an):

select non empty [Datum].[Monat].[Monat].members on columns,
non empty [Produkt].[Produkt].[Produkt].members on rows
from [Verkaeufe]
where ([Measures].[Verkaufswert], [Datum].[Jahr].&[2010])

liefert:

Monate mal Produkte

Die Spalte Summe geht auch einfach. Dazu müssen wir den Alle-Member der Attribut-Hierarchie Monat anzeigen:

select non empty {[Datum].[Monat].[Monat].members, [Datum].[Monat].[All]} on columns,
non empty [Produkt].[Produkt].[Produkt].members on rows
from [Verkaeufe]
where ([Measures].[Verkaufswert], [Datum].[Jahr].&[2010])

liefert:

Monate inkl. Alle Mal Produkt

Für den Planwert konstruieren wir ein einfache Element der Attribut-Hierarchie Monat, das konstant 10 ist:

with member [Datum].[Monat].[Plan] as 10
select non empty {[Datum].[Monat].[Monat].members, [Datum].[Monat].[All], [Datum].[Monat].[Plan]} on columns,
non empty [Produkt].[Produkt].[Produkt].members on rows
from [Verkaeufe]
where ([Measures].[Verkaufswert], [Datum].[Jahr].&[2010])

liefert:

Monate inkl. Alle und Plan mal Produkte

(Die unschöne unbekannt-Zeile kann man natürlich leicht loswerden, aber das ist hier nicht unser Thema)

Das war noch eine leichte Übung. Aber in diesen Berechnungen können wir auch rechnen, wie in jedem MDX. Somit können wir einfach die Differenz anzeigen:

with member [Datum].[Monat].[Plan] as 10
member [Datum].[Monat].[Differenz] as [Datum].[Monat].[All] – [Datum].[Monat].[Plan]
select non empty {[Datum].[Monat].[Monat].members, [Datum].[Monat].[All], [Datum].[Monat].[Plan], [Datum].[Monat].[Differenz]} on columns,
non empty [Produkt].[Produkt].[Produkt].members on rows
from [Verkaeufe]
where ([Measures].[Verkaufswert], [Datum].[Jahr].&[2010])

liefert:

Monate inkl. Alle, Plan, Differenz Mal Produkte

Das finde ich ein sehr schönes Beispiel, wie man berechnete Dimensions-Elemente einsetzt.

Um das ganze in Reporting Services zu verwenden, muss man alles natürlich auf die Zeilen bringen und nur das Measure in den Spalten haben – aber das ist trivial.

Außerdem muss man in der Matrix noch die Sortierung lösen, aber über iif-Berechnungen in dem Sortierungsfeld ist das auch leicht zu lösen.

Snowflake-Schema-Dimensionen mit NULL-Values konfigurieren

Eine Möglichkeit, eine Dimension relational abzubilden, ist ein Snowflake-Schema. In diesem Schema werden Hierarchien so abgebildet, dass jede Hierarchiestufe eine eigene Tabelle besitzt, die durch Foreign Keys verbunden sind.

Probleme können auftreten, wenn nicht bei allen Dimensionselementen alle Ebenen vorhanden sind und deshalb manche Elemente in der Datenbank NULL sind. Diese können über das Setzen der Eigenschaft „NULL Processing“ gelöst werden. Fangen wir aber vorne an.

Ein Beispiel für eine Dimension im Snowflake-Schema seien die Hierarchien Produkt > Warengruppe > Strategisches Geschäftsfeld (SGF) und Produkt > Produktionskennzeichen (hier ein Screenshot des Data Source View):

Data Source View Produkt Dimension

Daraus lässt sich einfach eine Dimension bauen:

Dimension Produkt mit Hierarchien
(Zum Vergrößern Bild anklicken)

Die einzelnen Attribute sind so definiert: Die Key-Column ist jeweils die ID-Spalte (also zum Beispiel id aus der Tabelle dim_SGF), die Name-Column jeweils die zugehörige Bezeichnung (z.B. die Tabelle SGFBezeichnung der Tabelle dim_SGF).

In meinem Beispiel kann die Produktionskennzeichen_ID der dim_Produkt NULL sein.

Das hätte jetzt zur Folge, dass – Stand jetzt – die Dimension nicht aufbereitet werden kann:

Fehler beim aufbereiten der Dimension
(Zum Vergrößern Bild anklicken)

Die Fehlermeldung im Detail:

Fehler im OLAP-Speichermodul: Der Attributschlüssel wurde bei der Verarbeitung nicht gefunden: Tabelle: dbo_dim_Produktionskennzeichen, Spalte: id, Wert: 0. Das Attribut ist ‚Produktionskennzeichen‘. Fehler im OLAP-Speichermodul: Fehler beim Verarbeiten des Produkt-Attributs der Produkt-Dimension aus der SimpleCube-Datenbank.

Offensichtlich hat der SSAS den NULL-Wert als 0 interpretiert, was er natürlich nicht finden kann.

Die Lösung ist:

  • Definieren des Unknown-Members der Dimension als sichtbar mit der Bezeichnung „unbekannt“ (oder wie auch immer)
  • Definieren der Nullprocessing-Eigenschaft der Key Column von Produktionskennzeichen auf „ConvertToUnknown“:
    Null Processing bei der Key Coulmn eines Attributs

Dann funktioniert die Aufbereitung und die Dimensionen sehen in meinem Beispiel so aus (die Zahlen sind irgendwelche Artikelnummern 🙂 ):

Hierarchie SGF:
Hierarchie SGF

Hierarchie Produktionskennzeichen:
Hierarchie Produktionskennzeichen