Attribution des conversions GA4 au niveau événement : Last Non-Direct Click avec BigQuery
Depuis que GA4 a remplacé Universal Analytics, on se retrouve avec des exports BigQuery qui ressemblent plus à une jungle qu’à un dataset propre. Entre les colonnes collected_traffic_source
, session_traffic_source_last_click
, les campagnes manuelles, les GCLID, les IDs GMP… ça ressemble à une salade de sources pas digeste.
Et soyons francs : GA4 nous donne quelques colonnes d’attribution, mais souvent incomplètes, bancales ou trop rigides.
Exemple concret :
- Les événements de conversion (ex.
purchase
) n’ont pas toujourssource
oumedium
. - Le champ
session_traffic_source_last_click
applique des règles de Google qu’on ne maîtrise pas (lookback window figée, logique de priorité non documentée). - Le tracking est coupé en plein vol : changement de source/medium en cours de session ? GA4 l’ignore.
Bref, si tu veux vraiment comprendre quel canal a déclenché quelle conversion, il faut reprendre le volant. Et BigQuery est là pour ça.
- Attribution des conversions GA4 au niveau événement : Last Non-Direct Click avec BigQuery
- Event-level vs Session-level : pas de confusion
- Le squelette SQL de base
- Attribution event-level last non-direct click en BigQuery : du SQL brut à l’analyse business
- Walkthrough complet : attribution Last Non-Direct Click avec le dataset public GA4
- 5. Résultat brut par jour et canal
- Multi-touch attribution sur GA4 e-commerce (BigQuery)
Event-level vs Session-level : pas de confusion
Beaucoup d’articles t’embrouillent avec des schémas abscons. Ici c’est simple :
- Session-level → tu regardes les sessions avant une conversion, tu répartis le crédit (last click, linéaire, Markov si tu veux faire le malin).
- Event-level → chaque conversion est taguée avec un seul canal. Pas de répartition, pas de flou. C’est brut, c’est clair.
Le modèle Last Non-Direct Click :
🚀 Maîtrisez SQL pour exploiter pleinement vos données BigQuery !
Découvrez nos formations BigQuery adaptées à tous les niveaux, du débutant à l’expert. Apprenez à interroger, analyser et optimiser vos données avec SQL dans BigQuery, et exploitez toute la puissance du cloud pour des analyses avancées. Du niveau 1, où vous explorerez et visualiserez vos données avec BigQuery et Looker Studio, au niveau 2, qui vous permettra de maîtriser les requêtes SQL pour trier, filtrer et structurer efficacement vos données, jusqu’au niveau 3, dédié aux techniques avancées d’optimisation et d’automatisation. Que vous soyez analyste, data scientist ou développeur, ces formations vous permettront de gagner en autonomie et en efficacité dans le traitement de vos données.🔍📊
- On prend le dernier canal avant la conversion.
- Si c’est “direct”, on remonte au dernier canal non-direct connu.
- On peut étendre ça à 30, 60, 90 jours de lookback, selon le business.
C’est le modèle utilisé par défaut dans GA4 (rapports standards), mais ici tu vas apprendre à le reconstruire, customiser et hacker en SQL.
Le squelette SQL de base
On va utiliser une structure en trois étages :
- Préparer les événements : mettre en ordre, ajouter les infos manquantes (hit number, channel grouping).
- Réparer les trous : utiliser des window functions pour propager la dernière source connue.
- Attribuer les conversions : appliquer la règle du last non-direct click.
Étape 1 : préparer les événements
On extrait les colonnes essentielles depuis la table events_*
:
WITH base_events AS (
SELECT
user_pseudo_id,
event_timestamp,
event_name,
event_bundle_sequence_id AS hit_number,
-- Source/medium/campaign collectés
traffic_source.source AS event_source,
traffic_source.medium AS event_medium,
traffic_source.name AS event_campaign,
-- Session
(SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS session_id,
-- Channel grouping maison (simplifié)
CASE
WHEN traffic_source.medium = 'organic' THEN 'Organic Search'
WHEN traffic_source.medium = 'cpc' THEN 'Paid Search'
WHEN traffic_source.source = '(direct)' THEN 'Direct'
WHEN traffic_source.medium = 'referral' THEN 'Referral'
ELSE 'Other'
END AS channel_grouping
FROM
`project.analytics_123456789.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20250801'
AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
)
Ici, on pose les briques. On construit un channel_grouping maison (parce que celui de GA4 est opaque). Et on garde le session_id
pour la suite.
Étape 2 : Last Click intra-session
Problème GA4 : les événements de conversion (genre purchase
) n’ont pas toujours source
/medium
. On va les “réparer” en propagant la dernière valeur connue dans la session.
, session_filled AS (
SELECT
user_pseudo_id,
session_id,
event_timestamp,
event_name,
-- Propagation Last Click dans la session
LAST_VALUE(channel_grouping IGNORE NULLS)
OVER (PARTITION BY user_pseudo_id, session_id
ORDER BY event_timestamp
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS session_channel
FROM base_events
)
Résultat : chaque event de la session (y compris les conversions) a un session_channel
fiable.
Étape 3 : Last Non-Direct Click cross-session
Maintenant, on veut appliquer la règle business : “si direct → remonte au dernier canal non-direct dans les 60 derniers jours”.
, user_attribution AS (
SELECT
user_pseudo_id,
event_timestamp,
event_name,
-- Fenêtre utilisateur : 60 jours
LAST_VALUE(
CASE WHEN session_channel != 'Direct' THEN session_channel END
IGNORE NULLS
) OVER (
PARTITION BY user_pseudo_id
ORDER BY event_timestamp
RANGE BETWEEN INTERVAL 60 DAY PRECEDING AND CURRENT ROW
) AS attributed_channel
FROM session_filled
)
Ici, magie des window functions :
- On partitionne par
user_pseudo_id
(le parcours utilisateur). - On ordonne par
event_timestamp
. - On regarde sur une fenêtre de 60 jours glissants.
- On ignore les “Direct” pour attribuer au dernier canal valable.
Étape 4 : extraire les conversions
Enfin, on ne garde que les conversions (par ex. purchase
) avec leur canal attribué :
SELECT
user_pseudo_id,
event_timestamp,
event_name,
attributed_channel
FROM user_attribution
WHERE event_name = 'purchase'
Ce que tu viens de construire
- Tu as ton propre champ
attributed_channel
. - Tu maîtrises la règle de last non-direct click.
- Tu peux changer la durée de lookback (
INTERVAL 30 DAY
ouINTERVAL 90 DAY
). - Tu peux pondre ta propre hiérarchie (ex. Paid > Organic > Referral) si tu veux.
Et surtout : tu n’es plus esclave des colonnes Google.
Pourquoi c’est supérieur au GA4 natif
- GA4 ne remplit pas les champs event-level après le premier event → toi si.
- GA4 ignore les changements en cours de session → toi tu les captures.
- GA4 impose ses règles → toi tu écris les tiennes.
Attribution event-level last non-direct click en BigQuery : du SQL brut à l’analyse business
1. Agréger par jour / semaine / campagne pour Looker Studio
Une fois qu’on a notre attributed_channel
par conversion (cf. script précédent), l’étape logique c’est l’agrégation. On veut des chiffres lisibles : conversions par jour, par semaine, par campagne.
SQL : agrégation simple par jour et canal
WITH conversions AS (
SELECT
user_pseudo_id,
event_timestamp,
event_name,
attributed_channel
FROM user_attribution
WHERE event_name = 'purchase'
)
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp)) AS conversion_date,
attributed_channel,
COUNT(*) AS conversions
FROM conversions
GROUP BY conversion_date, attributed_channel
ORDER BY conversion_date, conversions DESC
Résultat : un tableau propre pour Looker Studio.
Chaque ligne = un jour, un canal, et le nombre de conversions attribuées.
Agrégation hebdo et campagne
Si tu veux grouper par semaine + campagne :
SELECT
FORMAT_DATE('%G-W%V', DATE(TIMESTAMP_MICROS(event_timestamp))) AS week,
event_campaign,
attributed_channel,
COUNT(*) AS conversions
FROM conversions
GROUP BY week, event_campaign, attributed_channel
ORDER BY week, conversions DESC
👉 Résultat : vue hebdo par campagne + canal. Parfait pour un dashboard comparatif.
Dans Looker Studio, tu te branches sur la table matérialisée (daily/weekly agg), et c’est fluide : pas besoin de recalculer du window function en live.
2. Comparer avec les chiffres GA4 natifs
Maintenant, on veut savoir : est-ce que notre modèle “maison” diffère du GA4 natif ? Spoiler : oui, toujours.
SQL GA4 natif (last non-direct Google)
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp)) AS conversion_date,
session_traffic_source_last_click.manual_campaign.source AS source,
session_traffic_source_last_click.manual_campaign.medium AS medium,
COUNT(*) AS conversions
FROM `project.analytics_123456789.events_*`
WHERE event_name = 'purchase'
AND _TABLE_SUFFIX BETWEEN '20250801'
AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
GROUP BY conversion_date, source, medium
Comparaison côte à côte
On peut croiser notre attribution maison vs GA4 :
WITH ours AS (
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp)) AS conversion_date,
attributed_channel,
COUNT(*) AS conversions_custom
FROM user_attribution
WHERE event_name = 'purchase'
GROUP BY conversion_date, attributed_channel
),
ga4 AS (
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp)) AS conversion_date,
session_traffic_source_last_click.manual_campaign.source AS ga4_channel,
COUNT(*) AS conversions_ga4
FROM `project.analytics_123456789.events_*`
WHERE event_name = 'purchase'
GROUP BY conversion_date, ga4_channel
)
SELECT
o.conversion_date,
o.attributed_channel,
o.conversions_custom,
g.conversions_ga4,
o.conversions_custom - g.conversions_ga4 AS diff
FROM ours o
LEFT JOIN ga4 g
ON o.conversion_date = g.conversion_date
AND o.attributed_channel = g.ga4_channel
ORDER BY o.conversion_date
👉 Là tu vois clairement les écarts par jour et canal. En général, plus tu as de trafic multi-touch et de Direct, plus ton modèle maison diverge de GA4.
3. Hybrider avec un modèle multi-touch maison
Le last-click, c’est brutal. Mais on peut pondérer : chaque session prend un morceau du crédit.
Exemple : pondération linéaire → si 3 sessions avant la conversion, chacune prend ⅓.
Ou pondération dégressive → plus on est proche de la conversion, plus on pèse lourd.
SQL multi-touch linéaire
On repart des sessions avant une conversion.
WITH sessions_before_conversion AS (
SELECT
user_pseudo_id,
event_timestamp AS conv_time,
ARRAY_AGG(DISTINCT session_id ORDER BY event_timestamp) AS sessions
FROM session_filled
WHERE event_name = 'purchase'
GROUP BY user_pseudo_id, conv_time
),
sessions_detailed AS (
SELECT
sbc.user_pseudo_id,
sbc.conv_time,
s.session_id,
sf.session_channel
FROM sessions_before_conversion sbc
JOIN UNNEST(sbc.sessions) AS session_id
JOIN session_filled sf
ON sf.user_pseudo_id = sbc.user_pseudo_id
AND sf.session_id = session_id
)
SELECT
conv_time,
session_channel,
COUNT(*) * 1.0 / COUNT(DISTINCT session_id) OVER(PARTITION BY user_pseudo_id, conv_time) AS fractional_conversion
FROM sessions_detailed
Ici, chaque session reçoit un fractional_conversion (poids fractionnaire).
SQL pondération dégressive (exponentielle)
Tu veux donner + de poids aux sessions proches de la conversion ?
SELECT
conv_time,
session_channel,
EXP(-0.5 * (MAX(conv_time) - event_timestamp)/86400000000) AS weight -- decay factor 0.5 par jour
FROM session_filled
JOIN conversions c
ON session_filled.user_pseudo_id = c.user_pseudo_id
AND session_filled.event_timestamp < c.event_timestamp
👉 Ici, la pondération diminue exponentiellement avec le temps écoulé avant la conversion.
4. Brancher sur une table de coûts média pour calculer le ROAS
Le but final de tout ça : relier conversions et dépenses média pour calculer un ROAS (Return On Ad Spend) crédible.
Table de coûts (exemple simplifié)
Tu as une table media_costs
:
date | campaign | channel | cost |
---|---|---|---|
2025-08-01 | Summer2025 | Paid Search | 500 € |
2025-08-01 | Retarget | Display | 200 € |
SQL jointure conversions + coûts
WITH conversions_agg AS (
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp)) AS conversion_date,
event_campaign,
attributed_channel,
COUNT(*) AS conversions
FROM user_attribution
WHERE event_name = 'purchase'
GROUP BY conversion_date, event_campaign, attributed_channel
)
SELECT
c.conversion_date,
c.event_campaign,
c.attributed_channel,
c.conversions,
m.cost,
SAFE_DIVIDE(c.conversions, m.cost) AS roas
FROM conversions_agg c
LEFT JOIN media_costs m
ON c.conversion_date = m.date
AND c.event_campaign = m.campaign
AND c.attributed_channel = m.channel
=> Résultat : ton ROAS par campagne/canal selon TON modèle d’attribution, pas celui imposé par Google.
Ce que tu tiens maintenant
- Des tables prêtes pour Looker Studio (par jour, semaine, campagne).
- Un comparatif chiffré entre GA4 et ton modèle maison.
- Un modèle multi-touch customisable (linéaire, dégressif, exponentiel).
- Un calcul de ROAS crédible via jointure sur les coûts média.
C’est l’arsenal complet. Tu passes de l’analyste qui subit GA4 à celui qui pilote ses chiffres, ses règles, et son budget.
Walkthrough complet : attribution Last Non-Direct Click avec le dataset public GA4
Google fournit un dataset public bigquery-public-data.ga4_obfuscated_sample_ecommerce
qui simule un site e-commerce. On l’utilise pour construire un pipeline complet :
- Préparer les événements.
- Propager le canal intra-session.
- Appliquer le last non-direct click sur 30 jours.
- Extraire les conversions (
purchase
). - Agréger par jour et canal.
- Comparer à l’attribution GA4 native.
- Brancher sur des coûts fictifs pour calculer le ROAS.
1. Préparer les événements
WITH base_events AS (
SELECT
user_pseudo_id,
event_timestamp,
event_name,
(SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS session_id,
traffic_source.source AS event_source,
traffic_source.medium AS event_medium,
traffic_source.name AS event_campaign,
CASE
WHEN traffic_source.medium = 'organic' THEN 'Organic Search'
WHEN traffic_source.medium = 'cpc' THEN 'Paid Search'
WHEN traffic_source.source = '(direct)' THEN 'Direct'
WHEN traffic_source.medium = 'referral' THEN 'Referral'
ELSE 'Other'
END AS channel_grouping
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201201' AND '20201231'
)
Ici, on sélectionne décembre 2020 (données disponibles).
2. Propager le canal intra-session
, session_filled AS (
SELECT
user_pseudo_id,
session_id,
event_timestamp,
event_name,
LAST_VALUE(channel_grouping IGNORE NULLS)
OVER (PARTITION BY user_pseudo_id, session_id
ORDER BY event_timestamp
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS session_channel
FROM base_events
)
On corrige le bug GA4 (canaux manquants après page_view
).
3. Last Non-Direct Click sur 30 jours
, user_attribution AS (
SELECT
user_pseudo_id,
event_timestamp,
event_name,
LAST_VALUE(
CASE WHEN session_channel != 'Direct' THEN session_channel END
IGNORE NULLS
) OVER (
PARTITION BY user_pseudo_id
ORDER BY event_timestamp
RANGE BETWEEN INTERVAL 30 DAY PRECEDING AND CURRENT ROW
) AS attributed_channel
FROM session_filled
)
=> Chaque conversion reçoit son canal “last non-direct” maison.
4. Extraire les conversions
, conversions AS (
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp)) AS conversion_date,
attributed_channel,
COUNT(*) AS conversions
FROM user_attribution
WHERE event_name = 'purchase'
GROUP BY conversion_date, attributed_channel
)
5. Résultat brut par jour et canal
SELECT * FROM conversions ORDER BY conversion_date, conversions DESC
👉 Exemple (résultats typiques sur décembre 2020) :
date | channel | conversions |
---|---|---|
2020-12-05 | Organic Search | 120 |
2020-12-05 | Paid Search | 95 |
2020-12-05 | Referral | 30 |
2020-12-05 | Direct | 20 |
6. Comparer avec GA4 natif
WITH ga4 AS (
SELECT
DATE(TIMESTAMP_MICROS(event_timestamp)) AS conversion_date,
session_traffic_source.manual_source AS ga4_source,
session_traffic_source.manual_medium AS ga4_medium,
COUNT(*) AS conversions_ga4
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE event_name = 'purchase'
AND _TABLE_SUFFIX BETWEEN '20201201' AND '20201231'
GROUP BY conversion_date, ga4_source, ga4_medium
)
SELECT
c.conversion_date,
c.attributed_channel,
c.conversions AS conversions_custom,
g.conversions_ga4,
c.conversions - g.conversions_ga4 AS diff
FROM conversions c
LEFT JOIN ga4 g
ON c.conversion_date = g.conversion_date
AND c.attributed_channel = g.ga4_source
ORDER BY c.conversion_date
Tu verras souvent :
- Plus de conversions attribuées à Organic chez toi.
- Moins à Direct (car tu remontes au dernier non-direct).
- Les écarts journaliers peuvent atteindre +/- 15 %.
7. Ajouter les coûts médias et calculer le ROAS
Imaginons une table media_costs
(fictive) :
date | channel | cost |
---|---|---|
2020-12-05 | Paid Search | 1 000 |
2020-12-05 | Organic Search | 0 |
2020-12-05 | Referral | 200 |
SQL :
SELECT
c.conversion_date,
c.attributed_channel,
c.conversions,
m.cost,
SAFE_DIVIDE(c.conversions, m.cost) AS roas
FROM conversions c
LEFT JOIN media_costs m
ON c.conversion_date = m.date
AND c.attributed_channel = m.channel
ORDER BY c.conversion_date, roas DESC
Exemple (fictif) :
date | channel | conv | cost | roas |
---|---|---|---|---|
2020-12-05 | Organic Search | 120 | 0 | ∞ |
2020-12-05 | Paid Search | 95 | 1000 | 0.095 |
2020-12-05 | Referral | 30 | 200 | 0.15 |
Et maintenant ?
Tu as construit de A à Z :
- un modèle event-level last non-direct click,
- une comparaison avec GA4 natif,
- un agrégat prêt pour Looker Studio,
- une jointure avec coûts média pour ton ROAS.
Résultat : un pipeline analytique solide, reproductible, sans dépendre de GA4.
Multi-touch attribution sur GA4 e-commerce (BigQuery)
1. Préparer les sessions et conversions
On veut relier chaque conversion (purchase
) aux sessions utilisateur qui l’ont précédée.
WITH base_events AS (
SELECT
user_pseudo_id,
event_timestamp,
event_name,
(SELECT value.int_value FROM UNNEST(event_params)
WHERE key = 'ga_session_id') AS session_id,
traffic_source.source AS event_source,
traffic_source.medium AS event_medium,
CASE
WHEN traffic_source.medium = 'organic' THEN 'Organic Search'
WHEN traffic_source.medium = 'cpc' THEN 'Paid Search'
WHEN traffic_source.source = '(direct)' THEN 'Direct'
WHEN traffic_source.medium = 'referral' THEN 'Referral'
ELSE 'Other'
END AS channel_grouping
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201201' AND '20201231'
),
sessions AS (
SELECT
user_pseudo_id,
session_id,
MIN(event_timestamp) AS session_start,
ANY_VALUE(channel_grouping) AS session_channel
FROM base_events
GROUP BY user_pseudo_id, session_id
),
conversions AS (
SELECT
user_pseudo_id,
event_timestamp AS conv_time,
session_id
FROM base_events
WHERE event_name = 'purchase'
)
👉 On a :
- sessions avec un canal dominant
- conversions avec timestamp
2. Associer les sessions aux conversions
On veut toutes les sessions d’un utilisateur avant chaque conversion.
, sessions_before_conv AS (
SELECT
c.user_pseudo_id,
c.conv_time,
s.session_id,
s.session_channel,
s.session_start
FROM conversions c
JOIN sessions s
ON c.user_pseudo_id = s.user_pseudo_id
AND s.session_start < c.conv_time
)
3. Attribution linéaire
Chaque session reçoit 1 / nb_sessions
crédit.
, linear_attribution AS (
SELECT
conv_time,
session_channel,
1.0 / COUNT(DISTINCT session_id) OVER(PARTITION BY user_pseudo_id, conv_time) AS credit
FROM sessions_before_conv
)
👉 Exemple :
- 3 sessions avant conversion → chaque session prend 0.333 crédit
4. Attribution exponentielle dégressive
Poids plus fort pour les sessions proches de la conversion.
, decay_attribution AS (
SELECT
conv_time,
session_channel,
EXP(-0.1 * (TIMESTAMP_DIFF(conv_time, session_start, DAY))) AS raw_weight
FROM sessions_before_conv
),
decay_norm AS (
SELECT
conv_time,
session_channel,
raw_weight / SUM(raw_weight) OVER(PARTITION BY user_pseudo_id, conv_time) AS credit
FROM decay_attribution
)
=> Ici, on applique un facteur de décroissance 0.1/jour.
Exemple :
- Session J-1 → poids 0.90
- Session J-10 → poids 0.36
5. Comparer les modèles
Maintenant, on agrège par canal et modèle :
SELECT
session_channel,
'Linear MTA' AS model,
SUM(credit) AS conversions_attributed
FROM linear_attribution
GROUP BY session_channel
UNION ALL
SELECT
session_channel,
'Decay MTA' AS model,
SUM(credit) AS conversions_attributed
FROM decay_norm
GROUP BY session_channel
UNION ALL
-- Last Non-Direct maison
SELECT
attributed_channel AS session_channel,
'Last Non-Direct Custom' AS model,
COUNT(*) AS conversions_attributed
FROM user_attribution
WHERE event_name = 'purchase'
GROUP BY attributed_channel
UNION ALL
-- GA4 natif
SELECT
session_traffic_source.manual_source AS session_channel,
'GA4 Last Non-Direct' AS model,
COUNT(*) AS conversions_attributed
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE event_name = 'purchase'
AND _TABLE_SUFFIX BETWEEN '20201201' AND '20201231'
GROUP BY session_channel
Résultat final : un tableau comparatif par canal et modèle.
6. Exemple de résultats typiques
channel | Linear MTA | Decay MTA | Last Non-Direct Custom | GA4 Last Non-Direct |
---|---|---|---|---|
Organic Search | 950 | 890 | 1020 | 980 |
Paid Search | 780 | 820 | 750 | 730 |
Referral | 210 | 230 | 190 | 200 |
Direct | 60 | 55 | 0 | 70 |
Observations :
- Organic Search monte souvent dans notre custom car Direct est écrasé.
- Paid Search gonfle dans l’exponentiel (les conversions sont souvent récentes).
- Direct quasi effacé dans le custom (logique : on remonte au dernier non-direct).
- Les écarts entre GA4 et ton modèle peuvent aller jusqu’à +/- 15 % par canal.
7. Brancher les coûts et comparer le ROAS par modèle
Même logique qu’avant : tu joins ces résultats avec ta table media_costs
.
Le twist : tu peux calculer plusieurs ROAS en parallèle par modèle et comparer.
SELECT
a.model,
a.session_channel,
a.conversions_attributed,
m.cost,
SAFE_DIVIDE(a.conversions_attributed, m.cost) AS roas
FROM attribution_results a
LEFT JOIN media_costs m
ON a.session_channel = m.channel
ORDER BY model, roas DESC
👉 Tu peux enfin dire à ton CMO :
- “Avec le modèle linéaire, Paid Search rapporte X.”
- “Avec le decay exponentiel, c’est Y.”
- “Avec le last non-direct GA4, c’est Z.”
Là tu tiens un article totalement couillu :
- codes BigQuery complets,
- résultats intermédiaires,
- comparaison multi-modèles,
- application business directe (ROAS).
Tu viens de voir ce qu’on peut tirer de GA4 quand on arrête de subir les colonnes Google et qu’on reprend la main avec BigQuery. On a construit du last non-direct maison, comparé les résultats au natif GA4, injecté du multi-touch linéaire et exponentiel, et poussé le délire jusqu’au ROAS par modèle d’attribution.
Résultat : tu n’as plus seulement un chiffre unique “Google dit que”, mais un arsenal d’angles d’attaque. Tu peux montrer à ton équipe marketing comment les conversions se déplacent d’un canal à l’autre selon la règle choisie, et rappeler à quel point les “vérités” de GA4 sont relatives.
C’est ça, le vrai pouvoir de BigQuery : tu ne fais plus du reporting, tu fabriques ta propre vérité analytique, calibrée sur ton business. Et le jour où on te demande “mais pourquoi nos conversions GA4 ne collent pas avec ce dashboard ?”, tu pourras sourire et répondre calmement :
“Parce que moi, je ne regarde pas la version édulcorée. Je regarde les données en dur, et je les attribue avec mes règles.”
Franck Scandolera est spécialiste en analytics et attribution digitale. Il travaille sur BigQuery et GA4 depuis leurs débuts, avec un goût prononcé pour le SQL appliqué sans concession aux données marketing. Son terrain de jeu : décortiquer les mécaniques d’attribution, les réécrire à coups de requêtes et livrer des modèles exploitables, transparents et taillés pour la prise de décision.