Skip to main content
On this page

Model Fields Dimensions

Groups, buckets and flags

Turn values into groups people can read: label maps, numeric buckets and yes-or-no flags.

Audience
Data teams
Prerequisites
  • A model you can edit

A raw value often has too many values, or values that are too fine, to group by. The Derive menu turns a field into a new dimension that people can read: a map of values to labels, numeric buckets, or a flag that is yes or no. Each one is a KATER__ call. Each one reopens in the menu’s form, so you can change its settings later.

How it works

Right-click a field and select Derive. Each item opens the New dimension dialog, filled in. The dialog writes a DIMENSION over a KATER__ call. It makes a name and a label from the field, such as bucket__amount and “Amount (Bucket)”. You can type your own under Name it.

OperationDerive menu itemTurn it intoThe call it writesOffered on
derive.mapMapRename valuesKATER__MAP(field, (value, 'label'), ..., default_label => 'Other')Text, true or false, and categorical numbers
derive.bucketBucketBandsKATER__BUCKET(field, [edges], labels => [...])Continuous numbers
derive.is_equal_toIs Equal ToIs exactlyKATER__IS_EQUAL_TO(field, value)Text, true or false, and categorical numbers
derive.is_one_ofIs One OfIs one ofKATER__IS_ONE_OF(field, value, value, ...)Text and categorical numbers
derive.text_containsContainsContainsKATER__TEXT_CONTAINS(field, 'text')Text
derive.is_nullIs EmptyIs emptyKATER__IS_NULL(field)A field that can be empty
derive.is_not_nullIs Not EmptyHas a valueKATER__IS_NOT_NULL(field)A field that can be empty
derive.is_greater_thanIs Greater ThanIs more than, or Is after on a dateKATER__IS_GREATER_THAN(field, value), or KATER__IS_AFTER on a dateContinuous numbers, dates and timestamps
derive.is_less_thanIs Less ThanIs less than, or Is before on a dateKATER__IS_LESS_THAN(field, value), or KATER__IS_BEFORE on a dateContinuous numbers, dates and timestamps
derive.is_betweenIs BetweenIs betweenKATER__IS_BETWEEN(field, low, high)Continuous numbers, dates and timestamps
derive.periods_sincePeriods SincePeriods sinceKATER__PERIODS_SINCE(field, since, 'unit')Dates and timestamps
derive.day_of_week and the other partsDay of Week and the othersDay of week and the othersKATER__DAY_OF_WEEK(field) and the othersDates and timestamps

The date parts and periods since are on Dates and times. Whether a number is categorical or continuous decides which items it offers. See Classification.

Map values to labels

How many orders come from Europe, the Americas and the rest of the world? semantic.shop maps each region to a group.

  1. Open semantic.shop in Shared models, and right-click Region in the field list.
  2. Under Make a new field from it, select Derive, then Map.
  3. The New dimension dialog opens on Manual. Start with is Region, and Turn it into is Rename values.
  4. Under Settings, in Mappings, select EMEA as the Value and type Europe as its Label. Select Add row, and map AMER to Americas.
  5. Set Default label to Rest of World.
  6. Under Name it, type Region Group as the Name and region_group as the SQL name.
  7. Select Add to the model.
modeling/kater_objects/semantic_models/shop.sql
DIMENSION(
'region_group',
KATER__MAP(o.region, ('EMEA', 'Europe'), ('AMER', 'Americas'), default_label => 'Rest of World')
) AS "Region Group",
workspaces/shop/query_contracts/orders_by_region_group.sql
SELECT
shop.region_group,
shop.region,
shop.count_distinct__order_id
FROM workspace.shop AS shop
GROUP BY
shop.region_group,
shop.region
ORDER BY
1,
2
Region GroupRegionOrders
AmericasAMER728
EuropeEMEA777
Rest of WorldAPAC671
Rest of WorldLATAM760
Rest of Worldnull64

Every region that no pair names gets the default label. That includes the 64 orders with no region: a missing value matches no pair, so it goes to Rest of World, not to null.

  • The default is Other. Leave out default_label, and every other value reads Other. A map of ('paid', 'Paid'), ('email', 'Email') over Channel gives Paid 762, Email 778 and Other 1,460.
  • A pair matches one value exactly. To group many values, write a pair for each one.
  • The labels are text. The new field holds text, and it is categorical.

Buckets

How do orders spread across small, medium and large amounts? semantic.shop cuts Amount at 25 and 100.

  1. Open semantic.shop in Shared models, and right-click Amount in the field list.
  2. Under Make a new field from it, select Derive, then Bucket.
  3. The New dimension dialog opens on Manual. Start with is Amount, and Turn it into is Bands.
  4. Under Settings, enter 25 and 100 in Cut at. Leave Band names empty, so Kater names the bands from the edges.
  5. Under Name it, type Amount Band as the Name and amount_band as the SQL name.
  6. Select Add to the model.
modeling/kater_objects/semantic_models/shop.sql
DIMENSION('amount_band', KATER__BUCKET(o.amount, [25, 100])) AS "Amount Band"
workspaces/shop/query_contracts/orders_by_amount_band.sql
SELECT
shop.amount_band,
shop.count_distinct__order_id,
MEASURE('min__amount', KATER__MIN(shop.amount)) AS "Smallest Amount",
MEASURE('max__amount', KATER__MAX(shop.amount)) AS "Largest Amount"
FROM workspace.shop AS shop
GROUP BY
shop.amount_band
ORDER BY
3
Amount BandOrdersSmallest AmountLargest Amount
< 2518812.5019.99
25 – 1001,35625.0099.95
≥ 1001,425100.00600.00
null31nullnull

Two edges make three bands. The shop has 201 orders of exactly 25.00 and 88 of exactly 100.00.

  • A value on an edge goes to the band above. Each band holds its lower edge, so 25.00 is in 25 – 100 and 100.00 is in ≥ 100.
  • A missing value stays missing. The 31 orders with no amount have no band, so they group under null.
  • Kater names the bands from the edges. The first band is < 25, the last is ≥ 100, and each band between reads 25 – 100.

Name the bands

To show your own names, give one name for each band, in order:

workspaces/shop/query_contracts/orders_by_amount_size.sql
SELECT
DIMENSION('amount_size', KATER__BUCKET(shop.amount, [25, 100], labels => ['Small', 'Medium', 'Large'])) AS "Amount Size",
shop.count_distinct__order_id
FROM workspace.shop AS shop
GROUP BY
1
ORDER BY
2
Amount SizeOrders
null31
Small188
Medium1,356
Large1,425

Two edges need three names. With two names, the compiler refuses the call: “KATER__BUCKET: 2 edges make 3 buckets, so labels takes 3 labels.”

Flags

A flag is a yes-or-no dimension: a condition on one field. It shows as Yes or No. When the field is empty, the flag is null, except for Is Empty and Is Not Empty.

FlagYes whenExample
KATER__IS_EQUAL_TO(field, value)The value is this one.KATER__IS_EQUAL_TO(shop.region, 'EMEA')
KATER__IS_ONE_OF(field, value, ...)The value is one of these.KATER__IS_ONE_OF(customers.country, 'US', 'CA')
KATER__TEXT_CONTAINS(field, 'text')The text holds these letters anywhere, in any letter case.KATER__TEXT_CONTAINS(orders.notes, 'gift')
KATER__IS_NULL(field)The field is empty.KATER__IS_NULL(shop.region)
KATER__IS_NOT_NULL(field)The field has a value.KATER__IS_NOT_NULL(o.order_id)
KATER__IS_GREATER_THAN(field, value)The value is more than this one.KATER__IS_GREATER_THAN(shop.amount, 100)
KATER__IS_LESS_THAN(field, value)The value is less than this one.KATER__IS_LESS_THAN(shop.amount, 25)
KATER__IS_BETWEEN(field, low, high)The value is between the two, both ends included.KATER__IS_BETWEEN(o.amount, 25, 100)

The Derive menu writes these. A filter has more conditions, such as KATER__IS_AT_LEAST, which the Derive menu does not offer. You can write KATER__IS_AT_LEAST as a flag by hand, as More than 100, or at least 100 does. See Filters and parameters for every condition.

Notes that mention a gift

How many orders ask for gift wrap? The orders dataset flags each order whose notes contain “gift”:

modeling/kater_objects/datasets/orders.sql
DIMENSION('text_contains_gift__notes', KATER__TEXT_CONTAINS(orders.notes, 'gift')) AS "Notes Contain gift",

Pass the flag through in semantic.shop, so the workspace can group by it:

modeling/kater_objects/semantic_models/shop.sql
o.text_contains_gift__notes,
workspaces/shop/query_contracts/gift_notes.sql
SELECT
shop.text_contains_gift__notes,
shop.count_distinct__order_id
FROM workspace.shop AS shop
GROUP BY
shop.text_contains_gift__notes
ORDER BY
2
Notes Contain giftOrders
Yes575
No1,181
null1,244

The 575 orders say “gift wrap please”. The 1,244 orders with no notes get null, not No, because there is no text to search.

Customers in the US or Canada

How many orders and customers are in the US or Canada? The customers dataset flags the two countries:

modeling/kater_objects/datasets/customers.sql
DIMENSION('is_one_of_us_ca__country', KATER__IS_ONE_OF(customers.country, 'US', 'CA')) AS "Country Is US or CA",

Pass it through in semantic.shop:

modeling/kater_objects/semantic_models/shop.sql
c.is_one_of_us_ca__country,
workspaces/shop/query_contracts/orders_in_us_or_canada.sql
SELECT
shop.is_one_of_us_ca__country,
shop.count_distinct__order_id,
shop.count_distinct__customer_id
FROM workspace.shop AS shop
GROUP BY
shop.is_one_of_us_ca__country
ORDER BY
2
Country Is US or CAOrdersCustomers
null31426
Yes89972
No1,787142

72 customers are in the US or Canada: 42 in the US and 30 in Canada. The 26 customers with no country have no flag.

One region, and no region

Which orders are from EMEA, and which have no region at all? Two flags in a query:

workspaces/shop/query_contracts/emea_and_no_region.sql
SELECT
DIMENSION('is_equal_to_emea__region', KATER__IS_EQUAL_TO(shop.region, 'EMEA')) AS "Region Is EMEA",
DIMENSION('is_null__region', KATER__IS_NULL(shop.region)) AS "Region Is Empty",
shop.count_distinct__order_id
FROM workspace.shop AS shop
GROUP BY
1,
2
ORDER BY
3
Region Is EMEARegion Is EmptyOrders
nullYes64
YesNo777
NoNo2,159

Region Is EMEA is null for the 64 orders with no region. Region Is Empty is never null: it says Yes for them.

More than 100, or at least 100

Do orders of exactly 100 count as large? Compare Is Greater Than with Is At Least.

workspaces/shop/query_contracts/amount_over_100.sql
SELECT
DIMENSION('is_greater_than_100__amount', KATER__IS_GREATER_THAN(shop.amount, 100)) AS "Amount Is Greater Than 100",
DIMENSION('is_at_least_100__amount', KATER__IS_AT_LEAST(shop.amount, 100)) AS "Amount Is At Least 100",
shop.count_distinct__order_id
FROM workspace.shop AS shop
GROUP BY
1,
2
ORDER BY
3
Amount Is Greater Than 100Amount Is At Least 100Orders
nullnull31
NoYes88
YesYes1,337
NoNo1,544

The 88 orders of exactly 100.00 are not greater than 100, but they are at least 100. Is At Least is not in the Derive menu, so this query writes it by hand.

Count a flag

How many orders in each region ask for gift wrap? The Aggregations menu on a flag offers Count True and Count False. Each one is a measure.

workspaces/shop/query_contracts/gift_notes_by_region.sql
SELECT
shop.region,
shop.count_distinct__order_id,
MEASURE('count_true__text_contains_gift__notes', KATER__COUNT_TRUE(shop.text_contains_gift__notes)) AS "Notes Contain gift (Count True)",
MEASURE('count_false__text_contains_gift__notes', KATER__COUNT_FALSE(shop.text_contains_gift__notes)) AS "Notes Contain gift (Count False)"
FROM workspace.shop AS shop
GROUP BY
shop.region
ORDER BY
2 DESC
RegionOrdersNotes Contain gift (Count True)Notes Contain gift (Count False)
EMEA777143293
LATAM760144295
AMER728153289
APAC671119274
null641630

Count True and Count False do not add up to Orders. An order with no notes has a null flag, which is neither true nor false. See Measures for the other aggregations.

By hand

When no operation writes what you need, write a CASE. The orders dataset declares Order Size that way:

modeling/kater_objects/datasets/orders.sql
DIMENSION('order_size', CASE WHEN orders.amount >= 100 THEN 'large' ELSE 'small' END) AS "Order Size",

Compare it with Amount Band, the bucket that cuts at the same 100:

workspaces/shop/query_contracts/order_size_and_amount_band.sql
SELECT
shop.order_size,
shop.amount_band,
shop.count_distinct__order_id
FROM workspace.shop AS shop
GROUP BY
shop.order_size,
shop.amount_band
ORDER BY
1,
2
Order SizeAmount BandOrders
large≥ 1001,425
small25 – 1001,356
small< 25188
smallnull31

Both put an order of 100.00 in the upper group. But the CASE sends the 31 orders with no amount to ELSE, so they are small. The bucket keeps them null.

Use the operation when one writes what you need:

  • It reopens in its form. The field’s menu opens the bucket with its edges and names, so you change a setting, not the SQL. A CASE written in the same shape as a bucket or a map reopens in that form too.
  • It handles missing values the same way every time. A bucket and a flag keep an empty value null. A map sends it to the default label.
  • It names and labels the field. The dialog writes a name such as group__region and a label such as “Region (Group)”, unless you type your own.

Rules

  • A derived field is a new field. It needs a name that no field in scope has.
  • A value on a bucket’s edge goes to the band above. Each band holds its lower edge.
  • A missing value has no band and no flag. A bucket and a flag give null for it. Is Empty and Is Not Empty always give Yes or No. A map gives its default label.
  • A map’s default is Other. Write default_label to set your own.
  • Contains ignores letter case. KATER__TEXT_CONTAINS(orders.notes, 'gift') matches Gift and GIFT too, anywhere in the text.

Common errors

CodeCauseFix
invalid_kater_functionlabels does not have one name for each band, such as two names for two edges.Give one name more than the number of edges.
field_name_in_scopeThe new field takes a name that is already in scope, such as region_group a second time.Give the new field another name.