In the first two parts of this series (see here and here) I showed how Power BI/Power Query/Excel Get & Transform’s data privacy settings can influence whether query folding takes place or even whether a query is able to run or not. In this post I’m going to talk about the situations where, whatever data privacy level you use, the query will not run at all and you get the infamous Formula.Firewall error.
I’ll admit I don’t understand this particular topic perfectly (I’m not sure anyone outside the Power Query dev team does) so what I will do is explain what I do know, demonstrate a few scenarios where the error occurs and show how to work around it.
Assume you have the two data sources described in my previous posts: an Excel workbook that contains just a single day name, and the DimDate table in SQL Server that can be filtered by the day name from Excel. Let’s also assume that both data sources have their data privacy levels set to Public. The following query, called FilterDay, loads the data from Excel and returns a text value containing the day name:
let Source = Excel.Workbook( File.Contents("C:\FilterParameter.xlsx"), null, true), FilterDay_Table = Source{[Item="FilterDay",Kind="Table"]}[Data], ChangedType = Table.TransformColumnTypes( FilterDay_Table, {{"Parameter", type text}} ), Output = ChangedType{0}[#"Parameter"] in Output
Now, look at the following query:
let Source = Sql.Database( "localhost", "adventure works dw", [Query="select DateKey, EnglishDayNameOfWeek from DimDate"]), FilteredRows = Table.SelectRows(Source, each ([EnglishDayNameOfWeek] = FilterDay) ) in FilteredRows
It filters the contents of the DimDate table and only returns the rows where the EnglishDayNameOfWeek column matches the day name returned by the FilterDay query. Notice that there are two steps in the query, Source (which runs a SQL query) and FilteredRows (which does the filtering). Here’s the output:
As you can see from the screenshot, the query runs. In fact it runs whatever data privacy settings you have set on both the data sources, although it’s worth pointing out that if you use your own SQL in an M query (as I do in this case) this stops query folding in all subsequent steps, as described here.
Now take a look at the following version of the query:
let Source = Table.SelectRows( Sql.Database( "localhost", "adventure works dw", [Query="select DateKey, EnglishDayNameOfWeek from DimDate"] ), each ([EnglishDayNameOfWeek] = FilterDay) ) in Source
The important difference here is that there is now one step in this query instead of two: the query and the filtering take place in the same step. Even more importantly, regardless of the data privacy settings, the query fails with the error:
Formula.Firewall: Query ‘DimDate With Native Query Single Step Fails’ (step ‘Source’) references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.
The problem here is that the Power Query engine is not allowed to access two different data sources originating from different queries in the same step – as far as I understand it this is because it makes it too hard for the engine to work out whether a step connects to a data source or not, and so which data privacy rules should be applied.
At this point you might think that it’s straightforward to break your logic up into separate steps, as in the first example above. However there are some situations where it’s not so easy to work around the problem. For example, consider the following query:
let Source = Sql.Database( "localhost", "adventure works dw", [Query=" select DateKey, EnglishDayNameOfWeek from DimDate where EnglishDayNameOfWeek='" & FilterDay & "'" ] ) in Source
In this example I’m dynamically generating the SQL query that is being run and passing the name of the day to filter by into the WHERE clause. In the two previous examples the query that was run had no WHERE clause and the filtering on day name took place inside Power BI – in this case the filtering is happening inside the query, so in order to generate the WHERE clause I have to refer to the value that the FilterDay query returns in the same step. Therefore, this query also gives the same Formula.Firewall error seen above.
How can you work around this? Well, the following version of the query that attempts to reference FilterDay in a separate step doesn’t work either:
let DayAsStep = FilterDay, Source = Sql.Database( "localhost", "adventure works dw", [Query=" select DateKey, EnglishDayNameOfWeek from DimDate where EnglishDayNameOfWeek='" & DayAsStep & "'" ] ) in Source
Luckily, it turns out that if you use the Value.NativeQuery() function to run your query instead you can avoid the error. As I showed here, you can use this function to pass parameters to SQL queries. If you generate the record containing the parameters for the query as a separate step (called ParamRecord here), like so:
let Source = Sql.Database("localhost", "adventure works dw"), ParamRecord = [FilterParameter=FilterDay], Query = Value.NativeQuery( Source, "select DateKey, EnglishDayNameOfWeek from DimDate where EnglishDayNameOfWeek=@FilterParameter", ParamRecord) in Query
Then the query runs successfully.
There is another way to avoid the error. In all the examples above I have two queries: one to get data from Excel, one to get filtered data from SQL Server. If these two queries are combined into a single query, it doesn’t matter if data from different data sources is accessed in the same step. So, for example, unlike all of the queries above the following query does not reference any other queries; instead it gets the day name from the Excel workbook in the ExcelSource step and then runs the dynamic SQL query in the SQLSource step, and runs successfully:
let ExcelSource = Excel.Workbook( File.Contents("C:\FilterParameter.xlsx") , null, true), FilterDay_Table = ExcelSource{[Item="FilterDay",Kind="Table"]}[Data], ChangedType = Table.TransformColumnTypes(FilterDay_Table, {{"Parameter", type text}}), FilterDayStep = ChangedType{0}[#"Parameter"], SQLSource = Sql.Database( "localhost", "adventure works dw", [Query=" select DateKey, EnglishDayNameOfWeek from DimDate where EnglishDayNameOfWeek='" & FilterDayStep & "'" ]) in SQLSource
Clearly the M engine doesn’t get confused about accessing data from different sources in the same step if those data sources are created in the same query.
Of course you can avoid the Formula.Firewall error and make query folding happen as often as possible by turning off data privacy checks completely in the Options dialog. This will be the subject of the next post in this series.