Event modeling
An event table records what users do: one row for every action a user took, at the moment they took it. Funnels, retention, journeys and segmentation in Mitzu are all built from these rows, so the shape of your event tables decides what you can ask.
This page covers the columns an event table needs, the three layouts Mitzu supports, and how to choose between them. Properties that describe a user or a company rather than a single event belong in dimension tables. Numbers your data team already calculates per period belong in metric tables. There is more background in this blog post.
The columns of an event table
| Column | Required | What it holds |
|---|---|---|
| User ID | Yes | The ID of the user who performed the event. Every count of users, every funnel and every retention cohort is keyed on it. |
| Event time | Yes | When the event happened. |
| Event name | Multi event tables only | The name of the event, for example page_viewed or checkout_completed. |
| Entity ID columns | No | The ID of another entity the event belongs to, such as the company, team or session. |
| Date partition | No | A date column the warehouse partitions the table by. Mitzu filters on it so queries only read the partitions they need. |
| Every other column | No | Event properties, which you can filter and break down by. |
Every event a user performs should be a single row. Mitzu counts rows, so a row that is duplicated by a load job is counted twice.
User ID
Use the same ID for a user in every event table and in the user dimension tables. Mitzu joins on it as is.
If your tracking writes an anonymous ID before sign-in and a user ID after it, resolve the two in the warehouse before Mitzu reads the table. A view that picks the resolved ID per row works well:
SELECT
coalesce(identity_map.user_id, events.anonymous_id) AS user_id,
events.*
FROM raw.events AS events
LEFT JOIN analytics.identity_map AS identity_map
ON identity_map.anonymous_id = events.anonymous_id
Event time
Use a timestamp column without a time zone, holding every event in the same zone, ideally UTC. Unix epoch numbers in seconds, milliseconds or microseconds also work on most warehouses. Where you can choose, use the client time corrected by the server clock, which most CDPs store as the event's timestamp. Supported data types has the recommended type for every role.
Entity ID columns
An event can belong to more than one thing: the user who clicked, the company they work for, the workspace they were in. Add an entity for each of these, then set its ID column on the event table. Mitzu can then:
- count unique companies (or any other entity) instead of unique users,
- join that entity's dimension tables, so you can filter and break down events by company properties such as industry or plan.
A column only needs to exist on the event tables where it makes sense. Events without a company ID simply have no company properties.
Event properties
Every column that isn't one of the roles above becomes an event property. Columns with nested types (JSON, MAP, VARIANT and STRUCT) are expanded: Mitzu reads the keys during indexing and lists each one as its own property, so context.page.url can be filtered like a plain column.
Use Ignore columns on the event table settings for columns that shouldn't show up as properties, such as internal load metadata or personal data you don't want analysts to filter on.
Three event table layouts
Mitzu supports three ways of laying out events. They can be mixed: a workspace can have single event tables from one source and a multi event table from another.
| Single event tables | Wide multi event table | Short multi event table | |
|---|---|---|---|
| Tables | One per event | One for all events | One for all events |
| Event name comes from | The table name | The event name column | The event name column |
| Properties live in | That table's columns | Columns shared by all events | A JSON, MAP or VARIANT column |
| Typical source | Segment and RudderStack warehouse loads, dbt models per event | Snowplow's events table, "one big table" dbt models | Exports from analytics tools, custom tracking pipelines |
| Main cost | Many tables to configure | Many sparse columns | Nested values are slower to read |
Single event tables
Each event has its own table, and the table name is the event name. A checkout_completed table holds one row per completed checkout:
| user_id | event_time | order_id | amount | currency |
|---|---|---|---|---|
| u_42 | 2026-09-01 10:14:03 | o_981 | 49.00 | EUR |
| u_17 | 2026-09-01 10:20:51 | o_982 | 120.00 | USD |
Each table needs a user ID column and an event time column. The other columns are properties of that event only, so each table stays narrow and easy to read.
CDPs such as Segment and RudderStack write events this way, so you can point Mitzu at their schema and add every table at once. Select the tables with Add all <N> tables on the event tables page, and set the user ID and event time column names once for all of them.
The same CDPs also write a combined table, such as tracks, with one row per event of every type. Add either the per-event tables or the combined table, not both, otherwise each event appears twice in the event list.
Wide multi event tables
All events share one table, told apart by an event name column. Each property gets its own column, and a column is empty for events that don't set it:
| user_id | event_time | event_name | page_url | order_id | amount |
|---|---|---|---|---|---|
| u_42 | 2026-09-01 10:12:40 | page_viewed | /pricing | ||
| u_42 | 2026-09-01 10:14:03 | checkout_completed | o_981 | 49.00 |
Every value of the event name column becomes an event in Mitzu. Mitzu also works out which properties belong to which event during indexing, so the amount property only shows up for events that set it.
A wide table often grows to 100+ columns, and every new property means a schema change. It is easiest to maintain when a dbt model or a similar job builds it.
Short multi event tables
All events share one table, and the properties of each event are stored together in a single JSON, MAP or VARIANT column:
| user_id | event_time | event_name | properties |
|---|---|---|---|
| u_42 | 2026-09-01 10:12:40 | page_viewed | {"page_url": "/pricing"} |
| u_42 | 2026-09-01 10:14:03 | checkout_completed | {"order_id": "o_981", "amount": 49.0} |
New properties need no schema change. Mitzu expands the keys of the properties column into separate event properties, just as it does with the columns of a wide table.
Reading values out of JSON, MAP or VARIANT columns is slower than reading plain columns. If a few properties are used in almost every insight, such as the plan or the platform, copy them into their own columns.
Which layout to choose
- Use the layout you already have. Mitzu works on raw data, so start with whatever your CDP or pipeline writes and remodel only when you hit a limit.
- Single event tables suit a moderate number of events with distinct properties, and they are the easiest to keep fast.
- A multi event table suits hundreds of event types, or event names that change often: a new event appears after the next indexing without anyone adding a table.
- Short over wide when properties change often. Wide over short when the property set is stable and query speed matters most.
Performance
Event tables are usually the largest tables Mitzu reads. These choices keep queries fast and warehouse costs down:
- Partition or cluster by event time. Every insight filters on a date range. Set the partition column as the Date partition column on the event table so Mitzu adds a filter the warehouse can prune on.
- Cluster multi event tables by event name. Most insights read only a few events out of hundreds.
- Keep the user ID cheap to compare. Joins and unique counts are on this column, so a plain string or integer is faster than a value pulled out of JSON.
- Materialize expensive views. A view is recomputed every time it is queried. If a view joins or unnests large tables, build it as a table or a materialized view instead.
- Optimize lakehouse tables. For Iceberg and Delta Lake, compact small files and keep table statistics current.
Performance settings covers the Mitzu side, such as event and entity sampling.
Checklist
- One row per event, without duplicates.
- A user ID that matches the user dimension tables and the other event tables.
- An event time column, in one time zone.
- An event name column on multi event tables.
- An ID column for each entity you want to count or describe, such as the company.
- A partition or cluster key on event time, set as the date partition column.
- Columns you don't want to expose listed under Ignore columns.
Next: describe users and companies with dimension tables, or add your event tables.