
VARIANT fields to store nested fan engagement: { team → season → events }.Working with sports organizations has taught us that no two teams run on the same calendar. Hockey seasons span October to April. Soccer kicks off in March and wraps up in October. Basketball might start in November and end in June. When you're managing customer data across multiple brands, leagues, and engagement windows, the concept of a "season" doesn't map neatly to a calendar year.
In previous roles, we experienced firsthand how these overlapping timelines made it difficult to produce a unified Customer 360 view. Every January, we found ourselves re-aggregating data, rebuilding logic, and struggling to answer basic questions like: who attended games last season? The problem was we mapped seasons to calendar years, and the moment January 1st arrived, our reports would prematurely flip to a new "season," even though most leagues were still mid-season. We were losing active context and misclassifying fan behaviour.
This time, we wanted to approach the problem differently. We put on our software developer hats and asked: how would we solve this if this were a versioned API or evolving data contract?
But then came more seasons. More teams. More engagement dimensions. Suddenly, what started as a tidy schema turned into a maintenance nightmare. Every new campaign or season meant schema changes, backfills, and more logic sprawl across dbt models. We needed a better approach, one that could scale across brands, seasons, and years without sacrificing data integrity while making it easy to query.
We embraced a software-driven data modelling mindset, thinking in terms of abstraction, contracts, and structure, rather than fixed schemas.
In software, developers often encapsulate complexity behind well-defined interfaces. Rather than expose every field directly, they define structured payloads, APIs, or objects that hold related information together.
We realized we could apply the same principles to our Customer 360 model. Instead of adding a column for every team/season combination, we would store a fan’s entire engagement history in a single VARIANT column called ticket_history. This semi-structured object holds a nested map of brands and seasons, like this:
In this format, the data is compact, complete, and history-preserving. No schema changes are needed when a new team or season is introduced.
For multi-brand sports organizations, this approach solves a very real problem: seasons don't align neatly with calendar years. Hockey may run from October to April, soccer from March to October, and basketball from November to June. Hardcoding logic that flips over on January 1st leads to confusion, incorrect aggregations, and misaligned reporting. By treating each brand and season independently inside a nested structure, we preserve fidelity to how the sports themselves actually operate.
Instead of trying to force a shared temporal structure across different leagues, we let each team define its own season keys. This approach also eliminates the need to restructure models annually, or to anticipate every combination of brand and season ahead of time.
These derived flags give marketing teams exactly what they need: easy segmentation, consistent naming, and precomputed logic, without duplicating the underlying data logic.
A particularly strong use case has been retention modelling for season ticket holders. Using this nested ticket_history structure, we've built machine learning-ready features that track a fan's year-over-year activity across brands without needing to maintain separate tables or join-heavy models. For example we will store all events attended, a single record like this:
...allows us to calculate features such as:
These features are then fed into our feature store for modelling churn risk, renewal likelihood, and cross-sell potential.
We’ve extended this to build a full feature store pipeline using our ticket_history and related fan-level data. For example, we generate season-level features that can power retention and renewal models.
These are then stored in a feature store with a schema like:
These features are aggregated per fan per brand per season and versioned in a feature store. They can be used by downstream models to understand behavioural changes, predict likelihood of renewal, and trigger proactive outreach.
We're also finding benefits of this software-driven data modelling approach beyond season alignment. When used thoughtfully, this structure improves not just fan targeting but multi-brand analytics, retention modelling, campaign impact measurement and reporting. Because all engagement history is consolidated into one flexible, queryable structure, we're no longer limited to a single team's view or a rigid year-by-year slice. We've applied this in our work with multi-team organizations and found it dramatically reduces the number of derived models, joins, and edge-case logic we used to carry season to season.
The shift toward software-driven data modelling isn’t just about structure. It’s structure, scalability, and usability. Get all three right, and your Customer 360 becomes a living product that grows with your organization.
Need help designing or optimizing your Customer 360 for sports or multi-brand environments? We’re here to help. Contact us to learn more.