Skip to content

DP-600 · Domain 3: Implement and manage semantic models · 23% of the exam

Task 3.1: Design and build semantic models

Storage modes including Direct Lake, uses for DAX Studio and Tabular Editor 2, star schemas, bridge tables and many-to-many relationships, DAX variables, iterators, table filtering, window and information functions, calculation groups, dynamic strings, field parameters, large semantic models, composite models with aggregations, and dynamic row-level and object-level security.

Study it

  • Choosing a storage mode: Import, DirectQuery, Direct Lake or composite

    Partly covered by: Caching

  • Direct Lake framing, fallback, guardrails and security

    Lesson coming

  • Star schemas, bridge tables and many-to-many relationships

    Lesson coming

  • DAX variables, iterators, table filtering, window and information functions

    Partly covered by: Window Functions

  • Calculation groups, dynamic format strings and field parameters

    Lesson coming

  • Large semantic models and composite models with aggregations

    Lesson coming

  • Dynamic row-level and object-level security, and validating it

    Lesson coming

  • DAX Studio and Tabular Editor 2

    Lesson coming

Sample questions

Try each one before opening the answer. Every option is explained, with the Microsoft documentation page that proves it.

Question 1 · choose 1

A warehouse enforces a SQL row-level security policy that limits each regional manager to the rows of their region in dbo.Sales. A team replaces the Direct Lake on SQL semantic model over it with a Direct Lake on OneLake model that reads the same tables through a cloud connection with a fixed identity. After the switch, every manager sees all regions in the reports. Managers must again see only their own region. What should you do?

  1. ASwitch the model's Direct Lake behavior to DirectLakeOnly
  2. BGrant the managers ReadData on the warehouse so the SQL policy applies to them
  3. CTurn single sign-on on in the cloud connection, keeping the fixed identity for refreshes
  4. DDefine the region filter as an RLS role in the semantic model
Show the answer and why
  • ASwitch the model's Direct Lake behavior to DirectLakeOnly

    Incorrect

    That setting applies only to Direct Lake on SQL, and on OneLake the queries already run without fallback; it doesn't bring back the SQL policy.

  • BGrant the managers ReadData on the warehouse so the SQL policy applies to them

    Incorrect

    Direct Lake on OneLake reads the Delta tables through OneLake and never evaluates the SQL policy, whoever queries.

  • CTurn single sign-on on in the cloud connection, keeping the fixed identity for refreshes

    Incorrect

    With SSO, queries would run as each manager and need their own OneLake access to the data, and the SQL policy still wouldn't be applied.

  • DDefine the region filter as an RLS role in the semantic model

    Correct

    Direct Lake on OneLake doesn't apply SQL-based RLS, so with a fixed identity the rule has to live in the model.

Direct Lake on OneLake isn't coupled with the SQL analytics endpoint: it reads Delta tables through OneLake, so row-level security defined in the SQL analytics endpoint or warehouse isn't applied and queries succeed. Data-access rules must then be defined in the semantic model or in OneLake security. Semantic model RLS is the recommended choice when report consumers have no permission on the source item and the cloud connection uses a fixed identity.

Question 2 · choose 1

While developing a Direct Lake on SQL semantic model, you want any query that can't be answered in Direct Lake mode to fail with an error, so that you find and fix the cause before release, instead of quietly running in DirectQuery mode. Which Direct Lake behavior setting should the model use during development?

  1. AAutomatic
  2. BDirectLakeOnly
  3. CDirectQueryOnly
  4. DImport
Show the answer and why
  • AAutomatic

    Incorrect

    Automatic, the default, falls back to DirectQuery silently, which hides the cause.

  • BDirectLakeOnly

    Correct

    With DirectLakeOnly, a query whose Direct Lake conditions aren't met fails with an error, which is meant for development.

  • CDirectQueryOnly

    Incorrect

    DirectQueryOnly sends every query in DirectQuery mode; it's for measuring fallback performance.

  • DImport

    Incorrect

    Import isn't a value of the Direct Lake behavior setting; it's a different table storage mode.

The DirectLakeBehavior property applies to Direct Lake on SQL analytics endpoints. Automatic (the default) silently falls back to DirectQuery when Direct Lake conditions aren't met, which suits production. DirectLakeOnly makes such queries fail with an error, so you can identify and resolve problems during development. DirectQueryOnly always uses DirectQuery, for measuring fallback performance. You set it in the model's Properties pane or through TOM or TMSL.

Question 3 · choose 1

Report visuals over a Direct Lake on SQL semantic model with 30 tables are slower than expected, and you suspect that some tables fall back to DirectQuery. You want to see, for every table at once, whether it uses Direct Lake and, if not, the reason. What should you run?

  1. APerformance Analyzer on each report page
  2. BThe DAX query EVALUATE INFO.STORAGETABLECOLUMNSEGMENTS()
  3. CThe DAX query EVALUATE TABLETRAITS(), reading DirectLakeFallbackInfo
  4. DA refresh, with the model's Refresh history open on the Direct Lake tab
Show the answer and why
  • APerformance Analyzer on each report page

    Incorrect

    Performance Analyzer shows visual and query durations; it doesn't list every table's fallback state and reason.

  • BThe DAX query EVALUATE INFO.STORAGETABLECOLUMNSEGMENTS()

    Incorrect

    It shows column segments and their memory residency, which helps analyze framing, not each table's fallback reason.

  • CThe DAX query EVALUATE TABLETRAITS(), reading DirectLakeFallbackInfo

    Correct

    TABLETRAITS returns a row per table, and DirectLakeFallbackInfo shows the fallback reason, or None when Direct Lake is used.

  • DA refresh, with the model's Refresh history open on the Direct Lake tab

    Incorrect

    That tab lists Direct Lake refresh failures, not the per-table fallback reason of queries.

To identify which tables fall back to DirectQuery and why, run the DAX query EVALUATE TABLETRAITS(). Its DirectLakeFallbackInfo column shows the reason for each table, and None means the table uses Direct Lake mode. Common causes include unframed tables, tables based on SQL views, SQL security at the endpoint, guardrails exceeded and memory pressure, each with its own fix.

Question 4 · choose 1

A developer writes Sales Red = VAR S = [Sales] RETURN CALCULATE(S, Product[Color] = "Red"). In a matrix by product category, every row shows the category's total sales for all colors, not the red sales. Why?

  1. AS is computed before CALCULATE applies the filter, and a variable's value never changes
  2. BCALCULATE can't take a Boolean filter on a column from another table
  3. CVariables are evaluated once per query, so S holds the grand total of all categories
  4. DRETURN must reference the measure directly; variables can't be used as CALCULATE's first argument
Show the answer and why
  • AS is computed before CALCULATE applies the filter, and a variable's value never changes

    Correct

    Variables are evaluated outside the filters that the RETURN expression applies, so S keeps the all-colors value.

  • BCALCULATE can't take a Boolean filter on a column from another table

    Incorrect

    A filter such as Product[Color] = "Red" is valid in CALCULATE; the issue is that S is already computed.

  • CVariables are evaluated once per query, so S holds the grand total of all categories

    Incorrect

    The variable is evaluated in each cell's filter context; that's why each row shows its own category's total.

  • DRETURN must reference the measure directly; variables can't be used as CALCULATE's first argument

    Incorrect

    A variable can be used there, but its value is a constant, so the filter has no effect on it.

After DAX calculates a variable's value, it doesn't change, even when you reference the variable in another expression. Variables are evaluated outside the filters your RETURN expression applies, so wrapping a variable in CALCULATE doesn't recalculate it under the new filters. To get red sales, apply the filter to the measure itself, for example VAR S = CALCULATE([Sales], Product[Color] = "Red").

Question 5 · choose 1

A matrix shows Product[Category] and then 'Date'[Year] on rows, with [Sales] as values. A Prior Year Sales measure must return the previous year's sales of the same category, and blank for each category's first year rather than a value from another category. Which expression should the measure use?

  1. ACALCULATE([Sales], OFFSET(-1, ALLSELECTED(Product[Category], 'Date'[Year]), ORDERBY(Product[Category], ASC, 'Date'[Year], ASC)))
  2. BCALCULATE([Sales], OFFSET(-1, ALLSELECTED(Product[Category], 'Date'[Year]), ORDERBY('Date'[Year]), PARTITIONBY(Product[Category])))
  3. CCALCULATE([Sales], OFFSET(1, ALLSELECTED(Product[Category], 'Date'[Year]), ORDERBY('Date'[Year]), PARTITIONBY(Product[Category])))
  4. DCALCULATE([Sales], OFFSET(-1, ALLSELECTED(Product[Category], 'Date'[Year]), ORDERBY('Date'[Year], ASC), PARTITIONBY('Date'[Year])))
Show the answer and why
  • ACALCULATE([Sales], OFFSET(-1, ALLSELECTED(Product[Category], 'Date'[Year]), ORDERBY(Product[Category], ASC, 'Date'[Year], ASC)))

    Incorrect

    Without PARTITIONBY, the first year of a category reaches back to the last year of the previous category.

  • BCALCULATE([Sales], OFFSET(-1, ALLSELECTED(Product[Category], 'Date'[Year]), ORDERBY('Date'[Year]), PARTITIONBY(Product[Category])))

    Correct

    PARTITIONBY restarts the order for each category, so the first year of a category has no previous row and returns blank.

  • CCALCULATE([Sales], OFFSET(1, ALLSELECTED(Product[Category], 'Date'[Year]), ORDERBY('Date'[Year]), PARTITIONBY(Product[Category])))

    Incorrect

    A delta of 1 moves to the following row, which is next year's sales, not last year's.

  • DCALCULATE([Sales], OFFSET(-1, ALLSELECTED(Product[Category], 'Date'[Year]), ORDERBY('Date'[Year], ASC), PARTITIONBY('Date'[Year])))

    Incorrect

    Partitioning by year compares categories within a year instead of years within a category.

OFFSET returns a single row positioned before (negative delta) or after (positive delta) the current row within a relation, sorted by ORDERBY and split into partitions by PARTITIONBY. When the delta shifts to a row that doesn't exist within the partition, OFFSET returns an empty table, so the measure is blank for a category's first year. Without a partition, the whole relation is treated as one partition.

Question 6 · choose 1

A model has two calculation groups: Time Intelligence, with a YTD item, and Currency Conversion, whose item multiplies SELECTEDMEASURE() by an exchange rate. When readers pick YTD and EUR, the result must be the year-to-date total of the daily converted amounts, and the current results show a different order of evaluation. What should you change?

  1. AThe precedence of the two calculation groups
  2. BThe ordinal values of the YTD and EUR calculation items
  3. CA selection expression on Currency Conversion for when no currency is selected
  4. DA dynamic format string on the EUR calculation item
Show the answer and why
  • AThe precedence of the two calculation groups

    Correct

    With several calculation groups, precedence decides the order in which they're applied to the measure.

  • BThe ordinal values of the YTD and EUR calculation items

    Incorrect

    Ordinals sort the items within a group for display; they don't change the order in which groups evaluate.

  • CA selection expression on Currency Conversion for when no currency is selected

    Incorrect

    Selection expressions control results for multiple, invalid or no selections, not the order of two selected items.

  • DA dynamic format string on the EUR calculation item

    Incorrect

    A format string changes how the result is displayed, not how the two calculations combine.

When a model has more than one calculation group, the precedence property specifies the order in which they apply to a measure, which matters when the calculations don't commute, such as time intelligence over a currency conversion. You set it in the Calculation groups section of the Properties pane. Ordinal sorts items within a group, selection expressions handle multiple, invalid or no selections, and dynamic format strings change formatting.

Question 7 · choose 1

A Sales measure must show amounts with the currency symbol of the country selected in a slicer, such as € or ¥. A first attempt wraps the value in FORMAT with a format string looked up from a currency table: the numbers look right in a table visual, but the column chart over the measure no longer plots them. Charts must work, and the formatting must still follow the slicer. What should you do?

  1. AKeep FORMAT and change the measure's data type to Decimal number in Measure tools
  2. BCreate a calculation group with one item per currency
  3. CReturn the number and move the currency lookup into a dynamic format string
  4. DTurn the currency table into a field parameter
Show the answer and why
  • AKeep FORMAT and change the measure's data type to Decimal number in Measure tools

    Incorrect

    FORMAT returns text whatever data type you set, so the chart still has no numeric values to plot.

  • BCreate a calculation group with one item per currency

    Incorrect

    A calculation group could switch formats, but it applies to every measure and adds a slicer for the items; the requirement is one measure following the country slicer.

  • CReturn the number and move the currency lookup into a dynamic format string

    Correct

    A dynamic format string formats the measure by context while the measure keeps its numeric data type.

  • DTurn the currency table into a field parameter

    Incorrect

    Field parameters switch which fields a visual shows; they don't format a measure's values.

FORMAT returns all results as strings, even for numeric values, which causes problems with visuals such as charts that need numbers. With a dynamic format string for a measure, you write a separate DAX expression that returns the format string, for example from a table of currency format strings, and the measure keeps its data type. Calculation groups also support dynamic format strings, but their scope is every measure they're applied to.

Question 8 · choose 1

A composite model keeps a 3-billion-row Sales table in DirectQuery against a warehouse. An Import table, Sales Agg, holds SalesAmount summed by DateKey, CustomerKey and ProductKey and is mapped in Manage aggregations, and the dimension tables use Dual storage mode. Sales by month and by customer segment hit the aggregation, but a Customers measure, DISTINCTCOUNT(Sales[CustomerKey]), always queries the warehouse. There are about 3 million customers. What should you change so that the measure can use Sales Agg?

  1. ASet the Customer table to Import storage mode
  2. BAdd a GroupBy entry for CustomerKey in Manage aggregations
  3. CAdd a Count table rows entry for Sales in Manage aggregations
  4. DSet the Sales Agg table to DirectQuery storage mode
Show the answer and why
  • ASet the Customer table to Import storage mode

    Incorrect

    Sums grouped by customer attributes already hit the aggregation, so the dimension's storage mode isn't what's missing for the distinct count.

  • BAdd a GroupBy entry for CustomerKey in Manage aggregations

    Correct

    With relationship-based aggregations, GroupBy entries matter only for DISTINCTCOUNT: a CustomerKey entry keeps the keys distinct in Sales Agg.

  • CAdd a Count table rows entry for Sales in Manage aggregations

    Incorrect

    Count table rows lets COUNTROWS queries hit the aggregation; it doesn't keep the customer keys distinct.

  • DSet the Sales Agg table to DirectQuery storage mode

    Incorrect

    Once an aggregation table is set to Import, its storage mode can't be changed again, and DirectQuery wouldn't make distinct counts hit it.

In relationship-based aggregations, GroupBy entries are optional and mostly there for readability: without them, aggregations are still hit through the relationships. DISTINCTCOUNT is the exception. A GroupBy entry for CustomerKey maintains the distinctness of CustomerKey in the aggregation table, so a distinct count of customers can be answered from it. This suits billions of detail rows with a few million distinct values; beyond about 2 to 5 million distinct values, performance can suffer.

Question 9 · choose 1

In an import model, the Employee table has EmployeeID, ManagerID and Email columns for 4,000 employees in a hierarchy up to eight levels deep, and Sales relates to Employee on EmployeeID. Each manager must see the sales of every employee who reports to them directly or indirectly, plus their own, and the hierarchy changes weekly. Which RLS filter on Employee meets this?

  1. APATHCONTAINS(PATH(Employee[EmployeeID], Employee[ManagerID]), LOOKUPVALUE(Employee[ManagerID], Employee[Email], USERPRINCIPALNAME()))
  2. BEmployee[ManagerID] = LOOKUPVALUE(Employee[EmployeeID], Employee[Email], USERPRINCIPALNAME())
  3. CPATHCONTAINS(PATH(Employee[EmployeeID], Employee[ManagerID]), USERPRINCIPALNAME())
  4. DPATHCONTAINS(PATH(Employee[EmployeeID], Employee[ManagerID]), LOOKUPVALUE(Employee[EmployeeID], Employee[Email], USERPRINCIPALNAME()))
Show the answer and why
  • APATHCONTAINS(PATH(Employee[EmployeeID], Employee[ManagerID]), LOOKUPVALUE(Employee[ManagerID], Employee[Email], USERPRINCIPALNAME()))

    Incorrect

    It looks up the user's own manager, so each user would see that manager's whole subtree, peers included.

  • BEmployee[ManagerID] = LOOKUPVALUE(Employee[EmployeeID], Employee[Email], USERPRINCIPALNAME())

    Incorrect

    It keeps direct reports only, missing indirect reports and the manager's own rows.

  • CPATHCONTAINS(PATH(Employee[EmployeeID], Employee[ManagerID]), USERPRINCIPALNAME())

    Incorrect

    The path holds employee IDs, not email addresses, so the user's UPN never matches an item in it.

  • DPATHCONTAINS(PATH(Employee[EmployeeID], Employee[ManagerID]), LOOKUPVALUE(Employee[EmployeeID], Employee[Email], USERPRINCIPALNAME()))

    Correct

    PATH lists each row's chain of managers, and the row is kept when the signed-in user's ID is in that chain.

PATH returns a delimited string with the identifiers of all the parents of the current identifier, so for each employee it lists the chain of managers up to the top, including the employee. PATHCONTAINS returns TRUE when an item is in that path. Looking up the signed-in user's EmployeeID with USERPRINCIPALNAME and testing it against each row's path keeps every row in that manager's subtree. PATH and PATHCONTAINS aren't supported in RLS rules in DirectQuery mode.

Question 10 · choose 1

A developer creates a new semantic model with Direct Lake tables in Power BI Desktop by picking tables from a lakehouse in the OneLake catalog. Which kind of Direct Lake model does she get?

  1. ADirect Lake on SQL
  2. BA DirectQuery model over the SQL analytics endpoint
  3. CDirect Lake on OneLake
  4. DAn Import model with OneLake integration
Show the answer and why
  • ADirect Lake on SQL

    Incorrect

    Direct Lake on SQL models are created from the SQL analytics endpoint page, not from Desktop.

  • BA DirectQuery model over the SQL analytics endpoint

    Incorrect

    Picking Direct Lake tables creates Direct Lake tables, not DirectQuery tables.

  • CDirect Lake on OneLake

    Correct

    Creating a Direct Lake model in Power BI Desktop supports Direct Lake on OneLake.

  • DAn Import model with OneLake integration

    Incorrect

    OneLake integration exports Import tables to OneLake; it isn't how Desktop builds Direct Lake tables.

Direct Lake semantic models can be created in Power BI Desktop, from the OneLake catalog in the service, or from a SQL analytics endpoint's page. Creation from Power BI Desktop and from the service supports Direct Lake on OneLake only; from the SQL analytics endpoint page you can choose Direct Lake on OneLake or Direct Lake on SQL.

Question 11 · choose 1

A semantic model has an OLS role that hides the Salary column. Members of that role who hold the Contributor role in the workspace still see Salary, while Viewers in the role don't. Why?

  1. AOLS applies only to Viewers; editors of the model aren't restricted
  2. BOLS applies only in Power BI Desktop and isn't enforced for reports in the service
  3. CThe Contributors opened the report from a different workspace
  4. DOLS rules take a day to reach Contributors
Show the answer and why
  • AOLS applies only to Viewers; editors of the model aren't restricted

    Correct

    Workspace roles above Viewer have Edit permission on the semantic model, so OLS doesn't apply to them.

  • BOLS applies only in Power BI Desktop and isn't enforced for reports in the service

    Incorrect

    OLS is enforced in the service for users it applies to, which is why the Viewers don't see Salary.

  • CThe Contributors opened the report from a different workspace

    Incorrect

    The report's location doesn't change whether OLS applies to a user.

  • DOLS rules take a day to reach Contributors

    Incorrect

    There's no delay by role; OLS simply doesn't apply to editors of the model.

Object-level security secures tables or columns from report viewers, and for users without permission it's as if the objects don't exist. OLS only applies to Viewers in a workspace: members of the Admin, Member or Contributor roles have Edit permission on the semantic model, so OLS doesn't apply to them. OLS is defined in model roles, for example with TMDL view or Tabular Editor.

Question 12 · choose 1

A matrix shows Product[Product] on rows, and a Position measure is defined as ROWNUMBER(ALLSELECTED(Product[Product]), ORDERBY([Sales], DESC)). Two products have exactly the same sales. What does Position return for those two products?

  1. AThe same number for both, with the next product's number skipping one
  2. BTwo consecutive numbers, with the tie broken by the product column
  3. CBLANK for both products
  4. DAn error, because the ORDERBY values tie at runtime
Show the answer and why
  • AThe same number for both, with the next product's number skipping one

    Incorrect

    ROWNUMBER returns a unique number for every row, so tied rows don't share a number.

  • BTwo consecutive numbers, with the tie broken by the product column

    Correct

    ROWNUMBER appends the fewest columns needed to make every row unique, here Product[Product], to its ORDERBY.

  • CBLANK for both products

    Incorrect

    ROWNUMBER is blank only when the current row can't be matched in the relation.

  • DAn error, because the ORDERBY values tie at runtime

    Incorrect

    An error is returned only when no additional columns can make every row unique; here Product[Product] can.

ROWNUMBER returns the unique ranking for the current context within the specified partition, sorted by the specified order, and returns blank if the current row can't be matched. If the orderBy and partitionBy columns can't uniquely identify every row in the relation, ROWNUMBER finds the least number of additional columns that do and appends them to orderBy; if no such columns exist and it detects a tie at runtime, it returns an error.

Question 13 · choose 1

A diagnostic measure must return TRUE whenever Product[Category] is affected by any filter, whether the filter is on Category itself, on another Product column such as Color, or on a related table such as Sales. Which DAX function returns that?

  1. AISFILTERED(Product[Category])
  2. BISEMPTY(Product)
  3. CHASONEVALUE(Product[Category])
  4. DISCROSSFILTERED(Product[Category])
Show the answer and why
  • AISFILTERED(Product[Category])

    Incorrect

    ISFILTERED returns TRUE only when the column itself is filtered directly.

  • BISEMPTY(Product)

    Incorrect

    ISEMPTY tests whether a table expression returns no rows.

  • CHASONEVALUE(Product[Category])

    Incorrect

    HASONEVALUE tests whether exactly one distinct value remains, not whether a filter elsewhere affects the column.

  • DISCROSSFILTERED(Product[Category])

    Correct

    A column is cross-filtered when a filter applies to it, to any column of its table or to any column of a related table.

ISCROSSFILTERED returns TRUE when the specified column or table is cross-filtered, that is, when a filter is applied to the column, to any column of its table, or to any column of a related table. ISFILTERED returns TRUE only for direct filters on the column, and HASONEVALUE checks whether one distinct value remains. Neither ISCROSSFILTERED nor ISFILTERED is supported in DirectQuery mode in calculated columns or RLS rules.

Practice domain 3 →Practice all domains →