Model Fields Measures
Change a measure's context
Use EVAL and its modifiers to work out a measure for the grand total, another member or another period, beside the rows you show.
A measure’s context is its grouping and its filters. EVAL works out a measure in a context that you set, for one value. The rows the query returns stay the same. So one query can show each region’s orders beside the orders of the whole shop, the region’s web orders or the month before.
How it works
Each EVAL has its own context, and it changes only its own value. A modifier inside the EVAL says how its context differs from the row’s context. This query shows Orders by region, and beside it one EVAL of Orders for each modifier:
SELECT shop.region, shop.count_distinct__order_id, MEASURE('all_orders', EVAL(shop.count_distinct__order_id, CLEAR())) AS "CLEAR", MEASURE('without_region', EVAL(shop.count_distinct__order_id, REMOVE(shop.region))) AS "REMOVE", MEASURE('web_orders', EVAL(shop.count_distinct__order_id, SET(shop.channel = 'web'))) AS "SET", MEASURE('all_web_orders', EVAL(shop.count_distinct__order_id, SET_CUSTOM(shop.channel = 'web'))) AS "SET_CUSTOM"FROM workspace.shop AS shopGROUP BY shop.regionORDER BY shop.region| Region | Orders | CLEAR | REMOVE | SET | SET_CUSTOM |
|---|---|---|---|---|---|
| AMER | 728 | 3,000 | 3,000 | 160 | 706 |
| APAC | 671 | 3,000 | 3,000 | 170 | 706 |
| EMEA | 777 | 3,000 | 3,000 | 185 | 706 |
| LATAM | 760 | 3,000 | 3,000 | 174 | 706 |
| null | 64 | 3,000 | 3,000 | 17 | 706 |
The query still has one row per region. Only the numbers in the EVAL columns change. Each one has its own name from MEASURE. Two EVALs of Orders in one SELECT would both take the name of Orders.
The forms
| Form | The context it uses | Grouped by region, EMEA reads |
|---|---|---|
EVAL(m) | The row’s group and the query’s filters. | 777 |
EVAL(m, CLEAR()) | No grouping and none of the query’s filters: the grand total. | 3,000 |
EVAL(m, REMOVE(dim, ...)) | These dimensions taken out of the grouping and the filters. The other dimensions stay. | 3,000, with REMOVE(shop.region) |
EVAL(m, SET(dim = value)) | One dimension’s grouping and filter replaced by one value. The rest of the context stays. | 185, with SET(shop.channel = 'web') |
EVAL(m, SET_CUSTOM(condition)) | All of the grouping and filters replaced by one condition of your own. | 706, with SET_CUSTOM(shop.channel = 'web') |
CURRENT(dim) inside SET | The dimension’s value in the row being worked out, such as this row’s month. | Grouped by month, September 2026 reads August’s 145, with SET(shop.order_month = CURRENT(shop.order_month) - INTERVAL 1 MONTH) |
SET and SET_CUSTOM differ in what they keep. SET(shop.channel = 'web') keeps the region of the row, so EMEA reads its own 185 web orders. SET_CUSTOM(shop.channel = 'web') replaces the whole context, so every row reads the shop’s 706 web orders.
Examples
Share of all orders
What share of all orders does each region have?
SELECT shop.region, shop.count_distinct__order_id, MEASURE('all_orders', EVAL(shop.count_distinct__order_id, CLEAR())) AS "All Orders", MEASURE( 'share_of_all_orders', EVAL(shop.count_distinct__order_id) / NULLIF(EVAL(shop.count_distinct__order_id, CLEAR()), 0) ) AS "Share of All Orders" /* @format percent(1) */FROM workspace.shop AS shopGROUP BY shop.regionORDER BY shop.region| Region | Orders | All Orders | Share of All Orders |
|---|---|---|---|
| AMER | 728 | 3,000 | 24.3% |
| APAC | 671 | 3,000 | 22.4% |
| EMEA | 777 | 3,000 | 25.9% |
| LATAM | 760 | 3,000 | 25.3% |
| null | 64 | 3,000 | 2.1% |
The first EVAL follows the row’s region. The CLEAR() one is always the grand total of 3,000. The share is a measure, so a reader that groups it another way gets each group’s share of all orders.
Share within each region
What share of each region’s orders comes from each channel?
SELECT shop.region, shop.channel, shop.count_distinct__order_id, MEASURE('region_orders', EVAL(shop.count_distinct__order_id, REMOVE(shop.channel))) AS "Region Orders", MEASURE( 'share_of_region_orders', EVAL(shop.count_distinct__order_id) / NULLIF(EVAL(shop.count_distinct__order_id, REMOVE(shop.channel)), 0) ) AS "Share of Region Orders" /* @format percent(1) */FROM workspace.shop AS shopGROUP BY shop.region, shop.channelORDER BY shop.region, shop.channel| Region | Channel | Orders | Region Orders | Share of Region Orders |
|---|---|---|---|---|
| AMER | 191 | 728 | 26.2% | |
| AMER | paid | 197 | 728 | 27.1% |
| AMER | store | 180 | 728 | 24.7% |
| AMER | web | 160 | 728 | 22.0% |
| APAC | 178 | 671 | 26.5% | |
| APAC | paid | 149 | 671 | 22.2% |
| APAC | store | 174 | 671 | 25.9% |
| APAC | web | 170 | 671 | 25.3% |
The first 8 of 20 rows. REMOVE(shop.channel) takes the channel out of the grouping and keeps the region, so each row divides by its own region’s orders.
One member beside every row
How do the web orders of EMEA compare with each of its channels?
SELECT shop.region, shop.channel, shop.count_distinct__order_id, MEASURE('web_orders', EVAL(shop.count_distinct__order_id, SET(shop.channel = 'web'))) AS "Web Orders"FROM workspace.shop AS shopWHERE shop.region = 'EMEA'GROUP BY shop.region, shop.channelORDER BY shop.channel| Region | Channel | Orders | Web Orders |
|---|---|---|---|
| EMEA | 202 | 185 | |
| EMEA | paid | 204 | 185 |
| EMEA | store | 186 | 185 |
| EMEA | web | 185 | 185 |
SET replaces the channel of each row with web, and keeps the region and the filter on it. So every row reads EMEA’s 185 web orders.
A condition of your own
How many orders do EMEA and LATAM have together, beside each region?
SELECT shop.region, shop.count_distinct__order_id, MEASURE( 'emea_latam_orders', EVAL(shop.count_distinct__order_id, SET_CUSTOM(shop.region IN ('EMEA', 'LATAM'))) ) AS "EMEA and LATAM Orders"FROM workspace.shop AS shopGROUP BY shop.regionORDER BY shop.region| Region | Orders | EMEA and LATAM Orders |
|---|---|---|
| AMER | 728 | 1,537 |
| APAC | 671 | 1,537 |
| EMEA | 777 | 1,537 |
| LATAM | 760 | 1,537 |
| null | 64 | 1,537 |
SET takes one value, and SET_CUSTOM takes any condition on the measure’s dimensions. The condition replaces all of the grouping and all of the query’s filters, so every row reads 777 plus 760.
The previous month
How many orders did each month have, and how many did the month before have?
SELECT shop.order_month, shop.count_distinct__order_id, DIMENSION( 'orders_previous_month', EVAL( shop.count_distinct__order_id, SET(shop.order_month = CURRENT(shop.order_month) - INTERVAL 1 MONTH) ) ) AS "Orders in the Previous Month"FROM workspace.shop AS shopWHERE shop.order_month >= DATE '2026-06-01'GROUP BY shop.order_monthORDER BY shop.order_month| Order Month | Orders | Orders in the Previous Month |
|---|---|---|
| 2026-06-01 | 136 | 155 |
| 2026-07-01 | 137 | 136 |
| 2026-08-01 | 145 | 137 |
| 2026-09-01 | 137 | 145 |
CURRENT(shop.order_month) is the month of the row. The SET replaces the order month’s grouping and its filter. So June still finds May’s 155 orders, although the WHERE keeps June to September.
- The shifted value needs its own name. Orders and the
EVALof Orders would both take the namecount_distinct__order_id. The compiler refuses that query withduplicate_output_field. TheDIMENSIONdeclaration names the shifted value. Readers get it as a value fixed at this query’s grouping. - A number counts periods of the dimension. An order month is a date: the first day of the month.
CURRENT(shop.order_month) - 1is the month before, the same as- INTERVAL 1 MONTH. On a year, such asKATER__YEAR(shop.order_date),- 1is the year before. On a date with no grain,- 1is the day before. CURRENTneeds the dimension in the context. The query must group by the dimension, or filter it to one value.CURRENTworks in anEVALand inside aMEASURE. A reader of the measure cannot yet use aSETon a dimension that the same SELECT declares withDIMENSION. For a comparison that readers can work out again, see Period comparisons.
Reader filters and CLEAR()
What does each modifier do with the query’s own filter?
SELECT shop.region, shop.count_distinct__order_id, MEASURE('cleared_orders', EVAL(shop.count_distinct__order_id, CLEAR())) AS "Orders with CLEAR", MEASURE('removed_orders', EVAL(shop.count_distinct__order_id, REMOVE(shop.region))) AS "Orders with REMOVE"FROM workspace.shop AS shopWHERE shop.channel = 'web'GROUP BY shop.regionORDER BY shop.region| Region | Orders | Orders with CLEAR | Orders with REMOVE |
|---|---|---|---|
| AMER | 160 | 3,000 | 706 |
| APAC | 170 | 3,000 | 706 |
| EMEA | 185 | 3,000 | 706 |
| LATAM | 174 | 3,000 | 706 |
| null | 17 | 3,000 | 706 |
CLEAR() drops the grouping and the query’s filter, so it counts all 3,000 orders. REMOVE(shop.region) drops only the region, so it keeps the filter and counts the 706 web orders. For a share of the filtered total, use REMOVE on the grouping. REMOVE also drops the filter on a dimension it names: here, REMOVE(shop.channel) would read each region’s orders in every channel.
The definition still applies
Suppose the orders Kater dataset keeps only the orders that have a region:
FROM shop.sales.orders AS ordersWHERE REQUIRED_FILTER('is_not_null__region', KATER__IS_NOT_NULL(orders.region), label='Region is set')A query of Orders by channel, with the grand total beside it:
SELECT shop.channel, shop.count_distinct__order_id, MEASURE('cleared_orders', EVAL(shop.count_distinct__order_id, CLEAR())) AS "Orders with CLEAR"FROM workspace.shop AS shopGROUP BY shop.channelORDER BY shop.channel| Channel | Orders | Orders with CLEAR |
|---|---|---|
| 757 | 2,936 | |
| paid | 751 | 2,936 |
| store | 739 | 2,936 |
| web | 689 | 2,936 |
CLEAR() drops the query’s grouping and filters, but not the measure’s definition. The WHERE and the required filters of the model that declares the measure, and its joins, stay in force under every modifier. So do the required filters of each model that passes the measure on, such as a semantic model’s. So the grand total is 2,936, the orders that have a region.
Calculate at
In a query, the field menu of a measure that the query reads from its source has Calculate at. It offers Ignore Grouping By, which works the measure out without some of the query’s grouping. Its label names the dimension it ignores, such as “Orders (Across Channel)”.
- In a query grouped by region and channel, right-click Orders in the field list.
- Open Calculate at and select Ignore Grouping By.
- The New measure dialog opens on Ignoring. Under Settings, select Channel.
- Select Create measure.
SELECT shop.region, shop.channel, shop.count_distinct__order_id, MEASURE( 'across_channel__count_distinct__order_id', KATER__IGNORE_GROUPING_BY(shop.count_distinct__order_id, shop.channel) ) AS "Orders (Across Channel)"FROM workspace.shop AS shopWHERE shop.region = 'EMEA'GROUP BY shop.region, shop.channelORDER BY shop.channel| Region | Channel | Orders | Orders (Across Channel) |
|---|---|---|---|
| EMEA | 202 | 777 | |
| EMEA | paid | 204 | 777 |
| EMEA | store | 186 | 777 |
| EMEA | web | 185 | 777 |
The compiler turns KATER__IGNORE_GROUPING_BY(m, dim, ...) into EVAL(m, REMOVE(dim, ...)), so each row reads EMEA’s 777 orders in every channel. The menu declares it as a MEASURE, so a query that reads this one works it out again at its own grouping.
Rules
- One kind of modifier in each
EVAL. Use oneCLEAR(), oneREMOVE, oneSET_CUSTOM, or one or moreSETs. SeveralSETs apply in order, and each one sees the context the one before it set:SET(shop.region = 'EMEA'), SET(shop.channel = 'web')reads 185 on every row. Any other mix, such asREMOVEwithSET, orSETwithCLEAR(), is refused. CLEAR()also drops the query’s filters. To keep the filters, remove only the grouping withREMOVE.SETreplaces the filter on its own dimension. The other filters stay.SET_CUSTOMreplaces everything. Its condition names at least one of the measure’s dimensions. It cannot read a measure, an aggregate, a window or a parameter.- The definition still applies. The
WHEREof the model that declares the measure, its required filters and its joins stay in force under every modifier. So do the required filters of each model that passes the measure on. - A modifier cannot break the measure’s rules.
EVAL(shop.sum__amount, CLEAR())would add euros to dollars. The compiler refuses it: “Revenue cannot be added up across Currency, so CLEAR() cannot drop the grouping. SET Currency to one value instead.” See Measures you cannot add up. - A dimension takes no modifiers. An
EVALof a dimension is the same dimension, with the same name. - An alias does not rename an
EVAL.EVAL(m)andEVAL(m, CLEAR()) AS all_ordersin one SELECT are both named afterm. Declare the second with its own name, such asMEASURE('all_orders', EVAL(m, CLEAR())).
If you know another tool
| The idea | In Kater |
|---|---|
| A pivot table’s percent of the grand total | EVAL(m) / EVAL(m, CLEAR()) |
| A percent of the parent row’s total | EVAL(m) / EVAL(m, REMOVE(child_dimension)) |
| A calculation that ignores one dimension of the view | EVAL(m, REMOVE(dimension)) |
| A filtered measure, fixed in the model | EVAL(m, SET(dimension = value)), set in the query that needs it |
| The same period one year earlier | EVAL(m, SET(year = CURRENT(year) - 1)) |
Common errors
| Code | Cause | Fix |
|---|---|---|
invalid_field_evaluation | Two kinds of modifier in one EVAL, such as REMOVE(shop.region), SET(shop.channel = 'web'). The compiler says “After the field name, EVAL accepts one CLEAR(), one REMOVE(dimension, …), one SET_CUSTOM(condition), or one or more SET(dimension = value)”. | Split it into two EVALs, or use SETs only. |
dimension_evaluation_modifiers | A modifier on a dimension, such as EVAL(shop.region, CLEAR()). The compiler says “only measures accept them”. | Remove the extra EVAL arguments. |
duplicate_output_field | Two EVALs of the same measure as outputs of one SELECT. The compiler says “Two outputs are named ‘count_distinct__order_id’. An AS alias only labels an output”. | Declare one of them with its own name. |
not_additive_across | A modifier drops a dimension the measure cannot be added up across, such as CLEAR() on Revenue. | SET the currency to one value instead. |