CiAgICA8IS0tIExpbmtlZEluIC0tPgogICAgPHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPgogICAgICAgIF9saW5rZWRpbl9wYXJ0bmVyX2lkID0gIjEyMzUwNzMiOwogICAgICAgIHdpbmRvdy5fbGlua2VkaW5fZGF0YV9wYXJ0bmVyX2lkcyA9IHdpbmRvdy5fbGlua2VkaW5fZGF0YV9wYXJ0bmVyX2lkcyB8fCBbXTsKICAgICAgICB3aW5kb3cuX2xpbmtlZGluX2RhdGFfcGFydG5lcl9pZHMucHVzaChfbGlua2VkaW5fcGFydG5lcl9pZCk7CiAgICA8L3NjcmlwdD48c2NyaXB0IHR5cGU9InRleHQvamF2YXNjcmlwdCI+CiAgICAgICAgKGZ1bmN0aW9uKCl7dmFyIHMgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgic2NyaXB0IilbMF07CiAgICAgICAgICAgIHZhciBiID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7CiAgICAgICAgICAgIGIudHlwZSA9ICJ0ZXh0L2phdmFzY3JpcHQiO2IuYXN5bmMgPSB0cnVlOwogICAgICAgICAgICBiLnNyYyA9ICJodHRwczovL3NuYXAubGljZG4uY29tL2xpLmxtcy1hbmFseXRpY3MvaW5zaWdodC5taW4uanMiOwogICAgICAgICAgICBzLnBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGIsIHMpO30pKCk7CiAgICA8L3NjcmlwdD4KICAgIDxub3NjcmlwdD4KICAgICAgICA8aW1nIGhlaWdodD0iMSIgd2lkdGg9IjEiIHN0eWxlPSJkaXNwbGF5Om5vbmU7IiBhbHQ9IiIgc3JjPSJodHRwczovL3B4LmFkcy5saW5rZWRpbi5jb20vY29sbGVjdC8/cGlkPTEyMzUwNzMmZm10PWdpZiIgLz4KICAgIDwvbm9zY3JpcHQ+CiAgICA8IS0tIEVuZCBMaW5rZWRJbiAtLT4KICAgIA==
Generic filters
Exact matches only
Search in title
Search in excerpt
Search in content

Relationale Pivoteingabe – Das Splashing-Upgrade

DeltaMaster bietet mit seinen Planungsfunktionen umfangreiche Möglichkeiten zur Dateneingabe in multidimensionalen Modellen. Nicht jeder Anwendungsfall rechtfertigt allerdings die Entwicklung eines solchen Modells. Mit Hilfe von relationalen DeltaMaster-Anwendungen auf SQL Server und der damit verfügbaren Pivoteingabe lassen sich einfache Anwendungsfälle auch ganz ohne Würfel realisieren. Auch wenn die Eingabe standardmäßig  kein Splashing zulässt, lässt sich diese Funktion mit einigen Anpassungen ergänzen.

Überblick

DeltaMaster und DeltaMaster ETL bieten umfangreiche Funktionen, um Plananwendungen für nahezu jeden Anwendungsfall zu realisieren. Diesen Anwendungen liegt meist ein multidimensionales Datenmodell zu Grunde, welches durch definierte Strukturen und schnelle Aggregationen prädestiniert dafür ist. Bei der Entscheidung für diese Lösung muss jedoch bedacht werden, dass der zugrundeliegende Cube nach jeder Änderung an Dimensionsdaten aktualisiert werden muss. Wird diese Aktualisierung im Planungsprozess zum Regelschritt, da neben Kennzahlen auch Strukturen, z.B. durch SQL-Durchgriffsberichte, erzeugt werden, kann dies die Bedienung der Anwendung unkomfortabel machen. Außerdem muss für die Bedienung des SQL-Durchgriffs in eine andere Anwendung gewechselt werden. DeltaMaster lässt uns mit diesem Problem jedoch nicht allein und ermöglicht auch in rein relationalen Anwendungen auf SQL Server die Eingabe in Pivottabellen.

Mittels einer unscheinbaren Einstellung in DeltaMaster ETL im Bericht „Measure Groups“ lassen sich alle dafür benötigten Objekte für Faktentabellen generieren:

Anschließend werden die folgenden Objekte erzeugt:

  • P_UPDATE_PIV_* – Prozedur für die Durchführung der Eingabe
  • T_D_* – Tabelle, in welcher die eingegebenen Daten abgespeichert werden
  • V_D_* – View auf der T_D_*-Tabelle

Rein relationalen Anwendungen liegt jedoch häufig kein DeltaMaster-ETL-Modell zugrunde. In diesen Fällen kann die notwendige P_UPDATE_PIV_* – Prozedur für eine Basistabelle, in welche Daten geschrieben werden sollen, mit Hilfe der Prozedur P_BC_Generate_DeltaMasterTableProc_PIV generiert werden. Diese wird standardmäßig mit dem DeltaMaster-ETL-Metamodell ausgeliefert. Eine Erläuterung der einzelnen Parameter kann der DeltaMaster ETL-Administratorenreferenz entnommen werden.

Einen Nachteil hat die Sache jedoch: Im Standard können relationale Eingaben in Pivottabellen nur auf der untersten Ebene vorgenommen werden. Ein Splashing, wie man es aus DeltaMaster-Plananwendungen kennt, ist nicht möglich. Das folgende Beispiel soll zeigen, wie die generierten Prozeduren um diese Funktionalität erweitert werden können.

Das Beispiel

Für diesen Blogbeitrag wird ein einfaches Datenmodell herangezogen, welches auf Anfrage heruntergeladen werden kann. In diesem sollen Geldbeträge für Kostenstellen, welche einzelnen Organisationseinheiten zugeordnet sind, pro Sachkonto und pro Jahr erfasst werden. Die Eingabe soll sowohl auf der Ebene „Kostenstelle“, als auch auf der übergeordneten Ebene „Organisationseinheit“ möglich sein. Auf den Dimensionen Sachkonto und Jahr soll nur auf der untersten Ebene eingegeben werden.

Implementierung der Eingabe

Um die notwendigen Objekte zur Transaktionssteuerung zu erzeugen, wird zunächst der folgende Befehl ausgeführt:


EXEC dbo.P_BC_Generate_DeltaMasterTableProc_PIV  
@TabName = 'V_D_Werte',                        
@BaseTabName = 'T_D_Werte',                    
@DimColList = 'SachkontoID, KostenstelleID, JahrID',                         
@MeasColList = 'Betrag',           
@ColSeparator = ',',                
@IncludeTransactions = 2, 
@AutoInsertUser = 1,               
@AddColsToBasetab = 1

Zu beachten ist, dass die Dimensionen und Measures analog zu den Spaltennamen in der zugrundeliegenden Tabelle angegeben werden müssen.

Anschließend muss nur noch die Planungsfunktion in der relationalen DelteMaster-Anwendung aktiviert und als Transaktionssteuerung „Modellspezifische Transaktionen“ unter Optionen = > Dateneingabe ausgewählt werden:

Die Euphorie schwindet jedoch, sobald eine Eingabe auf der Organisationseinheit vorgenommen wird:

Was ist passiert? Um das zu verstehen ist der sql.log aus der DeltaMaster-Diagnose aufschlussreich:

HTML2


EXECUTE dbo.P_Update_PIV_V_D_Werte 
@JahrID = 2020, 
@Organisationseinheit = N'OE1', 
@SachkontoID = 10000000, 

@TransactionID = 'ed7ac2cb-d22e-4c1f-80e7-43c57447f8d9', 
@StepNumber = 0, 
@MeasureName = N'Betrag', 
@NewValue = 40, 
@ErrorDesc = NULL, 

@ReturnValue = NULL 
 -> 
Procedure or function 'P_Update_PIV_V_D_Werte' expects parameter '@KostenstelleID', which was not supplied

Es ist leicht zu erkennen, wie die Aufrufe in der Anwendung generiert werden. DeltaMaster übergibt als Parameter die der aktuellen Zelle zugrundeliegenden Spalte. Den Parameter „Organisationseinheit“ kennt unsere Prozedur nicht und weiß auch nicht, ob und wie sie den Wert verteilen soll. Ergänzen wir also zunächst den Parameter und erlauben gleichzeitig, dass der Parameter „KostenstelleID“ nicht übergeben werden muss:

HTML3

ALTER PROCEDURE [dbo].[P_Update_PIV_V_D_Werte] 

( 
@SachkontoID varchar(255),  
@Organisationseinheit VARCHAR(255) = NULL, 
@KostenstelleID varchar(255) = NULL,  
@JahrID varchar(255), 
@TransactionID varchar(255), 
@StepNumber varchar(255) = NULL, 
@MeasureName varchar(255), 
@NewValue varchar(255), 
@ErrorDesc varchar(255) = NULL OUTPUT
)

Mit Hilfe dieser Änderung wird nun kein Fehler mehr beim Aufruf erzeugt. Eine sinnvolle Eingabe findet jedoch noch nicht statt, schließlich müssen wir der Prozedur noch die Verteilung auf darunter liegende Kostenstellen beibringen. Für die hier beschriebene Lösung wird die Verteilung analog zum DeltaMaster-Planungsstandard implementiert: Ist die Zelle leer findet eine Gleichverteilung auf darunterliegende Zellen statt. Ist die Zelle bereits gefüllt, findet die Verteilung proportional zur bestehenden Verteilung auf die darunterliegenden Zellen statt.

Die Prozedur muss dazu an den Stellen, an denen ein Join über KostenstelleID erfolgt um eine alternative Überprüfung der Organisationseinheit ergänzt werden.

HTML4

[…] 
FROM 
dbo.T_D_Werte w 
LEFT JOIN 
dbo.T_DIM_Kostenstelle k 
ON w.KostenstelleID = k.KostenstelleID 
WHERE 
SachkontoID = convert(int, @SachkontoID)   
AND  
( 
w.KostenstelleID = convert(int, @KostenstelleID) 
OR	 
k.Organisationseinheit = @Organisationseinheit 
) 
AND 
JahrID = convert(int, @JahrID)

Somit wird bei Eingabe auf eine Organisationseinheit überprüft, ob schon Einträge für die darunter liegenden Kostenstellen existieren. Bei Eingabe auf Kostenstellen bleibt die bestehende Funktionalität erhalten. In diesem Blogbeitrag werden nur einzelne Codestellen beschrieben, die komplette Prozedur befindet sich auf dem Laufwerk bzw. ist gegen Anfrage erhältlich.

Analog werden auch die Update-/Delete-/ und Insert-Statements in der Prozedur angepasst. Für das proportionale Splashing muss zusätzlich noch eine Ermittlung des Faktors, der den abzuziehenden Anteil angibt, ergänzt werden:

HTML5

SELECT @OldValue = SUM(w.Betrag) 
--select * 
FROM 
dbo.T_D_Werte w 
LEFT JOIN dbo.T_DIM_Kostenstelle k 
ON w.KostenstelleID = k.KostenstelleID 
 
WHERE 
w.SachkontoID = convert(int, @SachkontoID) 
AND  
( 
w.KostenstelleID = convert(int, @KostenstelleID) 
OR	 
k.Organisationseinheit = @Organisationseinheit 
) 
AND w.JahrID = convert(int, @JahrID)

Dieser Faktor wird dann in der Update-Prozedur verwendet:

HTML6a
HTML6b

SET @SQL = 
'	UPDATE w 
SET 
' + @MeasureName + ' = CONVERT(FLOAT, '+@MeasureName +') * CONVERT(FLOAT,' + 
CONVERT(VARCHAR,@Faktor) + '), 
TransactionState = CASE WHEN TransactionState IS NULL THEN 2 ELSE TransactionState END,  
TransactionID = ''' + @TransactionID + ''', 
InputTypeID = 0, 
ChangeDate = getdate(), 
ChangeUserID = ' + @ChangeUserID + ', 
ChangeHost = host_name() 
FROM  
T_D_Werte w 
LEFT JOIN dbo.T_DIM_Kostenstelle k 
ON w.KostenstelleID = k.KostenstelleID	 



WHERE 
SachkontoID = convert(int, ' + @SachkontoID + ')  
AND 
(k.KostenstelleID = ''' +  ISNULL(@KostenstelleID,'') + ''') 
OR 
(k.Organisationseinheit = ''' +  ISNULL(@Organisationseinheit,'') + ''')  
AND 
JahrID = convert(int, ' + @JahrID + ')' 
EXEC(@SQL)

Beim generierten Insert-Statement, also der Eingabe in eine leere Zelle, muss statt eines Faktors die Anzahl an darunterliegenden Elementen ermittelt werden:

HTML7

 
SELECT @Splashcount = COUNT(DISTINCT k.KostenstelleID) 
FROM 
 dbo.T_DIM_Kostenstelle k 

WHERE 
( 
(@KostenstelleID IS NOT NULL AND k.KostenstelleID = @KostenstelleID) 
OR 
(@Organisationseinheit IS NOT NULL  
AND k.Organisationseinheit = @Organisationseinheit) 
) 

--Neuer Wert ist der eingegebene Wert / Anzahl an Datensätzen 
SET @locNewValue = CONVERT(FLOAT,@NewValue)/CONVERT(FLOAT,@Splashcount);

Damit bei der Eingabe auf übergeordnete Elemente mehrere Datensätze geschrieben werden, wird das Insert-Statement erweitert:

HTML8
HTML8b

SET @SQL = 
'		INSERT INTO dbo.T_D_Werte 
( 
SachkontoID,  
KostenstelleID,  
JahrID, 
' + @MeasureName + ', 
TransactionState, 
TransactionID, 
InputTypeID, 
ChangeDate, 
ChangeUserID, 
ChangeHost 
) 
SELECT 

convert(int, ' + @SachkontoID + '),  
convert(int, k.KostenstelleID ),'+  
'convert(int, ' + @JahrID + '), 
' + CONVERT(VARCHAR,@locNewValue) + ', 
1, 
''' + @TransactionID + ''', 
0, 
getdate(), 
' + @ChangeUserID + ', 
host_name() 

FROM 
T_DIM_Kostenstelle k 


WHERE 
(k.KostenstelleID = ''' +  ISNULL(@KostenstelleID,'') + ''') 
OR 
(k.Organisationseinheit = ''' +  ISNULL(@Organisationseinheit,'') + ''')'  
EXEC(@SQL)

Eine Eingabe auf der Ebene Organisationseinheit zeigt nun das gewünschte Ergebnis:

Ebene Organisationseinheit

Rollback

Die nun gewonnene gute Stimmung wird schnell getrübt, sollte ein Anwender innerhalb einer Eingabetransaktion auf die Idee kommen, den Knopf  Knopf zu benutzen. Das Ergebnis lässt sich einfach zusammenfassen: Bei einer Eingabe auf leere Zellen funktioniert alles wie erwartet. Bei der Eingabe auf bereits gefüllte Zellen geschieht bei Abbruch nichts. Grund dafür ist, dass die P_Update_PIV_*-Prozeduren über eine einfache Rollback-Funktionalität verfügen. Diese muss aber nach den Anpassungen für die Splashing-Logiken ebenfalls modifiziert werden, um alle geänderten Datensätze zu sichern:

HTML9

 
INSERT INTO dbo.T_D_Werte_Rollback 
(  
SachkontoID,  
KostenstelleID,  
JahrID,  
Betrag,  
TransactionID,  
TransactionState,  
InputTypeID,  
ChangeDate,  
ChangeUserID,  
ChangeHost 
) 
SELECT  
SachkontoID,  
k.KostenstelleID,  
JahrID,  
Betrag,  
@TransactionID,  
TransactionState,  
InputTypeID,  
ChangeDate,  
ChangeUserID,  
ChangeHost 
FROM 
dbo.T_D_Werte w 
LEFT JOIN 
dbo.T_DIM_Kostenstelle k 
ON w.KostenstelleID = k.KostenstelleID 
WHERE 
SachkontoID = convert(int, @SachkontoID)   
AND  
( 
w.KostenstelleID = convert(int, @KostenstelleID) 
OR	 
k.Organisationseinheit = @Organisationseinheit 
) 
AND 
JahrID = convert(int, @JahrID)  
AND 
TransactionState IS NULL

Anschließend funktioniert auch das Rollback problemfrei.

Sperren von Datensätzen

Auch das Sperren von Datensätzen muss in mittlerweile gewohnter Manier angepasst werden:
HTML10

SELECT  
@TransactionID_InUse  = TransactionID  
FROM 
dbo.T_D_Werte w 
LEFT JOIN dbo.T_DIM_Kostenstelle k 
ON w.KostenstelleID = k.KostenstelleID 
WHERE 
SachkontoID = convert(int, @SachkontoID)  
AND  
( 
w.KostenstelleID = convert(int, @KostenstelleID) 
OR	 
k.Organisationseinheit = @Organisationseinheit 
) 
AND 
JahrID = convert(int, @JahrID)

Fazit

Die hier beschriebenen Anpassungen lassen sich problemlos auf unterschiedliche Anwendungsfälle übertragen. Zu beachten ist jedoch, dass die Logik für jede Dimension, auf der eine aggregierte Eingabe möglich sein soll, einzeln implementiert werden muss. Ansonsten bietet die modifizierte, relationale Pivoteingabe alle gewohnten Funktionen in guter Laufzeit. Besonders charmant wird die hier beschriebene Lösung, wenn sich die zugrundeliegenden Stammdaten zur Laufzeit erweitern, z.B. durch eine neue Kostenstelle. Diese ist dann in der Anwendung ohne Aufbereitungsschritte sofort beplanbar.