{"id":525,"date":"2022-02-12T18:57:19","date_gmt":"2022-02-12T17:57:19","guid":{"rendered":"https:\/\/www.csopro.de\/biblog\/?p=525"},"modified":"2022-03-09T00:34:59","modified_gmt":"2022-03-08T23:34:59","slug":"automatisierte-anlage-von-rollen-in-analysis-services","status":"publish","type":"post","link":"https:\/\/www.csopro.de\/biblog\/2022\/02\/automatisierte-anlage-von-rollen-in-analysis-services\/","title":{"rendered":"Automatisierte Anlage von Rollen in Analysis Services"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\" id=\"aufgabenstellung\">Aufgabenstellung<\/h2>\n\n\n\n<p>In einem aktuellen Projekt hatten wir die Aufgabe, in mehreren Cubes insgesamt ca. 250 Rollen anzulegen bzw. zu aktualisieren. <br>Ausgangspunkt war eine interne Umstrukturierung der Vertriebsstruktur, was dann in Rollen abgebildet werden musste.<\/p>\n\n\n\n<p>[Nebenbemerkung: Nat\u00fcrlich gibt es andere Optionen wie z.B. dynamische Rechtevergabe mittels USERNAME() oder CUSTOMDATA(). In diesem Projekt war aber eine hart kodierte L\u00f6sung sinnvoll, da die Rechtevergabe nicht dynamisch erfolgen soll und die Benutzer eh zu AD-Gruppen zugeordnet sind.]<\/p>\n\n\n\n<p>Somit war die Anforderung, ca. 250 S\u00e4tze mit folgenden Informationen automatisch zu Rollen umzusetzen:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Name des Cubes<\/li><li>Name der Rolle<\/li><li>Beschreibung<\/li><li>Filtereinstellungen f\u00fcr bestimmte Kunden-Attribute (Hub, Market, Sales Country) und ggf. Element-Attribute<\/li><li>Zuordnung einer oder mehrerer AD-Gruppen zu dieser Rolle<\/li><\/ul>\n\n\n\n<p>Deswegen haben wir zun\u00e4chst eine Tabelle angelegt, in der wir diese Metadaten speichern konnten:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.csopro.de\/biblog\/wp-content\/uploads\/2022\/02\/grafik.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"144\" src=\"https:\/\/www.csopro.de\/biblog\/wp-content\/uploads\/2022\/02\/grafik-1024x144.png\" alt=\"\" class=\"wp-image-526\" srcset=\"https:\/\/www.csopro.de\/biblog\/wp-content\/uploads\/2022\/02\/grafik-1024x144.png 1024w, https:\/\/www.csopro.de\/biblog\/wp-content\/uploads\/2022\/02\/grafik-300x42.png 300w, https:\/\/www.csopro.de\/biblog\/wp-content\/uploads\/2022\/02\/grafik-768x108.png 768w, https:\/\/www.csopro.de\/biblog\/wp-content\/uploads\/2022\/02\/grafik.png 1531w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption>Tabelle mit den Metdadaten (Auszug)<\/figcaption><\/figure>\n\n\n\n<p>Das CREATE-Statement sieht so aus:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">CREATE TABLE [Config].[CubeRollen](\n\t[Cube] [nvarchar](100) NOT NULL,\n\t[CubeRolle] [nvarchar](50) NOT NULL,\n\t[Description] [nvarchar](1000) NULL,\n\t[Filter_SalesCountry] [nvarchar](3) NULL,\n\t[Filter_HubId] [int] NULL,\n\t[Filter_RegionId] [int] NULL,\n\t[Filter_ElementNo] [int] NULL,\n\t[Bemerkung] [nvarchar](1000) NULL,\n\t[ADGruppe] [nvarchar](100) NULL,\n\t[AdminRights] [tinyint] NOT NULL,\n\t[ProcessPermission] [tinyint] NOT NULL,\n\t[ProcessAndReadPermission] [tinyint] NOT NULL,\n CONSTRAINT [PK_Config_CubeRollen] PRIMARY KEY CLUSTERED \n(\n\t[Cube] ASC,\n\t[CubeRolle] ASC\n)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]\n) ON [PRIMARY]\nGO\n\nALTER TABLE [Config].[CubeRollen] ADD  DEFAULT ((0)) FOR [AdminRights]\nGO\n\nALTER TABLE [Config].[CubeRollen] ADD  DEFAULT ((0)) FOR [ProcessPermission]\nGO\n\nALTER TABLE [Config].[CubeRollen] ADD  DEFAULT ((0)) FOR [ProcessAndReadPermission]\nGO<\/pre>\n\n\n\n<p>Jetzt betrachten wir mal, wie wir die Rollen automatisch anlegen wollen. Dazu gibt es JSON-Befehler (fr\u00fcher XMLA), die der Analysis Services (ab SQL 2016) ausf\u00fchrt. F\u00fcr die 4.Zeile des obigen Screen Shots sieht der Befehl so aus: (wenn man die Syntax nicht kennt, kann man sich so einen Befehl \u00fcber die Skript-Funktionalit\u00e4t im SQL Server Management Studio erstellen lassen)<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n  \"createOrReplace\": {\n    \"object\": {\n      \"database\": \"AFD Order Entry Reporting\",\n      \"role\": \"Andorra\"\n    },\n    \"role\": {\n      \"name\": \"Andorra\",\n      \"description\": \"Role for Andorra\",\n      \"modelPermission\": \"read\",\n      \"members\": [ { \"memberName\": \"DOM\u00c4NE\\\\BI_Andorra\" } ],\n      \"tablePermissions\": [\n        {\n          \"name\": \"Customer\",\n          \"filterExpression\": \"Customer[Hub ID]=\\\"20\\\" &amp;&amp; Customer[Sales country Id]=\\\"AD\\\"\"\n        }\n      ]\n    }\n  }\n}<\/pre>\n\n\n\n<p>Wir sehen, dass die Bedingung [Filter_HubId]=20 und [Filter_SalesCountry]=AD sich zu folgendem Block \u00fcbersetzt:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\"filterExpression\": \"Customer[Hub ID]=\\\"20\\\" &amp;&amp; Customer[Sales country Id]=\\\"AD\\\"\"<\/pre>\n\n\n\n<p>Im \u00fcbrigen kann man auch mehrere solche Rollen-Anlage-Skripte verkn\u00fcpfen &#8211; mit:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n  \"sequence\": {\n    \"operations\": [\n      {\n        \"createOrReplace\": { ... }\n      },\n      {\n        \"createOrReplace\": { ... }\n      },\n      {\n        \"createOrReplace\": { ... }\n      }\n    ]\n  }\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"umsetzung-mittels-sql\">Umsetzung mittels SQL<\/h2>\n\n\n\n<p>Damit wir diese JSONs erstellen k\u00f6nnen, brauchen wir noch eine Konfigurationstabelle, in der wir definieren, wie sich die Eintr\u00e4ge in den einzelnen [Filter_&#8230;]-Spalten in diese Bedingungen \u00fcbersetzen. Diese \u00dcbersetzung kann f\u00fcr jeden Cube unterschiedlich sein. Dazu haben wir folgende Tabelle angelegt:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">CREATE TABLE [Config].[CubeFilterDefinition](\n\t[Cube] [nvarchar](100) NOT NULL,\n\t[Filter_SalesCountry] [nvarchar](100) NULL,\n\t[Filter_HubId] [nvarchar](100) NULL,\n\t[Filter_RegionId] [nvarchar](100) NULL,\n\t[Filter_ElementNo] [nvarchar](100) NULL,\n CONSTRAINT [PK_Config_CubeFilterDefinition] PRIMARY KEY CLUSTERED \n(\n\t[Cube] ASC\n)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]\n) ON [PRIMARY]\nGO\nINSERT INTO [Config].[CubeFilterDefinition]\nSELECT 'AFD Order Entry Reporting', \n'Customer[Sales country Id]=\\\"##Wert##\\\"',\n'Customer[Hub ID]=\\\"##Wert##\\\"',\n'Customer[Region ID]=##Wert##',\nNULL\nINSERT INTO [Config].[CubeFilterDefinition]\nSELECT 'DCH Sales Margin Analysis', \n'Customer[Sales country Id]=\\\"##Wert##\\\"',\n'Customer[Hub ID]=\\\"##Wert##\\\"',\n'Customer[RegionID]=##Wert##',\n'Element[Element No]=\\\"##Wert##\\\"'<\/pre>\n\n\n\n<p>Man sieht, dass die Einschr\u00e4nkung aufs Element-Attribut nur beim 2. Cube m\u00f6glich ist (weil der erste Cube gar nicht diese Dimension hat).<\/p>\n\n\n\n<p>F\u00fcr eine Verallgemeinerung muss man nat\u00fcrlich die Filter_&#8230;-Spalten an die jeweilige Situation anpassen.<\/p>\n\n\n\n<p>Nun bauen wir das SQL-Statement f\u00fcr die Erstellung des JSON schrittweise zusammen. F\u00fcr jeden Schritt verwende ich eine eigene CommonTableExpression, damit man die Schritte einfach einzeln bauen kann.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">with piv as\n(\nselect r.[Cube], r.CubeRolle, \nr.Filter_SalesCountry as FilterWert, f.Filter_SalesCountry as FilterCondition from config.CubeRollen r\ninner join config.CubeFilterDefinition f\non r.Cube=f.Cube\nUNION \nselect r.[Cube], r.CubeRolle, \nconvert(nvarchar(10), r.Filter_HubId) as FilterWert, f.Filter_HubID as FilterCondition from config.CubeRollen r\ninner join config.CubeFilterDefinition f\non r.Cube=f.Cube\nUNION \nselect r.[Cube], r.CubeRolle, \nconvert(nvarchar(10), r.Filter_RegionId) as FilterWert, f.Filter_RegionID as FilterCondition from config.CubeRollen r\ninner join config.CubeFilterDefinition f\non r.Cube=f.Cube\nUNION \nselect r.[Cube], r.CubeRolle, \nconvert(nvarchar(10), r.Filter_ElementNo) as FilterWert, f.Filter_ElementNo as FilterCondition from config.CubeRollen r\ninner join config.CubeFilterDefinition f\non r.Cube=f.Cube\n)\nselect * from piv<\/pre>\n\n\n\n<p>Dieses SQL liefert die Spalten:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Cube<\/li><li>CubeRolle<\/li><li>FilterWert: der jeweilige Wert, auf den gefiltert werden soll (also in unserem Andorra-Beispiel von oben: <em>20 <\/em>bzw. <em>AD<\/em><\/li><li>FilterCondition: die jeweilige Definition aus der Meta-Tabelle, wie dieser Filterwert anzuwenden ist (z.B. <em>Customer[Hub ID]=\\&#8220;##Wert##\\&#8220;<\/em> bzw. <em>Customer[Sales country Id]=\\&#8220;##Wert##\\&#8220;<\/em>)<\/li><\/ul>\n\n\n\n<p>Im n\u00e4chsten Schritt ermitteln wir aus der FilterCondition die Tabelle (im Beispiel <em>Customer<\/em>). Das geht mit der SQL-Funktion charindex zum Suchen des Texts &#8222;[&#8222;:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">,\npivMitFilteredTable as (\nselect *, left(FilterCondition, charindex('[', FilterCondition, 1)-1) as FilteredTable  from piv\n)\nselect * from pivMitFilteredTable<\/pre>\n\n\n\n<p>Im n\u00e4chsten Schritt gruppieren wir nach FilteredTable und bauen die Bedingung pro Tabelle zusammen (in unserem Beispiel <em>Customer[Hub ID]=\\&#8220;20\\&#8220; &amp;&amp; Customer[Sales country Id]=\\&#8220;AD\\&#8220;<\/em>):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">,\nRollenFilter as (\nselect [Cube], CubeRolle, FilteredTable , string_agg(convert(nvarchar(max), replace(FilterCondition, '##Wert##', FilterWert)), ' &amp;&amp; ') as Filter from pivMitFilteredTable\ngroup by [Cube], CubeRolle, FilteredTable\n)\nselect * from RollenFilter<\/pre>\n\n\n\n<p>Nun erg\u00e4nzen wir diese Tabelle um die Description, die ADGruppe und die modelPermission (hier werden Admin- und processor-Rechte separat vergeben):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">,\nFDef as (\nselect rf.*, r.Description, ADGruppe, \ncase when AdminRights=1 then N'administrator'\nwhen ProcessPermission=1 then N'refresh'\nwhen ProcessAndReadPermission=1 then N'readRefresh'\nelse N'read' end as modelPermission from RollenFilter rf\nleft join Config.CubeRollen r\non rf.[cube] = r.[Cube] and rf.CubeRolle = r.CubeRolle\n)\nselect * from FDef<\/pre>\n\n\n\n<p>Jetzt haben wir alles, um die ersten JSON-Fragmente zusammenzubauen. <\/p>\n\n\n\n<p>Au\u00dferdem ist es erlaubt, in der Definition in dem Feld ADGruppe mehrere AD-Gruppen zu spezifizieren &#8211; indem sie durch , getrennt sind.  Zus\u00e4tzlich wird die Dom\u00e4ne vor die AD-Gruppe geschrieben. Somit entsteht aus <\/p>\n\n\n\n<p>BI_All,BI_DOCH_All<\/p>\n\n\n\n<p>das JSON-Fragment<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">[\n  { \"memberName\": \"DOM\u00c4NE\\\\BI_All\" },\n  { \"memberName\": \"DOM\u00c4NE\\\\BI_DOCH_All\" }\n]<\/pre>\n\n\n\n<p>Dazu verwenden wir die Funktionen STRING_AGG und STRING_SPLIT (die es ab SQL 2017 gibt). Dieser Schritt sieht dann so aus:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">, ff as (\nselect [cube], CubeRolle,\n'{\n  \"createOrReplace\": {\n    \"object\": {\n      \"database\": \"' + [Cube] + '\",\n      \"role\": \"' + CubeRolle + '\"\n    },\n    \"role\": {\n      \"name\": \"' + CubeRolle + '\",\n      \"description\": \"' + isnull(Description, '') + '\",\n      \"modelPermission\": \"' + modelPermission + '\", ' + isnull('\n      \"members\": [' \n+ (select string_agg('{\n          \"memberName\": \"' + replace(case when value like '%\\%' then value else 'DOM\u00c4NE\\'+ value end, '\\', '\\\\') + '\"\n        }', ',') from ( Select value from string_split(ADGruppe, ',')) as x) + '\n      ]', '') as Rumpf_Anfang, '\n    }\n  }\n}' as Rumpf_ende, FilteredTable, Filter, description, modelPermission, ADGruppe\nfrom FDef\n)\nselect * from ff<\/pre>\n\n\n\n<p>Als n\u00e4chstes bauen wir das Filter-JSON-Fragment pro Tabelle erstellt, f\u00fcr obiges Beispiel also<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n  \"name\": \"Customer\",\n  \"filterExpression\": \"Customer[Hub ID]=\\\"20\\\" &amp;&amp; Customer[Sales country Id]=\\\"AD\\\"\"\n}<\/pre>\n\n\n\n<p>Dazu verwenden wir dieses SQL-Fragment:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">,f2 as (\nselect [cube], CubeRolle, Rumpf_Anfang, Rumpf_ende,  '{\n          \"name\": \"' + FilteredTable + '\",\n          \"filterExpression\": \"' + Filter + '\"\n        }' as  FilterZeile, FilteredTable, Filter from ff\n)\nselect * from f2<\/pre>\n\n\n\n<p>Dann m\u00fcssen wir nur noch mit einem Group By Cube + CubeRolle das JSON pro Rolle bauen und konkatenieren:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">,\nerg as (\nselect [cube], CubeRolle, Rumpf_Anfang + \nisnull(\n', \"tablePermissions\": [' + \nstring_agg(convert(nvarchar(max), FilterZeile), ', ') \n+ ']', '')\n+ Rumpf_ende xmla from f2\ngroup by [cube], CubeRolle, Rumpf_Anfang , Rumpf_ende \n)\nselect '{\n\"sequence\":\n{\n\"operations\": [\n' + string_agg(xmla, ',') \n+ ']}}'\nfrom erg<\/pre>\n\n\n\n<p>Somit siehr das fertige SQL so aus:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">with piv as\n(\nselect r.[Cube], r.CubeRolle, \nr.Filter_SalesCountry as FilterWert, f.Filter_SalesCountry as FilterCondition from config.CubeRollen r\ninner join config.CubeFilterDefinition f\non r.Cube=f.Cube\nUNION \nselect r.[Cube], r.CubeRolle, \nconvert(nvarchar(10), r.Filter_HubId) as FilterWert, f.Filter_HubID as FilterCondition from config.CubeRollen r\ninner join config.CubeFilterDefinition f\non r.Cube=f.Cube\nUNION \nselect r.[Cube], r.CubeRolle, \nconvert(nvarchar(10), r.Filter_RegionId) as FilterWert, f.Filter_RegionID as FilterCondition from config.CubeRollen r\ninner join config.CubeFilterDefinition f\non r.Cube=f.Cube\nUNION \nselect r.[Cube], r.CubeRolle, \nconvert(nvarchar(10), r.Filter_ElementNo) as FilterWert, f.Filter_ElementNo as FilterCondition from config.CubeRollen r\ninner join config.CubeFilterDefinition f\non r.Cube=f.Cube\n),\npivMitFilteredTable as (\nselect *, left(FilterCondition, charindex('[', FilterCondition, 1)-1) as FilteredTable  from piv\n),\nRollenFilter as (\nselect [Cube], CubeRolle, FilteredTable , string_agg(convert(nvarchar(max), replace(FilterCondition, '##Wert##', FilterWert)), ' &amp;&amp; ') as Filter from pivMitFilteredTable\ngroup by [Cube], CubeRolle, FilteredTable\n)\n,\nFDef as (\nselect rf.*, r.Description, ADGruppe, \ncase when AdminRights=1 then N'administrator'\nwhen ProcessPermission=1 then N'refresh'\nwhen ProcessAndReadPermission=1 then N'readRefresh'\nelse N'read' end as modelPermission from RollenFilter rf\nleft join Config.CubeRollen r\non rf.[cube] = r.[Cube] and rf.CubeRolle = r.CubeRolle\n)\n, ff as (\nselect [cube], CubeRolle,\n'{\n  \"createOrReplace\": {\n    \"object\": {\n      \"database\": \"' + [Cube] + '\",\n      \"role\": \"' + CubeRolle + '\"\n    },\n    \"role\": {\n      \"name\": \"' + CubeRolle + '\",\n      \"description\": \"' + isnull(Description, '') + '\",\n      \"modelPermission\": \"' + modelPermission + '\", ' + isnull('\n      \"members\": [' \n+ (select string_agg('{\n          \"memberName\": \"' + replace(case when value like '%\\%' then value else 'DOM\u00c4NE\\'+ value end, '\\', '\\\\') + '\"\n        }', ',') from ( Select value from string_split(ADGruppe, ',')) as x) + '\n      ]', '') as Rumpf_Anfang, '\n    }\n  }\n}' as Rumpf_ende, FilteredTable, Filter, description, modelPermission, ADGruppe\nfrom FDef\n)\n,f2 as (\nselect [cube], CubeRolle, Rumpf_Anfang, Rumpf_ende,  '{\n          \"name\": \"' + FilteredTable + '\",\n          \"filterExpression\": \"' + Filter + '\"\n        }' as  FilterZeile, FilteredTable, Filter from ff\n)\n,\nerg as (\nselect [cube], CubeRolle, Rumpf_Anfang + \nisnull(\n', \"tablePermissions\": [' + \nstring_agg(convert(nvarchar(max), FilterZeile), ', ') \n+ ']', '')\n+ Rumpf_ende xmla from f2\ngroup by [cube], CubeRolle, Rumpf_Anfang , Rumpf_ende \n)\nselect '{\n\"sequence\":\n{\n\"operations\": [\n' + string_agg(xmla, ',') \n+ ']}}'\nfrom erg<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"anpassungen\">Anpassungen<\/h2>\n\n\n\n<p>Theoretisch k\u00f6nnte man dieses JSON auch automatisch an den Analysis Services schicken (z.B. via ETL). In unserem Projekt haben wir es einfach mit Copy&amp;Paste in das SQL Server Management Studio kopiert und dort ausgef\u00fchrt.<\/p>\n\n\n\n<p>F\u00fcr jeden Anwendungsfall muss man nat\u00fcrlich die unterschiedlichen gefilterten Felder spezifizieren. Das bedeutet &#8211; wie oben angedeutet &#8211; die Anpassung der Datenbank-Struktur. Nat\u00fcrlich k\u00f6nnte man das Datenmodell auch so w\u00e4hlen, dass f\u00fcr unterschiedliche Filter keine Struktur\u00e4nderung erforderlich w\u00e4re. Wir haben uns aber bewusst f\u00fcr die gezeigte L\u00f6sung entschieden, weil dadurch die Tabelle auch f\u00fcr Nicht-IT-ler verst\u00e4ndlich bleibt und so die Meta-Daten von den Fachbereichen gepflegt werden k\u00f6nnen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Aufgabenstellung 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\u00fcrlich gibt es andere Optionen wie z.B. dynamische Rechtevergabe mittels USERNAME() oder CUSTOMDATA(). In diesem Projekt war aber eine hart &hellip; <a href=\"https:\/\/www.csopro.de\/biblog\/2022\/02\/automatisierte-anlage-von-rollen-in-analysis-services\/\" class=\"more-link\"><span class=\"screen-reader-text\">Automatisierte Anlage von Rollen in Analysis Services<\/span> weiterlesen <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[63,9],"tags":[60,82,80,79,81],"class_list":["post-525","post","type-post","status-publish","format-standard","hentry","category-azure-anaysis-services","category-analysisservices","tag-analysis-services","tag-json","tag-rollen","tag-security","tag-xmla"],"_links":{"self":[{"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/posts\/525","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/comments?post=525"}],"version-history":[{"count":17,"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/posts\/525\/revisions"}],"predecessor-version":[{"id":547,"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/posts\/525\/revisions\/547"}],"wp:attachment":[{"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/media?parent=525"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/categories?post=525"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.csopro.de\/biblog\/wp-json\/wp\/v2\/tags?post=525"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}