Did you ever experience a long waiting time while opening a Power BI Desktop file?

There could be many reasons for that, but if you have calculated columns and/or calculated tables in your model, you should be aware that they could be the reasons why this happens. It could be, so I want to explain when this happens.

The short explanation is the following: when you open a PBIX file, Power BI Desktop automatically recalculates those calculated columns and calculated tables that depend on a volatile formula.

A volatile formula includes a DAX volatile function, which is any function that may return a different result every time you call it, even if you provide the same arguments:

I am not 100% sure about the list above because Microsoft does not provide documentation about the functions considered “volatile”. Once we have a confirmation about them, it would be a good idea to integrate such information into DAX Guide.

For example, the following calculated table (intentionally slow and useless) is always recalculated when you open Power BI Desktop because it uses the TODAY function:

Calculated table
TestStartup = 
VAR _Today = TODAY ()
VAR _YearStart = YEAR ( _Today )
VAR _YearEnd = YEAR ( _Today )
VAR _Base =
    ADDCOLUMNS (
        CALENDAR ( DATE ( _YearStart, 1, 1 ), DATE ( _YearEnd, 12, 31 ) ),
        "IsFuture", [Date] > _Today
    )
VAR _Step1 = CROSSJOIN ( _Base, SELECTCOLUMNS ( _Base, "Date1", [Date] ) )
VAR _Step2 = CROSSJOIN ( _Step1, SELECTCOLUMNS ( _Base, "Date2", [Date] ) )
VAR _Result = _Step2
RETURN _Result

If you open the file volatile-table.pbix included in the downloadable sample, you wait for at least 20 seconds – if you wait for a multiple of that, now you also have an excellent excuse to change your PC. I count 20 seconds of displaying the splash window with the Microsoft logo you have seen at the beginning.
The presence of TODAY makes the entire calculation dependent on a volatile expression. Because the calculated table is volatile, any other formula that depends on this calculated table will get the same treatment.

If we write a fixed date instead of using TODAY, then the calculated table is no longer dependent on a volatile formula. This is the code of the calculated table in the non-volatile-table.pbix file:

Calculated table
TestStartup = 
VAR _Today = dt"2022-04-28"
VAR _YearStart = YEAR ( _Today )
VAR _YearEnd = YEAR ( _Today )
VAR _Base =
    ADDCOLUMNS (
        CALENDAR ( DATE ( _YearStart, 1, 1 ), DATE ( _YearEnd, 12, 31 ) ),
        "IsFuture", [Date] > _Today
    )
VAR _Step1 = CROSSJOIN ( _Base, SELECTCOLUMNS ( _Base, "Date1", [Date] ) )
VAR _Step2 = CROSSJOIN ( _Step1, SELECTCOLUMNS ( _Base, "Date2", [Date] ) )
VAR _Result = _Step2
RETURN _Result

In this case, the time spent looking at the splash screen is only 5 seconds. Therefore, we removed 15 seconds of waiting time. Of course, the code is no longer updated automatically, but the goal here is to make you aware of the consequences of such functions.
For example, to speed up the development, it could be a good idea to isolate the use of TODAY in a single variable. This way, it is easy to replace it with a fixed value during the development for both testing specific conditions and improving the productivity while you are still developing the model.

Now, the bad news.
No, we do not have an easy way to identify the calculated columns and calculated tables recalculated when you open Power BI Desktop.
You can collect the processing trace events from the Analysis Services instance created by Power BI Desktop by following these steps:

  1. Open Power BI Desktop without opening any file
  2. Open DAX Studio from the External Tools ribbon in Power BI Desktop
  3. Open SQL Profiler from the Advanced ribbon in DAX Studio
  4. Go back to Power BI Desktop and open the PBIX file

This way, SQL Profiler collects all the processing events, including all the objects recalculated when you open the file.

By knowing the functions considered volatile by the engine, it should be possible to implement a static analysis of the dependencies in tools with a full DAX parser like Tabular Editor 3.

UPDATE 2022-05-01
Because Power BI Desktop requests a Calculate refresh type on the model when you open the file, you can execute the analysis on an already opened Power BI Desktop file by following these steps:

  1. Open DAX Studio from the External Tools ribbon in Power BI Desktop
  2. Open SQL Profiler from the Advanced ribbon in DAX Studio
  3. Open a PowerShell window and execute the following script replacing the port number after “localhost:” according to the number shown in DAX Studio.
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices.Tabular")
$server = New-Object Microsoft.AnalysisServices.Tabular.Server
# Replace the number with the value you can get from DAX Studio
$server.Connect("localhost:60427")  
$db = $server.Databases[0]
$model = $db.Model
$model.RequestRefresh("Calculate")
$result = $model.SaveChanges()
Write $result.XmlaResults
CUSTOMDATA

Returns the value of the CustomData connection string property if defined; otherwise, BLANK().

CUSTOMDATA ( )

NAMEOF

Returns the name of a column or measure.

NAMEOF ( <Value> )

NOW

Returns the current date and time in datetime format.

NOW ( )

RAND

Returns a random number greater than or equal to 0 and less than 1, evenly distributed. Random numbers change on recalculation.

RAND ( )

RANDBETWEEN

Returns a random number between the numbers you specify.

RANDBETWEEN ( <Bottom>, <Top> )

TODAY

Returns the current date in datetime format.

TODAY ( )

USERCULTURE

Returns the culture code for the user, based on their operating system or browser settings.

USERCULTURE ( )

USERNAME

Returns the domain name and user name of the current connection with the format of domain-name\user-name.

USERNAME ( )

USEROBJECTID

Returns the current user’s Object ID from Azure AD for Azure Analysis Server and the current user’s SID for on-premise Analysis Server.

USEROBJECTID ( )

USERPRINCIPALNAME

Returns the user principal name.

USERPRINCIPALNAME ( )

UTCNOW

Returns the current date and time in datetime format expressed in Coordinated Universal Time (UTC).

UTCNOW ( )

UTCTODAY

Returns the current date in datetime format expressed in Coordinated Universal Time (UTC).

UTCTODAY ( )