ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Mart de performance d'acquisition GA4

Un mart au grain quotidien x source/medium pour le reporting d'acquisition GA4 — agrégation des événements sessionisés en métriques prêtes pour les tableaux de bord avec taux de conversion et revenus.

Planté
ga4dbtbigquerydata modelinganalytics

Le mart de performance d’acquisition fournit des performances au niveau canal : quelles sources et médiums génèrent des sessions, des conversions et des revenus, et comment ces métriques évoluent dans le temps. C’est l’équivalent GA4 du rapport Acquisition de trafic dans l’interface GA4, construit à partir de données sessionisées avec des définitions contrôlées par l’utilisateur.

La décision de conception clé est le grain. Pas une ligne par session (presque aussi grand que votre modèle intermédiaire et trop granulaire pour les tableaux de bord). Pas une ligne par utilisateur (c’est une question différente). Le mart d’acquisition agrège à quotidien x source x medium, produisant environ 500 lignes par jour plutôt que 50 000 sessions. Les tableaux de bord se chargent rapidement, les coûts de stockage restent minimes, et les taux pré-calculés éliminent les calculs répétés.

Pourquoi ne pas agréger directement depuis les événements bruts ?

Vous pourriez ignorer le modèle sessionisé intermédiaire et agréger directement depuis le modèle de base. Certaines équipes le font, et pour un seul tableau de bord ça fonctionne. Mais cela signifie que chaque mart ayant besoin du contexte de session (page d’atterrissage, durée de session, flags de conversion) doit reconstruire ce contexte indépendamment. Le modèle intermédiaire sessionisé calcule le contexte de session une seule fois ; chaque mart en aval en hérite gratuitement.

Le mart d’acquisition illustre ce bénéfice. Les flags de conversion au niveau session comme session_has_purchase existent déjà sur chaque ligne d’événement. Le mart se contente de les compter. Sans le modèle sessionisé, le mart aurait besoin de ses propres window functions pour déterminer quelles sessions ont converti — dupliquant une logique dont le mart de session, le mart utilisateur et l’analyse d’entonnoir ont tous besoin.

L’agrégation en deux étapes

Le mart utilise une approche en deux étapes : d’abord agréger au grain session, puis agréger les sessions au grain quotidien x canal.

Étape 1 : Déduplication au niveau session

WITH sessions AS (
SELECT
ga4__event__session_key,
ga4__event__user_pseudo_id,
MIN(ga4__event__date) AS session_date,
MAX(ga4__event__session_source) AS session_source,
MAX(ga4__event__session_medium) AS session_medium,
MAX(ga4__event__session_campaign) AS session_campaign,
MAX(CAST(event__session_has_purchase AS INT64)) AS has_purchase,
MAX(CAST(event__session_has_add_to_cart AS INT64)) AS has_add_to_cart,
MAX(CAST(event__session_has_begin_checkout AS INT64)) AS has_begin_checkout,
SUM(ga4__event__purchase_revenue_usd) AS session_revenue_usd,
COUNT(*) AS event_count
FROM {{ ref('int__event__sessionized') }}
GROUP BY ga4__event__session_key, ga4__event__user_pseudo_id
)

Ce CTE réduit la table sessionisée au grain événement à une ligne par session. Le MAX sur les flags booléens fonctionne parce que chaque événement d’une session porte la même valeur de flag (elle a été définie via une window function sur la partition de session). SUM sur les revenus collecte tous les montants d’achat dans la session.

Inclure ga4__event__user_pseudo_id dans le GROUP BY n’est pas strictement nécessaire (la clé de session identifie déjà une session de façon unique), mais cela rend le comptage d’utilisateurs à l’étape 2 possible sans sous-requête.

Étape 2 : Agrégation quotidienne x canal

aggregated AS (
SELECT
session_date AS acquisition__date,
COALESCE(session_source, '(direct)') AS acquisition__source,
COALESCE(session_medium, '(none)') AS acquisition__medium,
COALESCE(session_campaign, '(not set)') AS acquisition__campaign,
-- Métriques de volume
COUNT(*) AS acquisition__session_count,
COUNT(DISTINCT ga4__event__user_pseudo_id) AS acquisition__user_count,
-- Métriques de conversion
SUM(has_purchase) AS acquisition__purchase_count,
SUM(has_add_to_cart) AS acquisition__add_to_cart_count,
SUM(has_begin_checkout) AS acquisition__begin_checkout_count,
-- Revenus
SUM(session_revenue_usd) AS acquisition__revenue_usd,
-- Engagement
AVG(event_count) AS acquisition__avg_events_per_session
FROM sessions
GROUP BY 1, 2, 3, 4
)

Les patterns COALESCE gèrent l’attribution nulle de façon élégante. Les sessions sans source deviennent (direct), correspondant à la convention de GA4 lui-même. Cela évite que les valeurs nulles ne créent des problèmes d’agrégation en aval.

Taux pré-calculés

La couche finale ajoute des métriques dérivées que les tableaux de bord calculeraient autrement de façon répétée :

enriched AS (
SELECT
*,
SAFE_DIVIDE(acquisition__purchase_count, acquisition__session_count)
AS acquisition__conversion_rate,
SAFE_DIVIDE(acquisition__revenue_usd, acquisition__session_count)
AS acquisition__revenue_per_session_usd,
SAFE_DIVIDE(acquisition__revenue_usd, acquisition__purchase_count)
AS acquisition__avg_order_value_usd
FROM aggregated
)

SAFE_DIVIDE empêche les erreurs de division par zéro lorsqu’une combinaison source/medium a zéro session ou zéro achat un jour donné. BigQuery renvoie NULL plutôt que d’échouer, ce que les outils BI gèrent proprement (affichant vide ou zéro selon la configuration).

Ces taux appartiennent au mart, pas à l’outil BI. Calculer le taux de conversion comme achats / sessions semble simple, mais si trois tableaux de bord différents l’implémentent légèrement différemment (l’un utilise NULLIF, un autre utilise CASE WHEN, le troisième utilise IF), vous obtenez trois chiffres différents. Le pré-calcul dans le mart garantit que tout le monde voit le même taux.

Configuration du modèle

{{ config(
materialized='table',
partition_by={
"field": "acquisition__date",
"data_type": "date",
"granularity": "day"
},
cluster_by=['acquisition__source', 'acquisition__medium'],
tags=['mart', 'reporting', 'ga4']
) }}

Matérialisé en table, pas incrémental. Les modèles mart sont suffisamment petits (des centaines de lignes par jour) pour que les reconstructions complètes soient peu coûteuses. La complexité de la logique incrémentale ne vaut pas le gain de performance marginal à ce grain.

Partitionné par date. Les requêtes de tableaux de bord qui filtrent sur « les 30 derniers jours » ne scannent que 30 partitions. L’unité de facturation minimale de BigQuery est de 10 Mo par partition, donc même avec 500 lignes par jour, le partitionnement est utile car il contrôle les coûts sur les requêtes en aval.

Clusterisé par source et medium. Le pattern de filtre le plus courant dans les tableaux de bord d’acquisition est « montrez-moi la recherche payante » ou « comparez organique vs payant ». Le clustering sur ces champs signifie que BigQuery ne lit que les groupes de lignes pertinents dans chaque partition.

Convention de nommage des colonnes

Le préfixe acquisition__ sert deux objectifs. D’abord, il rend le mart auto-documenté — chaque colonne appartient clairement au domaine d’acquisition. Ensuite, il évite les collisions de noms lorsque le mart est joint avec d’autres tables dans un outil BI. date est ambigu ; acquisition__date ne l’est pas.

Cela suit la même convention entité__attribut utilisée dans les modèles de base et intermédiaires.

Pourquoi la couche intermédiaire est toujours importante

Le mart d’acquisition vous donne des performances agrégées par canal. Mais il ne peut pas répondre à des questions sur les séquences d’événements. « Les sessions où l’utilisateur a consulté un produit, puis l’a ajouté au panier, puis a acheté, dans cet ordre » nécessite des données au niveau événement que ce mart a agrégées.

-- Cette requête a besoin du modèle intermédiaire sessionisé, pas du mart
WITH sequenced AS (
SELECT
ga4__event__session_key,
MAX(CASE WHEN ga4__event__name = 'view_item'
THEN event__session_event_number END) AS view_item_position,
MAX(CASE WHEN ga4__event__name = 'add_to_cart'
THEN event__session_event_number END) AS add_to_cart_position,
MAX(CASE WHEN ga4__event__name = 'purchase'
THEN event__session_event_number END) AS purchase_position
FROM {{ ref('int__event__sessionized') }}
WHERE event__session_has_purchase
GROUP BY ga4__event__session_key
)
SELECT COUNT(*) AS proper_funnel_sessions
FROM sequenced
WHERE view_item_position < add_to_cart_position
AND add_to_cart_position < purchase_position

Le modèle intermédiaire rend les questions sur les séquences d’événements répondables. Le mart rend les tableaux de bord rapides et peu coûteux. Ils servent des objectifs différents. Utilisez les deux — le pattern de sessionisation au grain événement est ce qui rend cette approche en couches possible.

Relation avec les autres marts

Le mart d’acquisition est l’un des trois marts GA4 typiques :

  • Mart d’acquisition (cette note) : grain quotidien x source/medium. Répond à « quels canaux performent le mieux ? »
  • Mart utilisateur : grain utilisateur. Répond à « à quoi ressemble le parcours client complet ? »
  • Mart de sessions : grain session. Répond à des questions détaillées au niveau session. Souvent le mart le plus large, dérivé du modèle sessionisé avec un simple GROUP BY.

Chaque mart sert une audience différente et un grain différent. Les trois dérivent du même modèle intermédiaire sessionisé, qui est la source de vérité unique.