Brouillon auto

Guide BigQuery sur l’attribution des conversions GA4

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 toujours source ou medium.
  • 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.

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 :

  1. Préparer les événements : mettre en ordre, ajouter les infos manquantes (hit number, channel grouping).
  2. Réparer les trous : utiliser des window functions pour propager la dernière source connue.
  3. 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 ou INTERVAL 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 :

datecampaignchannelcost
2025-08-01Summer2025Paid Search500 €
2025-08-01RetargetDisplay200 €

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 :

  1. Préparer les événements.
  2. Propager le canal intra-session.
  3. Appliquer le last non-direct click sur 30 jours.
  4. Extraire les conversions (purchase).
  5. Agréger par jour et canal.
  6. Comparer à l’attribution GA4 native.
  7. 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) :

datechannelconversions
2020-12-05Organic Search120
2020-12-05Paid Search95
2020-12-05Referral30
2020-12-05Direct20

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) :

datechannelcost
2020-12-05Paid Search1 000
2020-12-05Organic Search0
2020-12-05Referral200

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) :

datechannelconvcostroas
2020-12-05Organic Search1200
2020-12-05Paid Search9510000.095
2020-12-05Referral302000.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

channelLinear MTADecay MTALast Non-Direct CustomGA4 Last Non-Direct
Organic Search9508901020980
Paid Search780820750730
Referral210230190200
Direct6055070

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.

Retour en haut
Formations Analytics