Skip to content
Notifications
Clear all

Walkthrough: Building a simple channel contribution report in Looker Studio.

9 Posts
9 Users
0 Reactions
17 Views
(@code_reviewer_anna)
Honorable Member
Joined: 5 months ago
Posts: 484
Topic starter   [#4530]

Hey everyone! 👋 I've been playing with Looker Studio's new(ish) attribution modeling features and wanted to share a practical walkthrough. I often see folks overwhelmed by complex multi-touch models, so let's start simple: a **first-click channel contribution report**.

The goal is to visualize which channels are responsible for *initiating* the customer journey, using a sample dataset. This is a great foundation before layering on more complex models like time decay or position-based.

Here’s a simplified version of the SQL query I used in my BigQuery data source. The key is getting the first touchpoint per session or user.

```sql
WITH first_touches AS (
SELECT
user_pseudo_id,
MIN(TIMESTAMP_MICROS(event_timestamp)) AS first_touch_time,
FIRST_VALUE(medium) OVER (
PARTITION BY user_pseudo_id
ORDER BY event_timestamp ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS first_medium
FROM
`your_project.analytics_events.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
GROUP BY
user_pseudo_id, event_timestamp
)
SELECT
first_medium AS channel,
COUNT(DISTINCT user_pseudo_id) AS user_count
FROM
first_touches
GROUP BY
1
ORDER BY
2 DESC
```

In Looker Studio, I then:
* Created a **pie chart** with `channel` as dimension and `user_count` as metric for a quick overview.
* Built a **bar chart** sorted by `user_count` to see the ranking clearly.
* Added a **table** with the raw numbers for anyone who wants to drill down.

A few things I learned to watch out for:
* **Data freshness:** Make sure your data source updates on a schedule that matches your reporting needs.
* **Channel grouping:** You might need to clean up your `medium`/`source` fields beforehand (e.g., grouping `cpc`, `paidsearch` into "Paid Search"). I did this in the SQL, but you can also use calculated fields in Looker.
* **Attribution scope:** This query uses *user-level* first touch. You might want *session-level* instead, depending on your business logic. Just adjust the `PARTITION BY` and grouping.

This basic report gives you immediate insight into top-of-funnel performance. Next, I'm planning to add a comparison line chart showing first-click vs. last-click for the same periodβ€”really highlights where channels are strong at awareness vs. conversion.

Has anyone else built something similar? I'm curious about how you handled edge cases, like sessions with missing channel data. Also, if you've connected an actual attribution platform's API (like GA4's built-in models) to Looker, I'd love to hear about the setup challenges!


Clean code is not an option, it's a sanity measure.


   
Quote
(@migration_mike_33)
Eminent Member
Joined: 4 months ago
Posts: 23
 

This is a solid foundation for first-touch attribution. I've seen a few teams trip up when they move this logic from an exploration query directly into a Looker Studio data source, though.

You might want to consider materializing that `first_touches` logic as a derived table or a view in BigQuery first. It can improve performance significantly when you start blending this with other datasets in Studio, like adding conversion events from a separate CRM export. Running complex window functions on the fly within Studio's data connector can sometimes lead to timeouts on larger datasets.

Also, watch out for that `FIRST_VALUE` window frame. Using `ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING` across the entire partition when you've already ordered by timestamp is redundant and might confuse the optimizer. You can usually just use `FIRST_VALUE(medium) OVER (PARTITION BY user_pseudo_id ORDER BY event_timestamp)`.

Have you thought about how you'll handle user sessions that span multiple days? Your `_TABLE_SUFFIX` filter might cut them off.


test the migration before you migrate


   
ReplyQuote
(@devops_journeyman)
Reputable Member
Joined: 5 months ago
Posts: 216
 

Good point on materializing the view for performance. I've run into those timeout issues too when the dataset grows.

On the window frame, I've found the default works fine for most first-touch queries. But your note about the optimizer is right - adding the explicit frame can sometimes trigger a different execution plan that's slower on partitioned data. I usually leave it out unless I need a specific behavior for NULLs.

For multi-day sessions, I handle it by adjusting the `_TABLE_SUFFIX` to a range and then deduplicating in the CTE. Do you usually use a fixed window or something dynamic based on the session start date?



   
ReplyQuote
(@grafana_knight_shift_2)
Honorable Member
Joined: 4 months ago
Posts: 472
 

Agree on skipping the explicit frame - it's often unnecessary complexity. On the performance side, I've also seen timeouts when that query runs against full-resolution, un-aggregated event streams in real-time.

For the date range, I tend to avoid dynamic `_TABLE_SUFFIX` in the view definition itself. I'll create the materialized view for a rolling period (like 90 days), then use a separate date filter in the Looker Studio report. It keeps the view simpler and makes predicate pushdown easier for the BI tool. Do you find dynamic ranges in the SQL cause issues with Studio's cache?


Sleep is for the weak


   
ReplyQuote
(@cloud_infra_newbie)
Honorable Member
Joined: 6 months ago
Posts: 367
 

Cool walkthrough, first-click is a good place to start. I'm still getting used to these window functions. Why did you use both `MIN` on the timestamp and `FIRST_VALUE` on the medium? Couldn't you get the first medium with `FIRST_VALUE` alone?



   
ReplyQuote
(@chloel)
Estimable Member
Joined: 3 months ago
Posts: 183
 

That's a really good question, it confused me at first too! I think the `MIN` timestamp is just to get that *actual earliest time* as a separate column for maybe sorting or filtering later. The `FIRST_VALUE` for medium is getting the medium *associated with that exact same earliest row*.

So you could probably just use `FIRST_VALUE` and get the right medium, but grabbing the timestamp with `MIN` lets you keep that piece of data handy in the result set. I had to draw it out on paper to see why both were there, haha.

Does that make sense, or did I get that wrong? Still wrapping my head around this stuff myself.



   
ReplyQuote
(@danielp)
Estimable Member
Joined: 3 months ago
Posts: 200
 

Yeah, you've got the right idea! The `MIN` is essentially grabbing the timestamp value itself, while `FIRST_VALUE` is picking the medium from that exact same winning row.

A practical reason to grab the timestamp separately is for building reports. In my dashboards, I often want to show a trend of first-touch channels over time - like a line chart showing organic search as the first touch spiked last Tuesday. For that, you need that timestamp cleanly in its own column.

But you're right, you could technically derive everything from `FIRST_VALUE` with a nested query. It just gets messy fast when you try to filter or blend the data later. Keeping them separate makes the logic clearer for anyone else reading the SQL, too.



   
ReplyQuote
(@julier)
Eminent Member
Joined: 3 months ago
Posts: 20
 

Oh, that's a really practical point about the static 90-day view. I'm dealing with a smaller dataset now, but I can see that blowing up later.

So when you use a separate date filter in Looker Studio, does it still only pull data from the 90-day view, or does it try to query the underlying tables outside that range? I'm a bit fuzzy on how the filter interacts with the source SQL.



   
ReplyQuote
(@laurad)
Trusted Member
Joined: 3 months ago
Posts: 27
 

Starting with first-click is fine, but calling it a foundation for "more complex models" is optimistic. Most sales teams I've seen never move past it because the data gets too messy to trust.

This kind of report gives a neat, tidy picture that's often wrong. It'll tell you Paid Search started everything, while completely ignoring the podcast they heard a week before that made them search. Good for a slideshow, less good for actual budget decisions.


If it sounds too good, read the release notes


   
ReplyQuote