Model Fields Dimensions
Dates and times
Group dates by day, week, month, quarter or year, and pull out parts such as the day of the week.
A date can group its rows by a period, such as its month. It can also give one part of itself, such as its day of the week. Kater has a function for each grain and each part, and it turns each one into your warehouse’s SQL. A grain keeps the date field. A part is a new field.
How it works
- A grain keeps the field.
KATER__DATE,KATER__WEEK,KATER__MONTH,KATER__QUARTERandKATER__YEARgive the first day of each period, as a date on every warehouse. So the grain is in the data.KATER__HOUR,KATER__MINUTE,KATER__SECONDandKATER__MILLISECONDdo the same for a timestamp, and give a timestamp.KATER__MONTH(shop.order_date)is still theorder_datefield, by month, with the label Order Date. The Timeframe menu writes grains. - A part is a new field.
KATER__DAY_OF_WEEK,KATER__DAY_OF_WEEK_INDEX,KATER__DAY_OF_MONTH,KATER__DAY_OF_QUARTER,KATER__DAY_OF_YEAR,KATER__WEEK_OF_YEAR,KATER__MONTH_NUM,KATER__MONTH_NAME,KATER__QUARTER_OF_YEAR,KATER__HOUR_OF_DAYandKATER__MINUTE_OF_HOUReach give a different value. Declare each one with its own name. The Derive menu writes parts. - A plain date has no time of day. The compiler refuses an hour, minute, second or millisecond grain on a date, and an hour or minute part.
Order O00002 of the sample shop is on 2025-07-25, a Friday, at 13:56:
| You write | You get | Kind |
|---|---|---|
KATER__MONTH(shop.order_date) | 2025-07-01 | A grain: still Order Date, by month |
KATER__DAY_OF_WEEK(shop.order_date) | Fri | A part: a new field |
Grains
| Function | Its period | Order Date 2025-07-25 gives | Ordered At 2025-07-25 13:56 gives |
|---|---|---|---|
KATER__DATE | Day | 2025-07-25 | 2025-07-25 |
KATER__WEEK | Week | 2025-07-21 | 2025-07-21 |
KATER__MONTH | Month | 2025-07-01 | 2025-07-01 |
KATER__QUARTER | Quarter | 2025-07-01 | 2025-07-01 |
KATER__YEAR | Year | 2025-01-01 | 2025-01-01 |
KATER__HOUR | Hour | Refused: a date has no time of day | 2025-07-25 13:00:00 |
KATER__MINUTE | Minute | Refused | 2025-07-25 13:56:00 |
KATER__SECOND | Second | Refused | 2025-07-25 13:56:00 |
KATER__MILLISECOND | Millisecond | Refused | 2025-07-25 13:56:00 |
- A week starts on Monday in the sample shop. 2025-07-21 is the Monday before the order. The warehouse decides the first day of the week.
- A date grain of a timestamp is a date.
KATER__WEEK(ordered_at)gives the date 2025-07-21, with no time. - The shop records times to the minute. So the second and millisecond grains give the same time as the minute grain.
- A
DATE_TRUNCwritten by hand keeps the field too. The Timeframe menu reopens it as a grain. But on DuckDB,DATE_TRUNC('month', shop.order_date)gives a timestamp, such as 2026-07-01T00:00:00.KATER__MONTHgives a date on every warehouse.
Parts
| Function | Derive menu item | Gives | Order O00002 gives | Format |
|---|---|---|---|---|
KATER__DAY_OF_WEEK | Day of Week | The day’s name | Fri | None: it is text |
KATER__DAY_OF_WEEK_INDEX | Day of Week Index | The day’s number in its week. On DuckDB, Sunday is 0 and Saturday is 6. | 5 | plain |
KATER__DAY_OF_MONTH | Day of Month | 1 to 31 | 25 | plain |
KATER__DAY_OF_QUARTER | Day of Quarter | The day of its quarter, from 1 | 25 | plain |
KATER__DAY_OF_YEAR | Day of Year | 1 to 366 | 206 | plain |
KATER__WEEK_OF_YEAR | Week of Year | 1 to 53 | 30 | plain |
KATER__MONTH_NUM | Month Number | 1 to 12 | 7 | plain |
KATER__MONTH_NAME | Month Name | The month’s name | Jul | None: it is text |
KATER__QUARTER_OF_YEAR | Quarter of Year | 1 to 4 | 3 | plain |
KATER__HOUR_OF_DAY | Hour of Day | 0 to 23. A timestamp or a time only. | 13, from Ordered At | plain |
KATER__MINUTE_OF_HOUR | Minute of Hour | 0 to 59. A timestamp or a time only. | 56, from Ordered At | plain |
The Derive menu writes the format. plain shows a number as written, so 2026 shows as 2026, not 2,026. A name has no format, because a format does not change text. The menu also writes the name and the label, such as day_of_week__order_date and “Order Date (Day of Week)”.
Examples
Orders by month
How many orders did the shop get each month?
SELECT KATER__MONTH(shop.order_date), shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY KATER__MONTH(shop.order_date)ORDER BY 1| Order Date | Orders |
|---|---|
| 2025-01-01 | 141 |
| 2025-02-01 | 155 |
| 2025-03-01 | 133 |
| 2025-04-01 | 152 |
| 2025-05-01 | 143 |
| 2025-06-01 | 150 |
The first 6 of 21 rows, January 2025 to September 2026. Each row is the first day of its month. The column is still the Order Date field, so it keeps the field’s label.
Change the grain in place
How does the same count look week by week? Change the month to a week. The grain changes around the original date.
- In the query, right-click Order Date.
- Select Timeframe, then Week.
Kater writes KATER__WEEK in place of KATER__MONTH, in the SELECT and in the GROUP BY.
SELECT KATER__WEEK(shop.order_date), shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY KATER__WEEK(shop.order_date)ORDER BY 1| Order Date | Orders |
|---|---|
| 2024-12-30 | 24 |
| 2025-01-06 | 25 |
| 2025-01-13 | 34 |
| 2025-01-20 | 40 |
| 2025-01-27 | 28 |
| 2025-02-03 | 42 |
The first 6 of 92 rows. The first week starts on Monday 2024-12-30, before the first order. It holds the orders of 1 to 5 January 2025. The week reads each order’s own date. It never truncates a date that is already a month.
Orders by day of the week
On which day of the week do people order most? The orders dataset declares the day’s name with the Derive menu’s Day of Week:
DIMENSION('day_name__order_date', KATER__DAY_OF_WEEK(orders.order_date)) AS "Order Date Day Name",SELECT shop.day_name__order_date, shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY shop.day_name__order_dateORDER BY 1| Order Date Day Name | Orders |
|---|---|
| Fri | 412 |
| Mon | 466 |
| Sat | 421 |
| Sun | 442 |
| Thu | 394 |
| Tue | 451 |
| Wed | 414 |
Monday has the most orders, 466. The names sort as text, from Fri to Wed, not in calendar order.
Days in calendar order
How do you sort the days from Sunday to Saturday? Add the day’s number, and sort by it.
SELECT shop.day_name__order_date, DIMENSION('day_of_week_index__order_date', KATER__DAY_OF_WEEK_INDEX(shop.order_date)) AS "Order Date (Day of Week Index)", shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY shop.day_name__order_date, 2ORDER BY 2| Order Date Day Name | Order Date (Day of Week Index) | Orders |
|---|---|---|
| Sun | 0 | 442 |
| Mon | 1 | 466 |
| Tue | 2 | 451 |
| Wed | 3 | 414 |
| Thu | 4 | 394 |
| Fri | 5 | 412 |
| Sat | 6 | 421 |
On DuckDB, the index starts at 0 on Sunday, so Sunday sorts first. A week from KATER__WEEK starts on Monday, so the two do not agree.
Months in calendar order
How many orders fall in each month of the year, from January to December?
SELECT shop.month_name__order_date, DIMENSION('month_num__order_date', KATER__MONTH_NUM(shop.order_date)) AS "Order Date (Month Number)", shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY shop.month_name__order_date, 2ORDER BY 2| Order Date Month Name | Order Date (Month Number) | Orders |
|---|---|---|
| Jan | 1 | 286 |
| Feb | 2 | 287 |
| Mar | 3 | 285 |
| Apr | 4 | 286 |
| May | 5 | 298 |
| Jun | 6 | 286 |
| Jul | 7 | 267 |
| Aug | 8 | 296 |
The first 8 of 12 rows. A month name puts all the years together: Jan counts January 2025 and January 2026. October, November and December count about half as many, 149, 154 and 124, because the data stops in September 2026.
Orders by hour of day
At what hour do people order? Ordered At is a timestamp, so it has an hour. This line in semantic.shop declares the hour, as Derive and Hour of Day write it:
-- @format plain DIMENSION('hour_of_day__ordered_at', KATER__HOUR_OF_DAY(o.ordered_at)) AS "Ordered At (Hour of Day)",SELECT shop.hour_of_day__ordered_at, shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY shop.hour_of_day__ordered_atORDER BY 1| Ordered At (Hour of Day) | Orders |
|---|---|
| 0 | 121 |
| 1 | 141 |
| 2 | 111 |
| 3 | 113 |
| 4 | 123 |
| 5 | 111 |
| 6 | 149 |
| 7 | 121 |
The first 8 of 24 rows. Hour 0 counts the orders from 00:00 to 00:59. The sample shop’s orders spread across all 24 hours.
Two grains of one date
Can one query show the year and the month of Order Date side by side? Both grains keep the order_date field, so one of them needs its own name.
SELECT KATER__YEAR(shop.order_date), KATER__MONTH(shop.order_date), shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY 1, 2The compiler refuses it: “Duplicate public field identifier ‘order_date’; declare a uniquely named variant”. Declare the year as a new field:
SELECT DIMENSION('order_year', KATER__YEAR(shop.order_date)) AS "Order Year", KATER__MONTH(shop.order_date), shop.count_distinct__order_idFROM workspace.shop AS shopGROUP BY 1, 2ORDER BY 2| Order Year | Order Date | Orders |
|---|---|---|
| 2025-01-01 | 2025-01-01 | 141 |
| 2025-01-01 | 2025-02-01 | 155 |
| 2025-01-01 | 2025-03-01 | 133 |
The first 3 of 21 rows. Order Year is the first day of each year, because a date grain is a date.
The field list writes this for you. In a query by month, open Order Date and click Year. Kater declares DIMENSION('order_date_year', KATER__YEAR(shop.order_date)) AS "Order Date (Year)", and Year is ticked under Order Date.
Periods since another date
KATER__PERIODS_SINCE counts whole periods from one date to another, such as the months since a customer’s first order. Cohort charts use it for each cohort’s age.
KATER__PERIODS_SINCE(date, since, 'unit')| Part | What it does | Example |
|---|---|---|
date | The date to count to. | shop.order_date |
since | The date to count from. It is another date field, a grain of a date, or a fixed day, such as a launch. | shop.order_month, '2025-05-01'::DATE |
'unit' | 'day', 'week', 'month', 'quarter' or 'year'. The Derive menu starts at 'month'. | 'week' |
Both dates count from the start of their period, so every warehouse counts the same boundaries. This query counts three orders from the first day of their month:
SELECT shop.order_id, shop.order_date, shop.order_month, DIMENSION('days_since__order_month', KATER__PERIODS_SINCE(shop.order_date, shop.order_month, 'day')) AS "Days", DIMENSION('weeks_since__order_month', KATER__PERIODS_SINCE(shop.order_date, shop.order_month, 'week')) AS "Weeks", DIMENSION('months_since__order_month', KATER__PERIODS_SINCE(shop.order_date, shop.order_month, 'month')) AS "Months"FROM workspace.shop AS shopWHERE shop.order_id IN ('O00002', 'O00000', 'O00001')| Order ID | Order Date | Order Month | Days | Weeks | Months |
|---|---|---|---|---|---|
| O00000 | 2025-05-26 | 2025-05-01 | 25 | 4 | 0 |
| O00001 | 2025-11-28 | 2025-11-01 | 27 | 4 | 0 |
| O00002 | 2025-07-25 | 2025-07-01 | 24 | 3 | 0 |
For O00000, 25 days are about three and a half weeks, but the count is 4 weeks. 2025-05-01 is in the week that starts on Monday 28 April, and 2025-05-26 is a Monday. The Derive menu offers Periods Since on a date only when there is another date to count from. It writes the format number(0).
In the editor
- Timeframe in a shared model. Right-click a date in the field list. Under Make a new field from it, select Timeframe, then Week, Month, Quarter or Year. Kater adds a new field at once, such as
week__order_date, “Order Date (Week)”. - Timeframes in the field list. In a query, a date in the field list opens to Raw, Week, Month, Quarter and Year, and a timestamp also has Hour and Day. Click one to add the date by that timeframe, and click it again to take it out. A field the model already publishes by month has Raw, Quarter and Year. See Queries.
- Timeframe in a query. Right-click the date, select Timeframe, then Year, Quarter, Month, Week or Day. Kater changes the grain in place.
- Derive. Right-click a date and select Derive. Each part opens the New dimension dialog. Turn it into names the part in plain words, such as Day of week or Month number. Select Add to the model in a shared model, or Create dimension in a query.
Rules
- A plain date has no time of day.
KATER__HOUR(shop.order_date)is refused: “KATER__HOUR: shop.order_date is a date, so it has no time of day. Use one of a date’s own, such as KATER__DATE or KATER__DAY_OF_WEEK.” - A monthly value has no daily detail. Order Month in the sample shop is already a month. A week or a day of it cannot bring back the dates inside the month. To see weeks, change the grain on Order Date itself.
- A SELECT holds a field once. Two grains of one date keep the same field. Declare all but one under their own names.
- A name sorts as text. Sort a day or a month name by its index or its number.
- A date filter is not a grain. To keep the last 3 months, use a date filter such as
KATER__IS_WITHIN_LAST. See Filters and parameters. - A period comparison is a custom calculation. To compare this month with last month, see Period comparisons.
Common errors
| Code | Cause | Fix |
|---|---|---|
invalid_kater_function | A time grain or part on a plain date, such as KATER__HOUR(shop.order_date). | Use a date’s own grain or part, or a timestamp such as Ordered At. |
invalid_kater_function | KATER__PERIODS_SINCE counts from a value that is not a date, such as the text '2025-05-01'. The compiler says: “KATER__PERIODS_SINCE: ‘2025-05-01’ is a string, so it has no calendar date. Count periods between two dates or timestamps.” | Count from a date field, or write a fixed day as a date, such as '2025-05-01'::DATE. |
duplicate_field_name | Two grains of one date in one SELECT. | Declare all but one under their own names. |