Skip to main content
On this page

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.

Audience
Data teams
Prerequisites
  • A query you can edit
  • The basics of measures, on the Measures overview

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:

workspaces/shop/query_contracts/eval_modifiers.sql
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 shop
GROUP BY
shop.region
ORDER BY
shop.region
RegionOrdersCLEARREMOVESETSET_CUSTOM
AMER7283,0003,000160706
APAC6713,0003,000170706
EMEA7773,0003,000185706
LATAM7603,0003,000174706
null643,0003,00017706

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

FormThe context it usesGrouped 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 SETThe 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?

workspaces/shop/query_contracts/share_of_all_orders.sql
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 shop
GROUP BY
shop.region
ORDER BY
shop.region
RegionOrdersAll OrdersShare of All Orders
AMER7283,00024.3%
APAC6713,00022.4%
EMEA7773,00025.9%
LATAM7603,00025.3%
null643,0002.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?

workspaces/shop/query_contracts/share_of_region_orders.sql
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 shop
GROUP BY
shop.region,
shop.channel
ORDER BY
shop.region,
shop.channel
RegionChannelOrdersRegion OrdersShare of Region Orders
AMERemail19172826.2%
AMERpaid19772827.1%
AMERstore18072824.7%
AMERweb16072822.0%
APACemail17867126.5%
APACpaid14967122.2%
APACstore17467125.9%
APACweb17067125.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?

workspaces/shop/query_contracts/emea_web_orders.sql
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 shop
WHERE
shop.region = 'EMEA'
GROUP BY
shop.region,
shop.channel
ORDER BY
shop.channel
RegionChannelOrdersWeb Orders
EMEAemail202185
EMEApaid204185
EMEAstore186185
EMEAweb185185

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?

workspaces/shop/query_contracts/emea_latam_orders.sql
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 shop
GROUP BY
shop.region
ORDER BY
shop.region
RegionOrdersEMEA and LATAM Orders
AMER7281,537
APAC6711,537
EMEA7771,537
LATAM7601,537
null641,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?

workspaces/shop/query_contracts/orders_previous_month.sql
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 shop
WHERE
shop.order_month >= DATE '2026-06-01'
GROUP BY
shop.order_month
ORDER BY
shop.order_month
Order MonthOrdersOrders in the Previous Month
2026-06-01136155
2026-07-01137136
2026-08-01145137
2026-09-01137145

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 EVAL of Orders would both take the name count_distinct__order_id. The compiler refuses that query with duplicate_output_field. The DIMENSION declaration 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) - 1 is the month before, the same as - INTERVAL 1 MONTH. On a year, such as KATER__YEAR(shop.order_date), - 1 is the year before. On a date with no grain, - 1 is the day before.
  • CURRENT needs the dimension in the context. The query must group by the dimension, or filter it to one value.
  • CURRENT works in an EVAL and inside a MEASURE. A reader of the measure cannot yet use a SET on a dimension that the same SELECT declares with DIMENSION. 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?

workspaces/shop/query_contracts/orders_with_clear_and_remove.sql
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 shop
WHERE
shop.channel = 'web'
GROUP BY
shop.region
ORDER BY
shop.region
RegionOrdersOrders with CLEAROrders with REMOVE
AMER1603,000706
APAC1703,000706
EMEA1853,000706
LATAM1743,000706
null173,000706

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:

modeling/kater_objects/datasets/orders.sql
FROM shop.sales.orders AS orders
WHERE
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:

workspaces/shop/query_contracts/orders_by_channel.sql
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 shop
GROUP BY
shop.channel
ORDER BY
shop.channel
ChannelOrdersOrders with CLEAR
email7572,936
paid7512,936
store7392,936
web6892,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)”.

  1. In a query grouped by region and channel, right-click Orders in the field list.
  2. Open Calculate at and select Ignore Grouping By.
  3. The New measure dialog opens on Ignoring. Under Settings, select Channel.
  4. Select Create measure.
workspaces/shop/query_contracts/orders_across_channel.sql
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 shop
WHERE
shop.region = 'EMEA'
GROUP BY
shop.region,
shop.channel
ORDER BY
shop.channel
RegionChannelOrdersOrders (Across Channel)
EMEAemail202777
EMEApaid204777
EMEAstore186777
EMEAweb185777

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 one CLEAR(), one REMOVE, one SET_CUSTOM, or one or more SETs. Several SETs 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 as REMOVE with SET, or SET with CLEAR(), is refused.
  • CLEAR() also drops the query’s filters. To keep the filters, remove only the grouping with REMOVE.
  • SET replaces the filter on its own dimension. The other filters stay.
  • SET_CUSTOM replaces 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 WHERE of 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 EVAL of a dimension is the same dimension, with the same name.
  • An alias does not rename an EVAL. EVAL(m) and EVAL(m, CLEAR()) AS all_orders in one SELECT are both named after m. Declare the second with its own name, such as MEASURE('all_orders', EVAL(m, CLEAR())).

If you know another tool

The ideaIn Kater
A pivot table’s percent of the grand totalEVAL(m) / EVAL(m, CLEAR())
A percent of the parent row’s totalEVAL(m) / EVAL(m, REMOVE(child_dimension))
A calculation that ignores one dimension of the viewEVAL(m, REMOVE(dimension))
A filtered measure, fixed in the modelEVAL(m, SET(dimension = value)), set in the query that needs it
The same period one year earlierEVAL(m, SET(year = CURRENT(year) - 1))

Common errors

CodeCauseFix
invalid_field_evaluationTwo 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_modifiersA modifier on a dimension, such as EVAL(shop.region, CLEAR()). The compiler says “only measures accept them”.Remove the extra EVAL arguments.
duplicate_output_fieldTwo 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_acrossA modifier drops a dimension the measure cannot be added up across, such as CLEAR() on Revenue.SET the currency to one value instead.