Analysis Services Security: Multiple Roles in Tabular vs. Multidimensional – Part 2

In one of my recent posts I highlighted how multiple security roles are handled in tabular opposed to multidimensional Analysis Services models. In this post I will go into more detail and focus on how the security is evaluated for both models.

I would like to continue using the same example as in the last post with the following roles:
“Bikes” – is restricted to [ProductCategory] = “Bikes”
“Brakes” – is restricted to [ProductSubCategory] = “Brakes”
“DE” – is restricted to [Country] = “DE”

I used the following MDX query to test both, the tabular and the multidimensional model:

  2. NON EMPTY {[Geography].[Country Region Name].[Country Region Name].MEMBERS} ON 0,
  3. NON EMPTY {[Product].[Product Category Name].[Product Category Name].MEMBERS} ON 1
  4. FROM [Model]
  5. WHERE (
  6. [Measures].[Reseller Total Sales]
  7. )

The last thing I mentioned was how the combination of roles “Bikes” and “DE” could be expressed in SQL:

  2.     [ProductCategory],
  3.     [Country],
  4.     SUM([Reseller Sales])
  5. FROM <table>
  6. WHERE [ProductCategory] = ‘Bikes’
  7.     OR [Country] = ‘Germany’
  9.     [Product Category],
  10.     [Country]


When running the previous MDX query on our tabular model using roles “Bikes” and “DE” we will see a very similar query being executed against our xVelocity Storage Engine:
(Note: There are also some SE engine queries executed to get the elements for rows and columns but the query below is the main query)

  2. [Geography_1fa13899-0770-4069-b7cb-5ddf22473324].[EnglishCountryRegionName],
  3. [Product_11920c93-05ae-4f1c-980e-466dfbcfca2a].[CalculatedColumn1 1],
  4. SUM([Reseller Sales_fc635e72-28dc-4156-80d5-43b805f8df1c].[SalesAmount])
  5. FROM [Reseller Sales_fc635e72-28dc-4156-80d5-43b805f8df1c]
  6. LEFT OUTER JOIN [Reseller_d52b9c6f-8d2d-4e23-ae4c-2fc57c1d968a]
  7.         ON [Reseller Sales_fc635e72-28dc-4156-80d5-43b805f8df1c].[ResellerKey]
  8.             =[Reseller_d52b9c6f-8d2d-4e23-ae4c-2fc57c1d968a].[ResellerKey]
  9. LEFT OUTER JOIN [Geography_1fa13899-0770-4069-b7cb-5ddf22473324]
  10.     ON [Reseller_d52b9c6f-8d2d-4e23-ae4c-2fc57c1d968a].[GeographyKey]
  11.         =[Geography_1fa13899-0770-4069-b7cb-5ddf22473324].[GeographyKey]
  12. LEFT OUTER JOIN [Product_11920c93-05ae-4f1c-980e-466dfbcfca2a]
  13.     ON [Reseller Sales_fc635e72-28dc-4156-80d5-43b805f8df1c].[ProductKey]
  14.         =[Product_11920c93-05ae-4f1c-980e-466dfbcfca2a].[ProductKey]
  15. WHERE
  16. (COALESCE((PFDATAID( [Product_11920c93-05ae-4f1c-980e-466dfbcfca2a].[CalculatedColumn1 1] ) = 6))
  17.     OR
  18. COALESCE((PFDATAID( [Geography_1fa13899-0770-4069-b7cb-5ddf22473324].[CountryRegionCode] ) = 5)));

Well, not really readable so lets make it a bit nicer by removing those ugly GUIDs, etc:

  2.     [Geography].[EnglishCountryRegionName],
  3.     [Product].[ProductCategory],
  4.     SUM([Reseller Sales].[SalesAmount])
  5. FROM [Reseller Sales]
  6. LEFT OUTER JOIN [Reseller]
  7.     ON [Reseller Sales].[ResellerKey] = [Reseller].[ResellerKey]
  8. LEFT OUTER JOIN [Geography]
  9.     ON [Reseller].[GeographyKey] = [Geography].[GeographyKey]
  10. LEFT OUTER JOIN [Product]
  11.     ON [Reseller Sales].[ProductKey] = [Product].[ProductKey]
  12. WHERE [Product].[ProductCategory] = "Bikes"
  13. OR [Geography].[CountryRegionCode] = "Germany";

This looks very similar to our SQL query. The special thing about it is the WHERE clause which combines the restrictions of both roles using OR which is then propagated also to our [Reseller Sales] fact table and that’s the reason why we see what we want and expect to see – all sales that were either made with “Bikes” OR made in “Germany”:

Another very important thing to note and remember here is that the security restrictions get propagated into and are evaluated within the query. This is done for each and every query (!) which is usually not a problem but may become crucial if you use dynamic security.
To test this with dynamic security I introduced a new role called “CustData” which is going to replace our “Bikes” for this test. It is restricted on table ‘Product’ as:

  1. =([Product Category Name] = IF( CUSTOMDATA() = "1", "Bikes", "Clothing"))

So instead of using the connectionstring “…;Roles=Bikes,DE; …” I now used “…;Roles=CustData,DE;CustomData=1; …” which results in the exact same results of course. However, the query now changed to the following (already beautified) xVelocity query:

  2.     [Geography].[EnglishCountryRegionName],
  3.     [Product].[ProductCategory],
  4.     SUM([Reseller Sales].[SalesAmount])
  5. FROM [Reseller Sales]
  6. LEFT OUTER JOIN [Reseller]
  7.     ON [Reseller Sales].[ResellerKey] = [Reseller].[ResellerKey]
  8. LEFT OUTER JOIN [Geography]
  9.     ON [Reseller].[GeographyKey] = [Geography].[GeographyKey]
  10. LEFT OUTER JOIN [Product]
  11.     ON [Reseller Sales].[ProductKey] = [Product].[ProductKey]
  12. WHERE [Product].$ROWFILTER IN '0x000000…000000000000000000000fffff00000000000ffffffff'));
  13. OR [Geography].[CountryRegionCode] = "Germany";

Instead of using a direct filter on [ProductCategory] we now see a filter on $ROWFILTER ?!?

I have to admit that I am currently not entirely sure what this means but I assume the following:
Preceding the main query another xVelocity query is executed which is important for us:

  2. [Product].[RowNumber],
  3. [Product].[ProductCategory],
  4. COUNT()
  5. FROM [Product];

This query fetches each [RowNumber] and its associated [ProductCategory]. Internally the [RowNumber] column is created for every table. This is related to the columnar storage that xVelocity uses. Elaborating this in detail would go far beyond the context of this blog post. For more details on the RowNumber-column please refer too which describes the Excel data model which is actually Power Pivot and therefore also applies to Tabular. (In general this link contains a lot of in-depth information on the tabular data model and the columnar storage concepts!)

I further assume that our security-expression is then evaluated against this temporary table to create an bitmap index of which rows match the security-expression and which don’t. This result is then applied to our main query which using the WHERE clause  [Product].$ROWFILTER IN ‘0x0000….’
For all subsequent queries the above query on [RowNumber] and [ProductCategory] is not executed again so I assume that the bitmap index gets cached internally by Analysis Services. I further assume that if the bitmap index gets cached it is also shared between users belonging to the same role which would be similar to multidimensional models.

So the important take-away for tabular is that the security gets propagated into the query and down to the fact table. Combining multiple roles on this level using OR delivers the expected results.


For multidimensional models this is slightly different. You can define security on either the Database Dimension (which gets inherited down to all Cube Dimension) or you can define security on the Cube Dimension directly. Defining security on the Database Dimension already makes it very clear that the security restrictions are independent of any fact table/measure group. A Database Dimension may be used in several cubes so the engine cannot know in advance which measure group to use. Security for multidimensional models is defined on the multidimensional space defined by that dimension. If one role is not restricted on a dimension at all, the user will always see the whole dimension and its associated values, even if a second role would restrict that dimension. And this causes unexpected results as the user may see the whole cube.
In terms of SQL the query could be expressed as:

  2.     [ProductCategory],
  3.     [Country],
  4.     SUM([Reseller Sales])
  5. FROM <table>
  6. WHERE ( [ProductCategory] = 'Bikes' OR 1 = 1)
  7.     OR ( 1 = 1  OR [Country] = 'Germany')
  9.     [Product Category],
  10.     [Country]

The left side of the inner OR statements represents the role “Bikes” whereas the right part represents the “DE” role. It should be very obvious that due to the combination of both roles you finally see everything without any restriction!

Another important thing to point out is that security for multidimensional models is only evaluated once and not for every query. So even if you have complex dynamic security its evaluation only hits you once for the first user that connects to a role and is cached for all queries of that user and also shared with other users belonging to that role.


I hope this gives some more insights on how tabular and multidimensional handle multiple roles and their fundamental differences!

8 Replies to “Analysis Services Security: Multiple Roles in Tabular vs. Multidimensional – Part 2”

  1. Hi Gerhard!
    Quite old post, but it’s on a subject that is very relevant to me right now.
    We’re in the process of “migrating” some of our MD cubes to Tabular, and I have a slightly different scenario regarding role security that I’m struggling with.

    In MD I’m able to define my roles in such a way that we can have a
    Product role = Bikes, Country role = DE, and a Measures role 3 = Sales.
    To be able to browse the cube you need to be a member of at least one of each kind, meaning you need to have access to at least one product, one country and some measure in it to be able to access it.

    And in the above case we need our users to only be able to see sales for Bikes in DE. Unlike your scenario it’s not acceptable that users see any other measures than bikes sales in DE. No other measures in DE should be visible, and no bikes sales anywhere else should show up.

    In MD I’ve been able to achieve this. But in Tabular I can’t find a similar solution.

    Why we need this setup is because we have the same AD groups, for example Country groups and Product groups applied to many different cubes, and you might only be given access to a few of those cubes. By having “Measure-roles” we can restrict access to the cubes individually, even though we have many different shared AD-groups that are applied across multiple cubes.

    Do you have any idea on how to solve this? Or maybe my post didn’t even make any sense ?

    • Why cant you use the very same approach also in Tabular? One role for product, one for country and one for the colums that are used for the measures? Then assign the existing AD groups to those roles as you did in MD

      • Hi!
        Super quick reply, thank you for that ?

        If my brain has interpreted the outcome correctly, I get a feeling that it works like this.
        Each role (Country-role, Measure-role and Product-role), are evaluated individually against the whole model, to see which rows in which tables that a user is allowed to see. Once that evaluation is done the “total” access for the user is simply the sum of the rows for each role he/she is a member of.

        In my case, since the Measure-role only should grant access to the measures, and NO countries, and NO products, that role by itself actually doesn’t give access to any measures at all, since none can be found without a country or product key.

        Same goes for the Country-role and Product-role. They will give access to DE in a Country-table, or a Bikes in the Product-table, but since no measures (columns from the measures-table) should be granted on those roles, they don’t contain any measure-rows either.

        The sum of all three will then result in country and product-data available from those tables, but nothing from the measures-table.

        • well, you can grant access only to a measure and deny access to all countries and all products in the same role. This way the user belonging only to this role would see the measure, but it would be empty.
          the country roles has to be created in a similar way – explicitly grant access to a country and also explicitly deny access to the measure(s) and to the products
          same also for the product role

          in case someone is not member of all three roles, one of the secured dimensions will be empty and hence he will not see any data
          all 3 roles need to be assigned to remove all denies from all 3 roles

          But I have to admit that I have not tested a scenario like this on my own yet but it would be really interesting to get your feedback!

          • Hi!

            That’s exactly what I’ve been testing, but don’t get it to work.

            It works perfectly fine in Multidim, but not in Tabular.

            Thought maybe there was something simple that I missed, that you knew of, in my setup.

            Well well… will make a few more attempts to solve it. Otherwise we might have to change the security setup instead to suit the tabular model.

            Thanks anyway


        • Hi!

          Is there a way to have the roles act as AND logical? I need to have Product role = Bikes AND Country role = DE.
          One way if to add them into one role, but I want to have 2 separate roles, each filtering only one dimension, and if a user belongs to both groups, AND logical gets applied.
          Is there a way for that?

          • Hi Farhad,

            I am afraid that currently not possible – you would need to have it all in one role otherwise an OR is applied.
            in general, you cannot loose access to anything by being added to additional roles


  2. Hi again,

    Ok thanks you for your quick reply. I will have to merge the roles in one role to have the AND logical. As long as I remember, there was a way to implement the AND in multidimensional cubes, but I don’t remember exactly. I thought there would be a way in tabular as well.

    Thanks again,

Leave a Reply