BigQuery: cómo consultar el esquema completo de la tabla de GA4

Has conectado Google Analytics 4 (GA4)a Bigquery, pero el camino no ha terminado todavia. Los datos se estan volcando dentro de una tabla, pero es necesario comprender la estructura exacta de los datos que almacenas. Éste es el primer paso obligatorio de cualquier auditoría, pipeline de modelado o reporte analítico en Google Cloud. Sin embargo, al ver los datos nos damos cuenta que la tabla events_ no se comporta como una tabla relacional convencional. En lugar de dimensiones y metricas, el esquema de GA4 es un modelo semiestructurado que entrelaza campos planos, registros jerárquicos (STRUCT) y listas repetidas de pares clave-valor (ARRAY<STRUCT>).

Intentar inspeccionar esta estructura con herramientas habituales suele dejar fuera la parte más valiosa: las dimensiones y métricas personalizadas que viajan dentro de los eventos y las propiedades de usuario. En este artículo aprenderás a extraer un diccionario de datos completo y unificado mediante SQL, descubriendo tanto la arquitectura nativa del export como los parámetros dinámicos, al tiempo que detectas posibles inconsistencias técnicas generadas en el dataLayer.


Índice:

 

 

Direct Answer Box (TL;DR)

Para consultar el esquema completo de una tabla de GA4 en BigQuery no basta con INFORMATION_SCHEMA.COLUMNS, ya que omite los parámetros dinámicos. La solución integral combina INFORMATION_SCHEMA.COLUMN_FIELD_PATHS para extraer campos planos y structs fijos (device, geo), junto con una consulta SQL con UNNEST sobre event_params y user_properties en la tabla particionada (events_YYYYMMDD). Esto permite mapear cada clave, armonizar sus tipos de datos (STRING, INT64, FLOAT64) y detectar incoherencias por tipos mixtos originadas en el dataLayer.


Por qué INFORMATION_SCHEMA no es suficiente para el export de GA4

En cualquier almacén de datos estándar, las vistas del catálogo del sistema son el mecanismo para inventariar campos. Cuando explicamos cómo consultar el esquema básico de una tabla de BigQuery, vimos que INFORMATION_SCHEMA.COLUMNS resuelve en segundos la descripción de tipos nativos. No obstante, frente a la complejidad del export de GA4, estas vistas presentan limitaciones.

Las limitaciones de COLUMNS frente a tipos anidados

La vista INFORMATION_SCHEMA.COLUMNS evalúa únicamente las columnas de primer nivel declaradas en el DDL (Data Definition Language) de la tabla. Si lanzas una consulta contra ella en tu dataset de analítica:

SELECT
  column_name,
  data_type,
  is_nullable
FROM `TU_PROYECTO.analytics_XXXXXXXXX.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name = 'events_YYYYMMDD';

Obtendrás resultados planos como event_date (STRING) o event_timestamp (INT64), pero cuando la consulta llega a campos complejos, encontramos los primeros problemas:

column_name data_type ¿Permite ver campos internos?
event_name STRING Sí (campo escalar directo)
geo RECORD ❌ No (oculta country, city, etc.)
device RECORD ❌ No (oculta category, mobile_model_name, etc.)
event_params ARRAY<STRUCT<...>> ❌ No (solo describe la firma abstracta de la estructura)
user_properties ARRAY<STRUCT<...>> ❌ No (solo describe la firma abstracta de la estructura)

COLUMNS etiqueta los structs simplemente como RECORD o desglosa la firma tipológica del array sin mostrar qué campos residen en su interior, impidiendo confeccionar un mapa funcional de telemetría.

COLUMN_FIELD_PATHS: la solución para structs, la barrera con arrays

Para superar esta limitación en tipos estructurados, BigQuery ofrece la vista INFORMATION_SCHEMA.COLUMN_FIELD_PATHS. Esta vista navega de forma recursiva por los structs y devuelve la ruta completa de cada subcampo.

Gracias a COLUMN_FIELD_PATHS, un registro como geo desglosa automáticamente sus miembros: geo.continent, geo.country, geo.region, geo.city, etc., cada uno con su tipo específico (STRING).

Sin embargo, aquí surge la barrera arquitectónica de los event_params y las user_properties:

Esquema físico DDL:
└── event_params (ARRAY<STRUCT>)
    ├── key (STRING)
    └── value (STRUCT)
        ├── string_value (STRING)
        ├── int_value (INT64)
        ├── float_value (FLOAT64)
        └── double_value (FLOAT64)

COLUMN_FIELD_PATHS solo puede leer lo que está fijado en los metadatos de la tabla. Para BigQuery, event_params contiene únicamente dos columnas físicas: key y value. El catálogo desconoce por completo si dentro de las filas has enviado page_location, click_text, scroll_depth o transaction_id.

Para conocer los nombres y tipos reales de tus variables personalizadas, no basta con leer los metadatos estáticos: es indispensable interrogar a los datos mismos persistidos en la partición.


Cómo es la tabla de eventos de GA4: los tres niveles de datos

Para estructurar una consulta que unifique metadatos y variables dinámicas, es fundamental clasificar los campos de la tabla events_YYYYMMDD en tres niveles arquitectónicos diferenciados:

 TABLA DE EVENTOS DE GA4
NIVEL 1: Columnas Planas event_date, event_name, etc
NIVEL 2: Structs Nativos Fijos geo.*, device.*, traffic_source.*
 NIVEL 3: Arrays Dinámicos (EAV) event_params.*, user_properties.*

 

 

Nivel 1: Columnas planas de evento y sesión

Son campos escalares tradicionales que guardan un dato directo por cada fila (evento registrado). Entre ellos destacan:

  • event_date: Fecha del evento en formato YYYYMMDD (STRING).
  • event_timestamp: Marca de tiempo en microsegundos UNIX (INT64).
  • event_name: Nombre canónico del evento (STRING).
  • user_pseudo_id: Identificador anónimo de cliente / cookie _ga (STRING).
  • user_id: Identificador propio de usuario autenticado (STRING).

Estos campos se obtienen directamente consultando INFORMATION_SCHEMA.COLUMN_FIELD_PATHS filtrando aquellos registros que no contienen puntos en su ruta ni forman parte de tipos complejos.

Nivel 2: Structs de metadatos nativos (geo, device, traffic_source)

Los structs nativos son objetos JSON embebidos cuyo esquema es rígido y definido por Google. Cada propiedad hija tiene una posición fija en el registro:

  • geo: continent, country, region, city, metro, sub_continent.
  • device: category, mobile_brand_name, mobile_model_name, operating_system, operating_system_version, web_info.browser.
  • traffic_source: name, medium, source.
  • privacy_info: analytics_storage, ads_storage, uses_transient_token.

Al ser esquemas estáticos, COLUMN_FIELD_PATHS los expone con su jerarquía completa, por lo que no requieren desanidación manual en el motor de consulta.

Nivel 3: Arrays repetidos dinámicos (event_params y user_properties)

Constituyen el núcleo de la personalización en GA4. En lugar de alterar el esquema de la tabla cada vez que un analista crea una dimensión en Google Tag Manager, GA4 utiliza un patrón Entity-Attribute-Value (EAV) dentro de un array de registros:

  • event_params: Parámetros contextuales del evento (dimensiones y métricas como page_location, engagement_time_msec, search_term).
  • user_properties: Rasgos persistentes vinculados al usuario (como crm_status, membership_tier, preferred_language).

Para extraer qué claves existen y qué tipo de datos albergan, es imprescindible recurrir a la operación relacional de aplanado. Ya analizamos en detalle cómo consultar qué parámetros residen dentro de un campo anidado y la mecánica profunda de cómo desanidar parámetros de eventos con UNNEST en BigQuery. Aplicaremos ese principio para construir nuestro inventario.

Nota (Alcance de esta consulta): En esta entrega nos enfocamos exclusivamente en las dimensiones y métricas de eventos (event_params) y de usuario (user_properties). El array items y sus correspondientes items.item_params obedecen a la ontología de comercio electrónico mejorado y serán abordados en una guía monográfica dedicada.


Consulta SQL para extraer el diccionario de datos completo

La siguiente consulta SQL unifica en una sola vista tabular todos los campos de tu tabla de GA4: columnas planas, subcampos de structs nativos, parámetros de evento y propiedades de usuario. Además, evalúa la consistencia de tipos de cada parámetro.

Sintaxis y placeholders del proyecto

Antes de ejecutar el código en la consola de BigQuery, debes sustituir los marcadores de posición destacados en mayúsculas:

Marcador de Posición Descripción Ejemplo de Sustitución
TU_PROYECTO ID del proyecto de Google Cloud Platform donde reside el dataset. mi-empresa-analytics-prod
analytics_XXXXXXXXX Nombre del dataset exportado por GA4 (contiene el ID de la propiedad). analytics_284719203
events_YYYYMMDD Partición de fecha específica que deseas auditar. events_20261030

Importante (Optimización de costes y rendimiento): Se recomienda apuntar a una partición diaria cerrada (events_YYYYMMDD), por ejemplo del día anterior, en lugar de utilizar el comodín events_*. Consultar una partición única procesa solo unos pocos megabytes o gigabytes y ofrece una radiografía exacta del tracking activo.

Código SQL: unión de metadatos y desanidación de parámetros

La consulta utiliza cuatro Common Table Expressions (CTEs), armonizando todos los tipos a la nomenclatura oficial de BigQuery en mayúsculas (STRING, INT64, FLOAT64, RECORD, MIXED):

WITH 
-- 1. Campos planos nativos (Nivel 1)
plain_fields AS (
  SELECT
    'PLAIN_FIELD' AS scope,
    field_path AS field_name,
    UPPER(data_type) AS data_type,
    FALSE AS has_mixed_type_issue
  FROM `TU_PROYECTO.analytics_XXXXXXXXX.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS`
  WHERE table_name = 'events_YYYYMMDD'
    AND field_path NOT LIKE '%.%'
    AND data_type NOT LIKE 'STRUCT%'
    AND data_type NOT LIKE 'ARRAY%'
),

-- 2. Subcampos de structs nativos fijos (Nivel 2)
struct_fields AS (
  SELECT
    'STRUCT_FIELD' AS scope,
    field_path AS field_name,
    UPPER(data_type) AS data_type,
    FALSE AS has_mixed_type_issue
  FROM `TU_PROYECTO.analytics_XXXXXXXXX.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS`
  WHERE table_name = 'events_YYYYMMDD'
    AND field_path LIKE '%.%'
    AND field_path NOT LIKE 'event_params%'
    AND field_path NOT LIKE 'user_properties%'
    AND field_path NOT LIKE 'items%'
    AND data_type NOT LIKE 'STRUCT%'
    AND data_type NOT LIKE 'ARRAY%'
),

-- 3. Parámetros dinámicos de eventos (Nivel 3)
raw_event_params AS (
  SELECT
    ep.key AS param_key,
    CASE
      WHEN ep.value.string_value IS NOT NULL THEN 'STRING'
      WHEN ep.value.int_value IS NOT NULL THEN 'INT64'
      WHEN ep.value.float_value IS NOT NULL OR ep.value.double_value IS NOT NULL THEN 'FLOAT64'
      ELSE 'NULL'
    END AS detected_type
  FROM `TU_PROYECTO.analytics_XXXXXXXXX.events_YYYYMMDD`,
  UNNEST(event_params) AS ep
),
arrays_event_params AS (
  SELECT
    'EVENT_PARAM' AS scope,
    param_key AS field_name,
    IF(COUNT(DISTINCT detected_type) > 1, 'MIXED', ANY_VALUE(detected_type)) AS data_type,
    COUNT(DISTINCT detected_type) > 1 AS has_mixed_type_issue
  FROM raw_event_params
  WHERE detected_type != 'NULL'
  GROUP BY ALL
),

-- 4. Propiedades dinámicas de usuario (Nivel 3)
raw_user_properties AS (
  SELECT
    up.key AS prop_key,
    CASE
      WHEN up.value.string_value IS NOT NULL THEN 'STRING'
      WHEN up.value.int_value IS NOT NULL THEN 'INT64'
      WHEN up.value.float_value IS NOT NULL OR up.value.double_value IS NOT NULL THEN 'FLOAT64'
      ELSE 'NULL'
    END AS detected_type
  FROM `TU_PROYECTO.analytics_XXXXXXXXX.events_YYYYMMDD`,
  UNNEST(user_properties) AS up
),
arrays_user_properties AS (
  SELECT
    'USER_PROPERTY' AS scope,
    prop_key AS field_name,
    IF(COUNT(DISTINCT detected_type) > 1, 'MIXED', ANY_VALUE(detected_type)) AS data_type,
    COUNT(DISTINCT detected_type) > 1 AS has_mixed_type_issue
  FROM raw_user_properties
  WHERE detected_type != 'NULL'
  GROUP BY ALL
)

-- Consolidación y salida estructurada
SELECT * FROM plain_fields
UNION ALL
SELECT * FROM struct_fields
UNION ALL
SELECT * FROM arrays_event_params
UNION ALL
SELECT * FROM arrays_user_properties
ORDER BY scope, field_name;

Muestra de resultados esperados

Al ejecutar la consulta obtendrás una tabla normalizada que actúa como el inventario exhaustivo de tu implementación:

scope field_name data_type has_mixed_type_issue
EVENT_PARAM engagement_time_msec INT64 false
EVENT_PARAM ga_session_id INT64 false
EVENT_PARAM page_location STRING false
EVENT_PARAM page_id MIXED true
EVENT_PARAM scroll_depth INT64 false
PLAIN_FIELD event_date STRING false
PLAIN_FIELD event_name STRING false
PLAIN_FIELD user_pseudo_id STRING false
STRUCT_FIELD device.category STRING false
STRUCT_FIELD device.web_info.browser STRING false
STRUCT_FIELD geo.country STRING false
USER_PROPERTY customer_tier STRING false

Detección de tipos mixtos e inconsistencias del dataLayer

Uno de los aportes más críticos de esta consulta para los equipos de gobernanza de datos y analítica digital es la columna has_mixed_type_issue.

Cómo armonizar string_value, int_value y float_value

En el export nativo de GA4, la estructura value de los arrays event_params y user_properties no almacena un campo genérico de texto; contiene cuatro columnas mutuamente excluyentes:

  • string_value: Se rellena si el valor fue recibido como cadena de texto.
  • int_value: Se rellena si el valor fue procesado como un entero de 64 bits.
  • float_value: Se rellena si el valor contiene decimales de precisión simple.
  • double_value: Se rellena si el valor contiene decimales de doble precisión.

Nuestra sentencia CASE clasifica cada ocurrencia en un tipo SQL estándar:

CASE
  WHEN ep.value.string_value IS NOT NULL THEN 'STRING'
  WHEN ep.value.int_value IS NOT NULL THEN 'INT64'
  WHEN ep.value.float_value IS NOT NULL OR ep.value.double_value IS NOT NULL THEN 'FLOAT64'
  ELSE 'NULL'
END

Identificación de colisiones de tipo y alerta de gobernanza de datos

En una implementación limpia, un parámetro determinado (por ejemplo, ga_session_id o transaction_id) debe mantener siempre el mismo tipo de datos a lo largo de todos los eventos. Sin embargo, en auditorías reales es común encontrar que un mismo parámetro se almacena a veces como número (INT64) y a veces como texto (STRING).

¿Por qué ocurre esto?

  1. Definición del tipo de variable en la recogida con Tag Manager: Una etiqueta de evento envía la variable leída del dataLayer como un número puro (10492), mientras que otra etiqueta en una plantilla distinta la envía interpolada como string ("10492").
  2. Inconsistencias en el dataLayer del CMS: La capa de datos frontend emite variables sin tipar estrictamente en JavaScript, alternando entre strings y enteros según la página o el flujo de navegación.

Cuando agrupamos por param_key mediante la instrucción GROUP BY ALL, la función condicional:

IF(COUNT(DISTINCT detected_type) > 1, 'MIXED', ANY_VALUE(detected_type))

Detecta si existen múltiples tipos para la misma clave. Si la cuenta es mayor que 1, el parámetro recibe el tipo MIXED y activa la bandera has_mixed_type_issue = TRUE.

Consulta de diagnóstico para parámetros con tipo mixto

Si la consulta general reporta parámetros con has_mixed_type_issue = true, puedes aislar las colisiones y contar el volumen exacto de registros afectados con esta consulta de diagnóstico:

SELECT
  ep.key AS conflicting_param,
  CASE
    WHEN ep.value.string_value IS NOT NULL THEN 'STRING'
    WHEN ep.value.int_value IS NOT NULL THEN 'INT64'
    WHEN ep.value.float_value IS NOT NULL OR ep.value.double_value IS NOT NULL THEN 'FLOAT64'
  END AS observed_type,
  COUNT(1) AS event_count,
  ANY_VALUE(event_name) AS sample_event
FROM `TU_PROYECTO.analytics_XXXXXXXXX.events_YYYYMMDD`,
UNNEST(event_params) AS ep
WHERE ep.key IN (
  -- Subconsulta que localiza los parámetros polimórficos
  SELECT ep_sub.key
  FROM `TU_PROYECTO.analytics_XXXXXXXXX.events_YYYYMMDD`,
  UNNEST(event_params) AS ep_sub
  GROUP BY ep_sub.key
  HAVING COUNT(DISTINCT CASE
    WHEN ep_sub.value.string_value IS NOT NULL THEN 'STRING'
    WHEN ep_sub.value.int_value IS NOT NULL THEN 'INT64'
    WHEN ep_sub.value.float_value IS NOT NULL OR ep_sub.value.double_value IS NOT NULL THEN 'FLOAT64'
  END) > 1
)
GROUP BY ALL
ORDER BY conflicting_param, event_count DESC;

Esta consulta te mostrará exactamente qué tipo predomina y en qué eventos (sample_event) se produce la desviación, permitiendo al equipo técnico corregir el tag en GTM o normalizar el dataLayer antes de que corrompa modelos analíticos aguas abajo.


Automatización y gobierno: de la consulta manual a Scheduled Queries

Ejecutar esta consulta de forma aislada en la consola resuelve una duda puntual. Sin embargo, su valor estratégico se multiplica cuando se transforma en un proceso de auditoría continua.

Cómo convertir esta auditoría en una tabla de diccionario viva

Las implementaciones de tracking son dinámicas: nuevos despliegues de código, campañas de marketing y cambios en el CMS incorporan nuevos parámetros con frecuencia, en muchos casos sin previa notificación al equipo de analítica.

Para mantener el esquema de datos bajo control, puedes materializar la salida de esta consulta en una tabla estática de diccionario (ga4_data_dictionary) y programar su actualización (diaria, semanal, mensual,...). En nuestra guía sobre cómo programar consultas en BigQuery mediante Scheduled Queries, detallamos el procedimiento paso a paso para configurar ejecuciones desatendidas con BigQuery Data Transfer Service.

Al programar esta consulta para ejecutarse periódicamente sobre la partición recién cerrada (events_ del día anterior), consigues:

  1. Detección automática de variables no documentadas: Identificar la aparición de nuevas dimensiones creadas en GTM que no consten en el plan de medición.
  2. Monitorización de calidad de datos: Crear paneles en Looker Studio o alertas automáticas cuando haya una nueva variable o has_mixed_type_issue sea verdadero.
  3. Optimización de costes en reporting: se puede consultar la tabla de diccionario en milisegundos para verificar la sintaxis de un parámetro sin necesidad de escanear la tabla entera de eventos.

Para profundizar en la explotación técnica del export de GA4 y de BigQuery, te recomendamos consultar las siguientes artículosulos de nuestro blog:

David Hernández

Digital Analyst


Soy un apasionado de los datos, la tecnología, la ciencia y el aprendizaje continuo.

Trabajo como Digital Analyst desde 2020.

Participo en el ciclo completo de los datos: definición e implementación de recolección, procesado y distribución, análisis de datos y reporting.

Publicaciones más recientes

Publicaciones más visitadas

Submit Your Comment