In einem aktuellen Projekt hatten wir die Aufgabe, in mehreren Cubes insgesamt ca. 250 Rollen anzulegen bzw. zu aktualisieren. Ausgangspunkt war eine interne Umstrukturierung der Vertriebsstruktur, was dann in Rollen abgebildet werden musste.
[Nebenbemerkung: Natürlich gibt es andere Optionen wie z.B. dynamische Rechtevergabe mittels USERNAME() oder CUSTOMDATA(). In diesem Projekt war aber eine hart kodierte Lösung sinnvoll, da die Rechtevergabe nicht dynamisch erfolgen soll und die Benutzer eh zu AD-Gruppen zugeordnet sind.]
Somit war die Anforderung, ca. 250 Sätze mit folgenden Informationen automatisch zu Rollen umzusetzen:
Name des Cubes
Name der Rolle
Beschreibung
Filtereinstellungen für bestimmte Kunden-Attribute (Hub, Market, Sales Country) und ggf. Element-Attribute
Zuordnung einer oder mehrerer AD-Gruppen zu dieser Rolle
Deswegen haben wir zunächst eine Tabelle angelegt, in der wir diese Metadaten speichern konnten:
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTERTABLE [Config].[CubeRollen] ADDDEFAULT ((0)) FOR [AdminRights]
GO
ALTERTABLE [Config].[CubeRollen] ADDDEFAULT ((0)) FOR [ProcessPermission]
GO
ALTERTABLE [Config].[CubeRollen] ADDDEFAULT ((0)) FOR [ProcessAndReadPermission]
GO
CREATE TABLE [Config].[CubeRollen](
[Cube] [nvarchar](100) NOT NULL,
[CubeRolle] [nvarchar](50) NOT NULL,
[Description] [nvarchar](1000) NULL,
[Filter_SalesCountry] [nvarchar](3) NULL,
[Filter_HubId] [int] NULL,
[Filter_RegionId] [int] NULL,
[Filter_ElementNo] [int] NULL,
[Bemerkung] [nvarchar](1000) NULL,
[ADGruppe] [nvarchar](100) NULL,
[AdminRights] [tinyint] NOT NULL,
[ProcessPermission] [tinyint] NOT NULL,
[ProcessAndReadPermission] [tinyint] NOT NULL,
CONSTRAINT [PK_Config_CubeRollen] PRIMARY KEY CLUSTERED
(
[Cube] ASC,
[CubeRolle] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [Config].[CubeRollen] ADD DEFAULT ((0)) FOR [AdminRights]
GO
ALTER TABLE [Config].[CubeRollen] ADD DEFAULT ((0)) FOR [ProcessPermission]
GO
ALTER TABLE [Config].[CubeRollen] ADD DEFAULT ((0)) FOR [ProcessAndReadPermission]
GO
CREATE TABLE [Config].[CubeRollen](
[Cube] [nvarchar](100) NOT NULL,
[CubeRolle] [nvarchar](50) NOT NULL,
[Description] [nvarchar](1000) NULL,
[Filter_SalesCountry] [nvarchar](3) NULL,
[Filter_HubId] [int] NULL,
[Filter_RegionId] [int] NULL,
[Filter_ElementNo] [int] NULL,
[Bemerkung] [nvarchar](1000) NULL,
[ADGruppe] [nvarchar](100) NULL,
[AdminRights] [tinyint] NOT NULL,
[ProcessPermission] [tinyint] NOT NULL,
[ProcessAndReadPermission] [tinyint] NOT NULL,
CONSTRAINT [PK_Config_CubeRollen] PRIMARY KEY CLUSTERED
(
[Cube] ASC,
[CubeRolle] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [Config].[CubeRollen] ADD DEFAULT ((0)) FOR [AdminRights]
GO
ALTER TABLE [Config].[CubeRollen] ADD DEFAULT ((0)) FOR [ProcessPermission]
GO
ALTER TABLE [Config].[CubeRollen] ADD DEFAULT ((0)) FOR [ProcessAndReadPermission]
GO
Jetzt betrachten wir mal, wie wir die Rollen automatisch anlegen wollen. Dazu gibt es JSON-Befehler (früher XMLA), die der Analysis Services (ab SQL 2016) ausführt. Für die 4.Zeile des obigen Screen Shots sieht der Befehl so aus: (wenn man die Syntax nicht kennt, kann man sich so einen Befehl über die Skript-Funktionalität im SQL Server Management Studio erstellen lassen)
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"createOrReplace":{
"object":{
"database":"AFD Order Entry Reporting",
"role":"Andorra"
},
"role":{
"name":"Andorra",
"description":"Role for Andorra",
"modelPermission":"read",
"members":[{"memberName":"DOMÄNE\\BI_Andorra"}],
"tablePermissions":[
{
"name":"Customer",
"filterExpression":"Customer[Hub ID]=\"20\" && Customer[Sales country Id]=\"AD\""
Damit wir diese JSONs erstellen können, brauchen wir noch eine Konfigurationstabelle, in der wir definieren, wie sich die Einträge in den einzelnen [Filter_…]-Spalten in diese Bedingungen übersetzen. Diese Übersetzung kann für jeden Cube unterschiedlich sein. Dazu haben wir folgende Tabelle angelegt:
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERTINTO [Config].[CubeFilterDefinition]
SELECT'AFD Order Entry Reporting',
'Customer[Sales country Id]=\"##Wert##\"',
'Customer[Hub ID]=\"##Wert##\"',
'Customer[Region ID]=##Wert##',
NULL
INSERTINTO [Config].[CubeFilterDefinition]
SELECT'DCH Sales Margin Analysis',
'Customer[Sales country Id]=\"##Wert##\"',
'Customer[Hub ID]=\"##Wert##\"',
'Customer[RegionID]=##Wert##',
'Element[Element No]=\"##Wert##\"'
CREATE TABLE [Config].[CubeFilterDefinition](
[Cube] [nvarchar](100) NOT NULL,
[Filter_SalesCountry] [nvarchar](100) NULL,
[Filter_HubId] [nvarchar](100) NULL,
[Filter_RegionId] [nvarchar](100) NULL,
[Filter_ElementNo] [nvarchar](100) NULL,
CONSTRAINT [PK_Config_CubeFilterDefinition] PRIMARY KEY CLUSTERED
(
[Cube] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERT INTO [Config].[CubeFilterDefinition]
SELECT 'AFD Order Entry Reporting',
'Customer[Sales country Id]=\"##Wert##\"',
'Customer[Hub ID]=\"##Wert##\"',
'Customer[Region ID]=##Wert##',
NULL
INSERT INTO [Config].[CubeFilterDefinition]
SELECT 'DCH Sales Margin Analysis',
'Customer[Sales country Id]=\"##Wert##\"',
'Customer[Hub ID]=\"##Wert##\"',
'Customer[RegionID]=##Wert##',
'Element[Element No]=\"##Wert##\"'
CREATE TABLE [Config].[CubeFilterDefinition](
[Cube] [nvarchar](100) NOT NULL,
[Filter_SalesCountry] [nvarchar](100) NULL,
[Filter_HubId] [nvarchar](100) NULL,
[Filter_RegionId] [nvarchar](100) NULL,
[Filter_ElementNo] [nvarchar](100) NULL,
CONSTRAINT [PK_Config_CubeFilterDefinition] PRIMARY KEY CLUSTERED
(
[Cube] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
INSERT INTO [Config].[CubeFilterDefinition]
SELECT 'AFD Order Entry Reporting',
'Customer[Sales country Id]=\"##Wert##\"',
'Customer[Hub ID]=\"##Wert##\"',
'Customer[Region ID]=##Wert##',
NULL
INSERT INTO [Config].[CubeFilterDefinition]
SELECT 'DCH Sales Margin Analysis',
'Customer[Sales country Id]=\"##Wert##\"',
'Customer[Hub ID]=\"##Wert##\"',
'Customer[RegionID]=##Wert##',
'Element[Element No]=\"##Wert##\"'
Man sieht, dass die Einschränkung aufs Element-Attribut nur beim 2. Cube möglich ist (weil der erste Cube gar nicht diese Dimension hat).
Für eine Verallgemeinerung muss man natürlich die Filter_…-Spalten an die jeweilige Situation anpassen.
Nun bauen wir das SQL-Statement für die Erstellung des JSON schrittweise zusammen. Für jeden Schritt verwende ich eine eigene CommonTableExpression, damit man die Schritte einfach einzeln bauen kann.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
with piv as
(
select r.[Cube], r.CubeRolle,
r.Filter_SalesCountry as FilterWert, f.Filter_SalesCountry as FilterCondition from config.CubeRollen r
innerjoin config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_HubId) as FilterWert, f.Filter_HubID as FilterCondition from config.CubeRollen r
innerjoin config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_RegionId) as FilterWert, f.Filter_RegionID as FilterCondition from config.CubeRollen r
innerjoin config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_ElementNo) as FilterWert, f.Filter_ElementNo as FilterCondition from config.CubeRollen r
innerjoin config.CubeFilterDefinition f
on r.Cube=f.Cube
)
select * from piv
with piv as
(
select r.[Cube], r.CubeRolle,
r.Filter_SalesCountry as FilterWert, f.Filter_SalesCountry as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_HubId) as FilterWert, f.Filter_HubID as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_RegionId) as FilterWert, f.Filter_RegionID as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_ElementNo) as FilterWert, f.Filter_ElementNo as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
)
select * from piv
with piv as
(
select r.[Cube], r.CubeRolle,
r.Filter_SalesCountry as FilterWert, f.Filter_SalesCountry as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_HubId) as FilterWert, f.Filter_HubID as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_RegionId) as FilterWert, f.Filter_RegionID as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_ElementNo) as FilterWert, f.Filter_ElementNo as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
)
select * from piv
Dieses SQL liefert die Spalten:
Cube
CubeRolle
FilterWert: der jeweilige Wert, auf den gefiltert werden soll (also in unserem Andorra-Beispiel von oben: 20 bzw. AD
FilterCondition: die jeweilige Definition aus der Meta-Tabelle, wie dieser Filterwert anzuwenden ist (z.B. Customer[Hub ID]=\“##Wert##\“ bzw. Customer[Sales country Id]=\“##Wert##\“)
Im nächsten Schritt ermitteln wir aus der FilterCondition die Tabelle (im Beispiel Customer). Das geht mit der SQL-Funktion charindex zum Suchen des Texts „[„:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
,
pivMitFilteredTable as (
select *, left(FilterCondition, charindex('[', FilterCondition, 1)-1) as FilteredTable from piv
)
select * from pivMitFilteredTable
,
pivMitFilteredTable as (
select *, left(FilterCondition, charindex('[', FilterCondition, 1)-1) as FilteredTable from piv
)
select * from pivMitFilteredTable
,
pivMitFilteredTable as (
select *, left(FilterCondition, charindex('[', FilterCondition, 1)-1) as FilteredTable from piv
)
select * from pivMitFilteredTable
Im nächsten Schritt gruppieren wir nach FilteredTable und bauen die Bedingung pro Tabelle zusammen (in unserem Beispiel Customer[Hub ID]=\“20\“ && Customer[Sales country Id]=\“AD\“):
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
,
RollenFilter as (
select [Cube], CubeRolle, FilteredTable , string_agg(convert(nvarchar(max), replace(FilterCondition, '##Wert##', FilterWert)), ' && ') as Filter from pivMitFilteredTable
group by [Cube], CubeRolle, FilteredTable
)
select * from RollenFilter
,
RollenFilter as (
select [Cube], CubeRolle, FilteredTable , string_agg(convert(nvarchar(max), replace(FilterCondition, '##Wert##', FilterWert)), ' && ') as Filter from pivMitFilteredTable
group by [Cube], CubeRolle, FilteredTable
)
select * from RollenFilter
,
RollenFilter as (
select [Cube], CubeRolle, FilteredTable , string_agg(convert(nvarchar(max), replace(FilterCondition, '##Wert##', FilterWert)), ' && ') as Filter from pivMitFilteredTable
group by [Cube], CubeRolle, FilteredTable
)
select * from RollenFilter
Nun ergänzen wir diese Tabelle um die Description, die ADGruppe und die modelPermission (hier werden Admin- und processor-Rechte separat vergeben):
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
,
FDef as (
select rf.*, r.Description, ADGruppe,
case when AdminRights=1 then N'administrator'
when ProcessPermission=1 then N'refresh'
when ProcessAndReadPermission=1 then N'readRefresh'
else N'read' end as modelPermission from RollenFilter rf
leftjoin Config.CubeRollen r
on rf.[cube] = r.[Cube] and rf.CubeRolle = r.CubeRolle
)
select * from FDef
,
FDef as (
select rf.*, r.Description, ADGruppe,
case when AdminRights=1 then N'administrator'
when ProcessPermission=1 then N'refresh'
when ProcessAndReadPermission=1 then N'readRefresh'
else N'read' end as modelPermission from RollenFilter rf
left join Config.CubeRollen r
on rf.[cube] = r.[Cube] and rf.CubeRolle = r.CubeRolle
)
select * from FDef
,
FDef as (
select rf.*, r.Description, ADGruppe,
case when AdminRights=1 then N'administrator'
when ProcessPermission=1 then N'refresh'
when ProcessAndReadPermission=1 then N'readRefresh'
else N'read' end as modelPermission from RollenFilter rf
left join Config.CubeRollen r
on rf.[cube] = r.[Cube] and rf.CubeRolle = r.CubeRolle
)
select * from FDef
Jetzt haben wir alles, um die ersten JSON-Fragmente zusammenzubauen.
Außerdem ist es erlaubt, in der Definition in dem Feld ADGruppe mehrere AD-Gruppen zu spezifizieren – indem sie durch , getrennt sind. Zusätzlich wird die Domäne vor die AD-Gruppe geschrieben. Somit entsteht aus
group by [cube], CubeRolle, Rumpf_Anfang , Rumpf_ende
)
select'{
"sequence":
{
"operations": [
' + string_agg(xmla, ',')
+ ']}}'
from erg
with piv as
(
select r.[Cube], r.CubeRolle,
r.Filter_SalesCountry as FilterWert, f.Filter_SalesCountry as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_HubId) as FilterWert, f.Filter_HubID as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_RegionId) as FilterWert, f.Filter_RegionID as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_ElementNo) as FilterWert, f.Filter_ElementNo as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
),
pivMitFilteredTable as (
select *, left(FilterCondition, charindex('[', FilterCondition, 1)-1) as FilteredTable from piv
),
RollenFilter as (
select [Cube], CubeRolle, FilteredTable , string_agg(convert(nvarchar(max), replace(FilterCondition, '##Wert##', FilterWert)), ' && ') as Filter from pivMitFilteredTable
group by [Cube], CubeRolle, FilteredTable
)
,
FDef as (
select rf.*, r.Description, ADGruppe,
case when AdminRights=1 then N'administrator'
when ProcessPermission=1 then N'refresh'
when ProcessAndReadPermission=1 then N'readRefresh'
else N'read' end as modelPermission from RollenFilter rf
left join Config.CubeRollen r
on rf.[cube] = r.[Cube] and rf.CubeRolle = r.CubeRolle
)
, ff as (
select [cube], CubeRolle,
'{
"createOrReplace": {
"object": {
"database": "' + [Cube] + '",
"role": "' + CubeRolle + '"
},
"role": {
"name": "' + CubeRolle + '",
"description": "' + isnull(Description, '') + '",
"modelPermission": "' + modelPermission + '", ' + isnull('
"members": ['
+ (select string_agg('{
"memberName": "' + replace(case when value like '%\%' then value else 'DOMÄNE\'+ value end, '\', '\\') + '"
}', ',') from ( Select value from string_split(ADGruppe, ',')) as x) + '
]', '') as Rumpf_Anfang, '
}
}
}' as Rumpf_ende, FilteredTable, Filter, description, modelPermission, ADGruppe
from FDef
)
,f2 as (
select [cube], CubeRolle, Rumpf_Anfang, Rumpf_ende, '{
"name": "' + FilteredTable + '",
"filterExpression": "' + Filter + '"
}' as FilterZeile, FilteredTable, Filter from ff
)
,
erg as (
select [cube], CubeRolle, Rumpf_Anfang +
isnull(
', "tablePermissions": [' +
string_agg(convert(nvarchar(max), FilterZeile), ', ')
+ ']', '')
+ Rumpf_ende xmla from f2
group by [cube], CubeRolle, Rumpf_Anfang , Rumpf_ende
)
select '{
"sequence":
{
"operations": [
' + string_agg(xmla, ',')
+ ']}}'
from erg
with piv as
(
select r.[Cube], r.CubeRolle,
r.Filter_SalesCountry as FilterWert, f.Filter_SalesCountry as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_HubId) as FilterWert, f.Filter_HubID as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_RegionId) as FilterWert, f.Filter_RegionID as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
UNION
select r.[Cube], r.CubeRolle,
convert(nvarchar(10), r.Filter_ElementNo) as FilterWert, f.Filter_ElementNo as FilterCondition from config.CubeRollen r
inner join config.CubeFilterDefinition f
on r.Cube=f.Cube
),
pivMitFilteredTable as (
select *, left(FilterCondition, charindex('[', FilterCondition, 1)-1) as FilteredTable from piv
),
RollenFilter as (
select [Cube], CubeRolle, FilteredTable , string_agg(convert(nvarchar(max), replace(FilterCondition, '##Wert##', FilterWert)), ' && ') as Filter from pivMitFilteredTable
group by [Cube], CubeRolle, FilteredTable
)
,
FDef as (
select rf.*, r.Description, ADGruppe,
case when AdminRights=1 then N'administrator'
when ProcessPermission=1 then N'refresh'
when ProcessAndReadPermission=1 then N'readRefresh'
else N'read' end as modelPermission from RollenFilter rf
left join Config.CubeRollen r
on rf.[cube] = r.[Cube] and rf.CubeRolle = r.CubeRolle
)
, ff as (
select [cube], CubeRolle,
'{
"createOrReplace": {
"object": {
"database": "' + [Cube] + '",
"role": "' + CubeRolle + '"
},
"role": {
"name": "' + CubeRolle + '",
"description": "' + isnull(Description, '') + '",
"modelPermission": "' + modelPermission + '", ' + isnull('
"members": ['
+ (select string_agg('{
"memberName": "' + replace(case when value like '%\%' then value else 'DOMÄNE\'+ value end, '\', '\\') + '"
}', ',') from ( Select value from string_split(ADGruppe, ',')) as x) + '
]', '') as Rumpf_Anfang, '
}
}
}' as Rumpf_ende, FilteredTable, Filter, description, modelPermission, ADGruppe
from FDef
)
,f2 as (
select [cube], CubeRolle, Rumpf_Anfang, Rumpf_ende, '{
"name": "' + FilteredTable + '",
"filterExpression": "' + Filter + '"
}' as FilterZeile, FilteredTable, Filter from ff
)
,
erg as (
select [cube], CubeRolle, Rumpf_Anfang +
isnull(
', "tablePermissions": [' +
string_agg(convert(nvarchar(max), FilterZeile), ', ')
+ ']', '')
+ Rumpf_ende xmla from f2
group by [cube], CubeRolle, Rumpf_Anfang , Rumpf_ende
)
select '{
"sequence":
{
"operations": [
' + string_agg(xmla, ',')
+ ']}}'
from erg
Anpassungen
Theoretisch könnte man dieses JSON auch automatisch an den Analysis Services schicken (z.B. via ETL). In unserem Projekt haben wir es einfach mit Copy&Paste in das SQL Server Management Studio kopiert und dort ausgeführt.
Für jeden Anwendungsfall muss man natürlich die unterschiedlichen gefilterten Felder spezifizieren. Das bedeutet – wie oben angedeutet – die Anpassung der Datenbank-Struktur. Natürlich könnte man das Datenmodell auch so wählen, dass für unterschiedliche Filter keine Strukturänderung erforderlich wäre. Wir haben uns aber bewusst für die gezeigte Lösung entschieden, weil dadurch die Tabelle auch für Nicht-IT-ler verständlich bleibt und so die Meta-Daten von den Fachbereichen gepflegt werden können.
Meine Erfahrungen in der Business Intelligence Welt