Modeling Marketing Data
Clean, joined tables.
Modeling Marketing Data is a free Digital Marketing Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Digital Marketing Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Model at All
Raw connector tables are messy: inconsistent column names, mixed currencies, different grains, and platform-specific quirks. Querying them directly produces wrong, irreproducible numbers.
Data modeling is the discipline of turning raw rows into clean, consistent, business-ready tables. It is where ROAS, conversion, and revenue get defined once, correctly, so every report agrees.
The Star Schema
The dominant analytics model is the star schema: a central fact table of measurable events surrounded by dimension tables that describe them. Facts hold numbers (spend, clicks, revenue); dimensions hold context (campaign, date, channel, customer).
This shape is intuitive for marketers and efficient for BI tools, which join one fact to several dimensions to slice metrics by any attribute.
dim_date
|
dim_channel -- fct_ad_spend -- dim_campaign
|
dim_account
fct_ad_spend (facts): impressions, clicks, cost, conversions
dims: who / what / when contextFacts vs Dimensions
A fact table is long and additive: one row per event or per day-campaign, with numeric measures you sum. A dimension table is wide and descriptive: one row per campaign or customer, with attributes you filter and group by.
The test: if you would SUM it, it is a fact; if you would GROUP BY it, it is a dimension. Spend is a fact; campaign name is a dimension.
fct_ad_spend dim_campaign
---------------- ----------------
date campaign_id (PK)
campaign_id (FK) campaign_name
cost <-SUM-> channel
clicks <-SUM-> objective
conversions start_dateGrain: The First Decision
The grain is what one row of a fact table represents. Declaring it first is the single most important modeling decision. Mixing grains, daily rows with lifetime totals, double-counts and corrupts every metric downstream.
State the grain in plain language: one row per campaign per day. Every column must then be true at that grain, and every load must respect it.
Declared grain: one row per campaign per day
-- enforce uniqueness on the grain
SELECT date, campaign_id, COUNT(*)
FROM fct_ad_spend
GROUP BY 1,2
HAVING COUNT(*) > 1; -- must return 0 rowsStaging Models
Before facts and dimensions, build staging models: one per source table, renaming columns to a standard, casting types, and standardizing units (cents to dollars, UTC dates). One staging model maps one raw table, nothing more.
Staging is the cleaning layer. It isolates source quirks so your downstream marts never need to know that Meta calls it spend and Google calls it cost.
-- stg_google_ads__spend
SELECT
date AS spend_date,
campaign_id,
'google' AS channel,
cost_micros / 1000000 AS cost, -- micros -> dollars
clicks,
conversions
FROM raw.google_ads__campaign_stats;Unioning Channels
Each ad platform reports differently, but after staging they share a common shape. The next model unions them into one cross-channel spend fact, the foundation of blended reporting.
This single table is what makes total ROAS possible. With every channel conformed to the same columns, one query sums spend across Google, Meta, and TikTok at once.
-- fct_ad_spend: union all channels
SELECT * FROM stg_google_ads__spend
UNION ALL
SELECT * FROM stg_meta_ads__spend
UNION ALL
SELECT * FROM stg_tiktok_ads__spend;
-- now: SUM(cost) GROUP BY channel worksConformed Dimensions
For cross-channel analysis, dimensions must be conformed: a shared dim_date and dim_channel that every fact joins to identically. Then "revenue by month by channel" means the same thing whether the source is ads, email, or web.
Conformed dimensions are what let you place spend and revenue side by side in one chart. Without them, joins misalign and totals quietly disagree.
Conformed dims shared across facts:
dim_date -> joined by every fact on date
dim_channel -> 'google','meta','email','organic'
dim_campaign -> unified campaign keys
-> spend and revenue line up on the same axesAttribution in SQL
Attribution assigns credit for a conversion to touchpoints. Last-click is simplest: the final marketing source before conversion gets full credit. First-click, linear, and position-based spread credit differently.
In a warehouse you implement attribution as a model, not a platform black box. With GA4 event-level data you can window touchpoints per user and apply any rule, then compare results across models honestly.
-- last non-direct click per conversion
WITH touches AS (
SELECT user_id, channel, event_time,
ROW_NUMBER() OVER (PARTITION BY user_id
ORDER BY event_time DESC) AS rn
FROM web_touchpoints
WHERE channel <> 'direct'
)
SELECT channel, COUNT(*) FROM touches WHERE rn=1
GROUP BY 1;Slowly Changing Dimensions
Dimension attributes change over time: a campaign's budget owner moves, a customer's tier upgrades. A Type 2 slowly changing dimension keeps history by adding a new row with validity dates instead of overwriting.
This matters for accurate point-in-time reporting. To know which segment a customer was in when they converted, you need the version of the dimension that was valid then, not today's.
dim_customer (SCD Type 2)
cust_id tier valid_from valid_to is_current
101 free 2026-01-01 2026-04-01 false
101 pro 2026-04-01 9999-12-31 true
-- join on event_date BETWEEN valid_from AND valid_toTesting and Documentation
Models are code, so test them. Tools like dbt let you assert that keys are unique and not null, that channel values are within an accepted set, and that relationships between tables hold.
Tests catch schema drift and bad joins before they reach a dashboard. Paired with auto-generated documentation and lineage, they make the model trustworthy and onboardable instead of a fragile black box.
# dbt schema test
models:
- name: fct_ad_spend
columns:
- name: campaign_id
tests: [not_null]
- name: channel
tests:
- accepted_values:
values: ['google','meta','tiktok']Marts: The Final Layer
The top layer is marts: business-ready tables shaped for specific audiences, like a marketing_performance mart that already joins spend to revenue and computes ROAS per channel per day.
BI tools read only from marts. By pre-joining and pre-aggregating here, dashboards stay fast and cheap, and every analyst inherits the same correct definitions.
-- marts.marketing_performance (1 row / day / channel)
SELECT s.spend_date, s.channel,
SUM(s.cost) AS spend,
SUM(r.revenue) AS revenue,
SAFE_DIVIDE(SUM(r.revenue), SUM(s.cost)) AS roas
FROM fct_ad_spend s
LEFT JOIN fct_revenue r USING (spend_date, channel)
GROUP BY 1,2;Quick Check
You are building a fact table for ad performance and must avoid double-counting. What is the single most important thing to declare before writing any columns?
Recap
Modeling turns messy raw tables into trustworthy, business-ready data through layers: staging cleans and conforms each source, facts and conformed dimensions form a star schema, and marts pre-join everything for BI.
Declare the grain first, union channels for blended metrics, implement attribution and SCD Type 2 history in SQL, and test every model so wrong numbers fail loudly instead of reaching a dashboard.
Frequently asked questions
Is the “Modeling Marketing Data” lesson free?
Yes — the full text of “Modeling Marketing Data” is free to read here on the web, and the Digital Marketing Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Digital Marketing Academy course, upgrade to CoddyKit PRO.
What will I learn in “Modeling Marketing Data”?
Clean, joined tables. You practise Digital Marketing Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Digital Marketing Academy?
No prior experience is required. Digital Marketing Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Modeling Marketing Data” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Digital Marketing Academy lesson?
Yes. Every Digital Marketing Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Why a Warehouse
- ETL and Connectors
- Modeling Marketing Data
- Dashboards That Drive Action