ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Détection de type event_params dans GA4

Comment GA4 détecte automatiquement les types de paramètres entre les champs string_value, int_value et double_value — et le pattern défensif COALESCE quand le type n'est pas garanti.

Planté
ga4bigqueryanalyticsdata engineering

Le tableau event_params dans l’export GA4 BigQuery stocke chaque paramètre comme une paire clé-valeur où la valeur est un RECORD avec quatre champs possibles : string_value, int_value, float_value et double_value. GA4 détecte automatiquement quel champ renseigner en fonction du type de données de la valeur envoyée.

Cette conception garde le schéma flexible — vous pouvez ajouter de nouveaux paramètres sans modifier la structure de la table — mais elle introduit une classe de bugs subtils faciles à manquer car les requêtes ne génèrent pas d’erreur. Elles retournent simplement des nulls.

Comment fonctionne l’assignation de type

GA4 assigne les valeurs aux champs de type comme suit :

Champ de valeurTypeUtilisé pour
string_valueSTRINGValeurs texte, URLs, catégories, identifiants
int_valueINTEGERComptages, IDs, timestamps Unix, flags booléens (0/1)
float_valueFLOATActuellement non utilisé par GA4
double_valueFLOATValeurs décimales, prix

La détection automatique est cohérente pour les paramètres GA4 intégrés. Vous pouvez vous fier à :

  • ga_session_id → toujours int_value (timestamp Unix)
  • ga_session_number → toujours int_value
  • page_location → toujours string_value
  • page_title → toujours string_value
  • page_referrer → toujours string_value
  • engaged_session_event → toujours int_value (0 ou 1)

Extraire ces paramètres avec le mauvais champ de type retourne null silencieusement :

-- INCORRECT : Retourne NULL pour l'ID de session
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'ga_session_id')
-- CORRECT : Retourne le timestamp entier de session
(SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id')

Le problème des paramètres personnalisés

Les paramètres personnalisés — les dimensions que vous instrumentez vous-même — peuvent atterrir dans différents champs de type selon la manière dont les valeurs ont été collectées. Le problème s’amplifie avec les incohérences d’implémentation dans le temps :

  • Un paramètre envoyé comme chaîne "123" va dans string_value
  • Le même paramètre envoyé comme entier 123 va dans int_value
  • Si les deux se produisent sur différents événements ou dans le temps, vous avez des valeurs réparties sur deux champs de type

Cela se produit en pratique. Un développeur modifie une implémentation de tag GA4 pour envoyer un paramètre précédemment en chaîne comme un nombre. Les événements historiques ont la valeur dans string_value. Les nouveaux événements l’ont dans int_value. Une requête qui ne lit que depuis un champ obtient la moitié des données.

Le pattern défensif COALESCE

Lorsque vous n’êtes pas certain du type d’un paramètre — ou lorsque l’assignation de type a été incohérente — utilisez COALESCE sur tous les champs de type pertinents :

SELECT
COALESCE(
value.string_value,
CAST(value.int_value AS STRING),
CAST(value.double_value AS STRING)
) AS param_value
FROM UNNEST(event_params)
WHERE key = 'custom_param'

Cela retourne la première valeur non-null parmi les trois champs, en castant les types numériques en chaîne pour un type de sortie cohérent. C’est défensif mais pas gratuit — vous évaluez trois expressions au lieu d’une. Pour les requêtes à fort volume sur de longues plages de dates, cette surcharge s’accumule.

Une approche plus ciblée, une fois que vous avez audité quels types sont réellement en jeu :

-- Si vous savez que le paramètre est soit string soit int, jamais double :
COALESCE(
value.string_value,
CAST(value.int_value AS STRING)
) AS param_value

Auditer la distribution des types

Avant de construire des modèles de production qui extraient un paramètre personnalisé, auditez comment les valeurs sont réellement distribuées entre les champs de type :

SELECT
COUNTIF(value.string_value IS NOT NULL) AS string_count,
COUNTIF(value.int_value IS NOT NULL) AS int_count,
COUNTIF(value.double_value IS NOT NULL) AS double_count,
COUNTIF(value.float_value IS NOT NULL) AS float_count,
COUNT(*) AS total
FROM `project.analytics_123456789.events_*`,
UNNEST(event_params)
WHERE key = 'your_custom_param'
AND _TABLE_SUFFIX BETWEEN '20260101' AND '20260131'

Si string_count est 95 000 et int_count est 5 000, vous savez que vous avez besoin de COALESCE. Si seul int_count est non-nul, vous pouvez extraire directement depuis int_value avec confiance.

Exécutez cet audit sur une plage de dates représentative — pas seulement des données récentes — surtout si votre implémentation GA4 est active depuis des années et a pu évoluer.

Encoder dans les macros dbt

Lorsque vous construisez des macros d’extraction réutilisables, encodez l’hypothèse de type explicitement pour que les futurs mainteneurs comprennent l’intention :

-- macros/ga4_param_string.sql
{% macro ga4_param_string(column, param_name) %}
(SELECT value.string_value FROM UNNEST({{ column }}) WHERE key = '{{ param_name }}')
{% endmacro %}
-- macros/ga4_param_int.sql
{% macro ga4_param_int(column, param_name) %}
(SELECT value.int_value FROM UNNEST({{ column }}) WHERE key = '{{ param_name }}')
{% endmacro %}
-- macros/ga4_param_coerce.sql
-- Utiliser quand le type est incertain ou historiquement incohérent
{% macro ga4_param_coerce(column, param_name) %}
COALESCE(
(SELECT value.string_value FROM UNNEST({{ column }}) WHERE key = '{{ param_name }}'),
CAST((SELECT value.int_value FROM UNNEST({{ column }}) WHERE key = '{{ param_name }}') AS STRING),
CAST((SELECT value.double_value FROM UNNEST({{ column }}) WHERE key = '{{ param_name }}') AS STRING)
)
{% endmacro %}

Le nom de la macro signale l’hypothèse. ga4_param_string indique que la chaîne est attendue. ga4_param_coerce indique que le type est incertain. La documentation dans le nom de la macro vaut mieux qu’un commentaire qui pourrait diverger de la réalité.

Relation avec user_properties

La même logique de détection de type s’applique au tableau user_properties. Les user properties sont définies via setUserProperty et stockées dans une structure imbriquée identique : chaque entrée a une key et un RECORD value avec string_value, int_value, float_value et double_value. Les mêmes patterns d’audit et d’extraction défensive s’appliquent.

La seule différence avec user_properties est le champ supplémentaire set_timestamp_micros, qui enregistre quand la propriété a été mise à jour pour la dernière fois — utile pour tracker l’évolution des propriétés dans le temps.