Model Fields Dimensions
Groups, buckets and flags
Turn values into groups people can read: label maps, numeric buckets and yes-or-no flags.
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.
| Operation | Derive menu item | Turn it into | The call it writes | Offered on |
|---|---|---|---|---|
derive.map | Map | Rename values | KATER__MAP(field, (value, 'label'), ..., default_label => 'Other') | Text, true or false, and categorical numbers |
derive.bucket | Bucket | Bands | KATER__BUCKET(field, [edges], labels => [...]) | Continuous numbers |
derive.is_equal_to | Is Equal To | Is exactly | KATER__IS_EQUAL_TO(field, value) | Text, true or false, and categorical numbers |
derive.is_one_of | Is One Of | Is one of | KATER__IS_ONE_OF(field, value, value, ...) | Text and categorical numbers |
derive.text_contains | Contains | Contains | KATER__TEXT_CONTAINS(field, 'text') | Text |
derive.is_null | Is Empty | Is empty | KATER__IS_NULL(field) | A field that can be empty |
derive.is_not_null | Is Not Empty | Has a value | KATER__IS_NOT_NULL(field) | A field that can be empty |
derive.is_greater_than | Is Greater Than | Is more than, or Is after on a date | KATER__IS_GREATER_THAN(field, value), or KATER__IS_AFTER on a date | Continuous numbers, dates and timestamps |
derive.is_less_than | Is Less Than | Is less than, or Is before on a date | KATER__IS_LESS_THAN(field, value), or KATER__IS_BEFORE on a date | Continuous numbers, dates and timestamps |
derive.is_between | Is Between | Is between | KATER__IS_BETWEEN(field, low, high) | Continuous numbers, dates and timestamps |
derive.periods_since | Periods Since | Periods since | KATER__PERIODS_SINCE(field, since, 'unit') | Dates and timestamps |
derive.day_of_week and the other parts | Day of Week and the others | Day of week and the others | KATER__DAY_OF_WEEK(field) and the others | Dates 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.
- Open
semantic.shopin Shared models, and right-click Region in the field list. - Under Make a new field from it, select Derive, then Map.
- The New dimension dialog opens on Manual. Start with is Region, and Turn it into is Rename values.
- Under Settings, in Mappings, select EMEA as the Value and type Europe as its Label. Select Add row, and map AMER to Americas.
- Set Default label to Rest of World.
- Under Name it, type Region Group as the Name and
region_groupas the SQL name. - Select Add to the model.
DIMENSION( 'region_group', KATER__MAP(o.region, ('EMEA', 'Europe'), ('AMER', 'Americas'), default_label => 'Rest of World') ) AS "Region Group",SELECT shop.region_group, shop.region, shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY shop.region_group, shop.regionORDER BY 1, 2| Region Group | Region | Orders |
|---|---|---|
| Americas | AMER | 728 |
| Europe | EMEA | 777 |
| Rest of World | APAC | 671 |
| Rest of World | LATAM | 760 |
| Rest of World | null | 64 |
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.
- Open
semantic.shopin Shared models, and right-click Amount in the field list. - Under Make a new field from it, select Derive, then Bucket.
- The New dimension dialog opens on Manual. Start with is Amount, and Turn it into is Bands.
- Under Settings, enter 25 and 100 in Cut at. Leave Band names empty, so Kater names the bands from the edges.
- Under Name it, type Amount Band as the Name and
amount_bandas the SQL name. - Select Add to the model.
DIMENSION('amount_band', KATER__BUCKET(o.amount, [25, 100])) AS "Amount Band"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 shopGROUP BY shop.amount_bandORDER BY 3| Amount Band | Orders | Smallest Amount | Largest Amount |
|---|---|---|---|
| < 25 | 188 | 12.50 | 19.99 |
| 25 – 100 | 1,356 | 25.00 | 99.95 |
| ≥ 100 | 1,425 | 100.00 | 600.00 |
| null | 31 | null | null |
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 reads25 – 100.
Name the bands
To show your own names, give one name for each band, in order:
SELECT DIMENSION('amount_size', KATER__BUCKET(shop.amount, [25, 100], labels => ['Small', 'Medium', 'Large'])) AS "Amount Size", shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY 1ORDER BY 2| Amount Size | Orders |
|---|---|
| null | 31 |
| Small | 188 |
| Medium | 1,356 |
| Large | 1,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.
| Flag | Yes when | Example |
|---|---|---|
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”:
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:
o.text_contains_gift__notes,SELECT shop.text_contains_gift__notes, shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY shop.text_contains_gift__notesORDER BY 2| Notes Contain gift | Orders |
|---|---|
| Yes | 575 |
| No | 1,181 |
| null | 1,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:
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:
c.is_one_of_us_ca__country,SELECT shop.is_one_of_us_ca__country, shop.count_distinct__order_id, shop.count_distinct__customer_idFROM workspace.shop AS shopGROUP BY shop.is_one_of_us_ca__countryORDER BY 2| Country Is US or CA | Orders | Customers |
|---|---|---|
| null | 314 | 26 |
| Yes | 899 | 72 |
| No | 1,787 | 142 |
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:
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_idFROM workspace.shop AS shopGROUP BY 1, 2ORDER BY 3| Region Is EMEA | Region Is Empty | Orders |
|---|---|---|
| null | Yes | 64 |
| Yes | No | 777 |
| No | No | 2,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.
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_idFROM workspace.shop AS shopGROUP BY 1, 2ORDER BY 3| Amount Is Greater Than 100 | Amount Is At Least 100 | Orders |
|---|---|---|
| null | null | 31 |
| No | Yes | 88 |
| Yes | Yes | 1,337 |
| No | No | 1,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.
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 shopGROUP BY shop.regionORDER BY 2 DESC| Region | Orders | Notes Contain gift (Count True) | Notes Contain gift (Count False) |
|---|---|---|---|
| EMEA | 777 | 143 | 293 |
| LATAM | 760 | 144 | 295 |
| AMER | 728 | 153 | 289 |
| APAC | 671 | 119 | 274 |
| null | 64 | 16 | 30 |
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:
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:
SELECT shop.order_size, shop.amount_band, shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY shop.order_size, shop.amount_bandORDER BY 1, 2| Order Size | Amount Band | Orders |
|---|---|---|
| large | ≥ 100 | 1,425 |
| small | 25 – 100 | 1,356 |
| small | < 25 | 188 |
| small | null | 31 |
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
CASEwritten 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__regionand 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
nullfor 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_labelto set your own. - Contains ignores letter case.
KATER__TEXT_CONTAINS(orders.notes, 'gift')matches Gift and GIFT too, anywhere in the text.
Common errors
| Code | Cause | Fix |
|---|---|---|
invalid_kater_function | labels 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_scope | The new field takes a name that is already in scope, such as region_group a second time. | Give the new field another name. |