← Portafolio
Google Analytics · Google Cloud · BigQuery · SQL

Ecommerce Performance Insights

¿Puede esta tienda saber qué productos convierten mejor, con la medición que tiene hoy?

4.3Meventos
92tablas diarias
3meses
Ecommerce Performance Insights

1. El problema

La Google Merchandise Store (https://shop.merch.google/) vende merchandising de Google, Android y YouTube.

Las preguntas que llevan a mirar estos datos son tres: - ¿Cuál es la conversión del ecommerce? - ¿Los productos que más facturan en volumen son los que mejor convierten? - ¿Qué categoría convierte mejor? ¿Es la que más factura?

Para poder responder estas preguntas se hace dos cosas. Primero, se audita la tabla de eventos para saber qué se puede medir y qué no. Después, con lo que sobrevive a esa auditoría, se intenta responder las preguntas.

Sin embargo, para poder responder a las preguntas de negocio, no podemos ir con la premisa que los datos vienen limpios, tenemos que responder antes una pregunta importante para poder analizar los datos:

¿Puede esta tienda saber qué productos convierten mejor, con la medición que tiene hoy?

2. Los datos

Origen bigquery-public-data.ga4_obfuscated_sample_ecommerce
Qué es Datos ficticios de GA4 de la Google Merchandise Store publicados por Google
Grano Un evento por fila, con los parámetros anidados en un ARRAY
Periodo 1 nov 2020 → 31 ene 2021
Volumen 4,295,584 eventos , 17 tipos de evento , 270,154 usuarios
Estructura 92 tablas diarias (events_20201101events_20210131)
Herramientas GA4, BigQuery, SQL

Es un dataset público de demostración, no un cliente. Todo lo que hay en esta página se puede reproducir sin acceso a ningún dato privado.

3. Diagnóstico

Antes de armar un reporte debemos tener un contexto de los datos y del negocio. Esto debido a que no siempre los datos vienen limpios y además es necesario saber cómo están estructurados sus datos.

La documentación del esquema de la tabla que vamos a analizar lo encontramos en el siguiente artículo: https://support.google.com/analytics/answer/ . Sin embargo, esto solo nos dice como está estructurado es esquema. Lo que debemos revisar son los datos específicos del negocio.

Los eventos

3.1 ¿Qué está midiendo la tienda?

Para saber si la tienda puede responder la pregunta de negocio principal, primero hay que ver qué mide. Y conviene no llegar buscando eventos concretos: en GA4 los nombres de evento los decide quien implementó la medición, así que no hay garantía de que existan los estándar, ni de que los que existan hagan lo que su nombre sugiere.

Lo que se pide es el mapa completo, sin filtrar.

SELECT
  event_name,
  COUNT(*) AS eventos
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
GROUP BY event_name
ORDER BY eventos DESC
event_name eventos
page_view 1,350,428
user_engagement 1,058,721
scroll 493,072
view_item 386,068
session_start 354,970
first_visit 257,462
view_promotion 190,104
add_to_cart 58,543
begin_checkout 38,757
select_item 31,007
view_search_results 26,172
add_shipping_info 19,722
add_payment_info 13,899
select_promotion 9,450
purchase 5,692
click 1,446
view_item_list 71

Diecisiete tipos de evento, con los nombres estándar de ecommerce de GA4. Y entre ellos está el embudo entero: view_itemadd_to_cartbegin_checkoutpurchase.

Eso es más de lo que suele haber. Sobre el papel, la tienda tiene lo que hace falta para responder la pregunta.

3.2 ¿Qué eventos traen productos?

La pregunta es sobre productos, así que hay que construir el universo de productos antes de medir nada. Y para eso hay que saber de dónde salen: en GA4 los productos no son columnas, viajan anidados en un ARRAY llamado items que no todos los eventos llevan.

Hasta el momento podemos utilizaritem_id como la clave del producto.

SELECT
  event_name,
  COUNT(*)                  AS filas,
  COUNT(DISTINCT i.item_id) AS productos_unicos
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
  AND i.item_id IS NOT NULL AND i.item_id NOT IN ('(not set)', '')
GROUP BY event_name
ORDER BY filas DESC
event_name filas productos_unicos
view_item 2,748,237 426
add_to_cart 667,282 805
select_item 326,644 640
begin_checkout 76,047 796
view_promotion 16,843 1
purchase 15,555 809
select_promotion 975 1
view_item_list 508 63

Ocho eventos traen productos, no los cuatro del embudo. select_item aporta 640 y hasta view_promotion trae uno. Si el universo se sacara solo del embudo se dejarían fuera eventos que sí identifican productos.

Hay 426 productos en las vistas y **809 en las compras**. Se compraron el doble de productos de los que se vieron, y eso no puede ser: nadie compra lo que no está en el catálogo. Uno de los dos números no está contando productos.

Los productos

3.3 ¿Cuántos productos hay en total?

Aun con la contradicción anterior sin resolver, el conteo global es el que fija la base sobre la que se trabaja.

SELECT COUNT(DISTINCT i.item_id) AS productos_en_total
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
  AND i.item_id IS NOT NULL AND i.item_id NOT IN ('(not set)', '')
productos en total
1,393

Mil trescientos noventa y tres productos con actividad en tres meses. Es una cifra alta para una tienda de merchandising, pero no imposible — y de momento no hay con qué contrastarla.

Conviene dejar dicho qué es y qué no es este número. No es el catálogo de la tienda: solo aparece lo que tuvo algún evento en la ventana analizada. El stock es dinámico, así que un producto que existía y nadie miró no está aquí, y no hay forma de saberlo desde estos datos. Como no hay tabla de dimensión de productos, esto es lo más cerca que se puede estar del catálogo.

3.4 ¿Están completos esos productos?

Un producto necesita como mínimo un identificador y un nombre. Toca comprobar que los 1,393 tengan las dos cosas.

No basta con preguntar si tienen nombre alguna vez: un identificador puede traerlo en una fila y no traerlo en las otras diez mil, y eso ya contaría como "tiene nombre". Hay que separar los tres casos.

WITH por_id AS (
  SELECT
    i.item_id AS id,
    COUNTIF(i.item_name IS NULL OR i.item_name IN ('(not set)',''))          AS sin_nombre,
    COUNTIF(i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')) AS con_nombre
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND i.item_id IS NOT NULL AND i.item_id NOT IN ('(not set)','')
  GROUP BY id
)
SELECT
  COUNT(*)                                   AS ids_totales,
  COUNTIF(sin_nombre = 0)                    AS siempre_con_nombre,
  COUNTIF(sin_nombre > 0 AND con_nombre > 0) AS a_veces_sin_nombre,
  COUNTIF(con_nombre = 0)                    AS nunca_con_nombre
FROM por_id
ids totales siempre con nombre a veces sin nombre nunca con nombre
1,393 1,392 1 0

La comprobación inversa —agrupar por item_name y contar los que no traen identificador— no encuentra ninguno: todos los nombres vienen siempre con su id.

1,392 identificadores traen nombre siempre. Uno lo trae en una sola de sus 17,818 filas — y ese uno no es un producto: es `fall_campaign`, una campaña de marketing que entra por los eventos de promoción.

Ninguno se queda sin nombre del todo, así que el filtro no descarta ningún producto. Lo que descarta la comprobación es otra cosa: una entidad que nunca debió estar en la base.

Entró porque en la pregunta 2 se admitió cualquier evento con items, y los de promoción no traen productos sino campañas. Es la decisión de alcance que faltaba declarar: una promoción no es un producto. Fuera view_promotion y select_promotion, la base queda en 1,392.

3.5 ¿item_id e item_name cuentan lo mismo?

Con los eventos de promoción fuera, la base baja a 1,392 identificadores. Sobre esa base ya limpia toca la comprobación siguiente: hay un segundo campo que también parece identificar productos, y si los dos identifican lo mismo tienen que dar el mismo número.

SELECT
  COUNT(DISTINCT i.item_id)   AS identificadores,
  COUNT(DISTINCT i.item_name) AS nombres
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
  AND event_name NOT IN ('view_promotion', 'select_promotion')
  AND i.item_id   IS NOT NULL AND i.item_id   NOT IN ('(not set)', '')
  AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)', '')
identificadores nombres
1,392 429

No cuentan lo mismo. Como cada fila es una pareja de identificador y nombre, comparar los dos totales ya dice de qué lado se rompe la relación:

Comparación Qué se deduce
Más ids que nombres Algún nombre tiene varios ids
Más nombres que ids Algún id tiene varios nombres
Iguales Nada

Así que item_id no identifica productos: identifica alguna otra cosa, más fina, que se repite dentro del mismo producto.

La base de 1,392 nunca fueron productos. `item_name` sí se comporta como clave —no se multiplica— y da **429**. Esa es la nueva base de análisis.

Se ve mejor con un producto cualquiera de los 416:

SELECT DISTINCT i.item_name, i.item_id
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
  AND i.item_name = 'Google Land & Sea French Terry Sweatshirt'
ORDER BY i.item_id
item_name item_id
Google Land & Sea French Terry Sweatshirt 9199117
Google Land & Sea French Terry Sweatshirt 9199201
Google Land & Sea French Terry Sweatshirt 9199204
Google Land & Sea French Terry Sweatshirt 9199205
Google Land & Sea French Terry Sweatshirt GGCOGXXX1609
Google Land & Sea French Terry Sweatshirt GGOEGXXX1615
hasta 14

Una sudadera, catorce identificadores. Agrupando por item_id serían catorce productos en el análisis; agrupando por nombre, uno.

3.6 ¿Están los 429 productos en los eventos que vamos a analizar?

La pregunta de negocio necesita, para cada producto, cuánta gente lo vio y cuánta lo compró. De poco sirve tener 429 productos si no aparecen en las dos puntas del embudo.

WITH por_producto AS (
  SELECT
    i.item_name AS producto,
    MAX(IF(event_name = 'view_item',      1, 0)) AS vista,
    MAX(IF(event_name = 'add_to_cart',    1, 0)) AS carrito,
    MAX(IF(event_name = 'begin_checkout', 1, 0)) AS checkout,
    MAX(IF(event_name = 'purchase',       1, 0)) AS compra
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
    AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
  GROUP BY producto
)
SELECT vista, carrito, checkout, compra, COUNT(*) AS productos
FROM por_producto
GROUP BY vista, carrito, checkout, compra
ORDER BY productos DESC
vista carrito checkout compra productos
379
24
8
6
6
4
2

379 de 429 recorren el embudo entero. Otros 24 se quedan a mitad —se ven, se añaden al carrito y nunca se compran—, y eso es normal en cualquier tienda.

Pero hay una fila que no puede existir: 8 productos con compra y sin vista. Nadie compra algo que no vio.

**8 productos aparecen comprados sin haberse visto nunca.** Eso no puede ocurrir: nadie compra lo que no vio. O falta la vista, o el producto se llama distinto en cada mitad del embudo.

3.7 ¿Con qué productos nos quedamos?

Los 8 imposibles no se pueden analizar: sin vistas no hay denominador, así que no tienen conversión. Antes de excluirlos conviene verlos, porque un producto que se vende sin registrar visitas es un problema de medición que alguien debería arreglar.

WITH por_producto AS (
  SELECT
    i.item_name AS producto,
    COUNTIF(event_name = 'view_item') AS vistas,
    COUNTIF(event_name = 'purchase')  AS compras
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
    AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
  GROUP BY producto
)
SELECT producto, vistas, compras
FROM por_producto
WHERE vistas = 0 AND compras > 0
ORDER BY compras DESC
producto vistas compras
Google F/C Longsleeve Charcoal 0 164
Google Unisex Eco Tee Black 0 156
Google F/C Longsleeve Ash 0 143
Womens Google Striped LS 0 89
Unisex Google Pocket Tee Grey 0 52
Google Summer19 Crew Grey 0 24
Youth Jumbo Print Tee White 0 15
Unisex Google Jumbo Print Tee White 0 2

164 compras sin una sola visita registrada, y las ocho son ropa.

La hipótesis más razonable es que en la mitad alta del embudo estos productos se llamen de otra forma —Womens Google Striped LS frente a algún Google Women's Striped L/S— y que el nombre, igual que antes el identificador, tampoco los una. Con estos datos no se puede confirmar: haría falta el catálogo para saber si dos nombres son el mismo artículo.

**8 productos aparecen comprados sin haberse visto nunca.** Queda como limitación declarada, no como problema resuelto: la causa probable es que cambien de nombre entre etapas, pero no hay forma de comprobarlo desde el dato.

Aun así el nombre sigue siendo la clave. Con item_id el problema no sería de ocho productos sino de 416, y además inventaría productos que no existen. Cambiar de clave no arregla esto: lo empeora.

Para el análisis se quitan, y la base queda así:

productos analizables excluidos
429 421 8

Esas 645 compras —un 4% del total— quedan fuera de cualquier cálculo por producto. No es un error del análisis, es una limitación de los datos, y se declara.

Las categorías

3.8 ¿Cada producto tiene una sola categoría?

Las preguntas de negocio no son solo por producto, también por categoría. Lo ideal es que cada producto tenga una y solo una.

Lo natural sería empezar descartando los productos sin categoría, pero todavía no se puede: eso daría por hecho que existe una categoría de catálogo y que sabemos dónde está. En este punto item_category es un campo del que no sabemos nada.

Primero hay que comprobar si es fiable; el descarte viene después y depende de lo que salga.

WITH base AS (
  SELECT DISTINCT i.item_name AS producto
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name = 'view_item'
    AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
),
cat AS (
  SELECT i.item_name AS producto, COUNT(DISTINCT i.item_category) AS categorias
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
    AND i.item_name     IS NOT NULL AND i.item_name     NOT IN ('(not set)','')
    AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
  GROUP BY producto
)
SELECT categorias, COUNT(*) AS productos
FROM cat JOIN base USING (producto)
GROUP BY categorias
ORDER BY categorias
categorías productos
1 1
2 a 4 28
5 a 7 130
8 71
9 a 12 166
13 a 17 24

Solo **1 de 421** productos tiene una única categoría. Lo habitual son ocho, y uno llega a diecisiete. Tal cual está, `item_category` no sirve para agrupar productos.

3.9 ¿Ocurre lo mismo en todos los eventos?

Un campo puede comportarse distinto según quién lo escribe. Antes de descartarlo conviene mirar evento por evento.

SELECT
  event_name,
  COUNT(*)                AS productos,
  COUNTIF(categorias = 1) AS con_una_sola,
  MAX(categorias)         AS maximo
FROM (
  SELECT event_name, i.item_name AS producto, COUNT(DISTINCT i.item_category) AS categorias
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
    AND i.item_name     IS NOT NULL AND i.item_name     NOT IN ('(not set)','')
    AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
  GROUP BY event_name, producto
)
GROUP BY event_name
ORDER BY productos DESC
evento productos con una sola categoría máximo
view_item 420 6 17
add_to_cart 412 10 15
purchase 386 377 2
begin_checkout 382 373 2

En purchase y begin_checkout la categoría sí es única por producto. En view_item y add_to_cart casi nunca.

Para saber por qué hay que mirar qué guarda de manera más frecuente cada uno:

SELECT
  event_name,
  i.item_category,
  COUNT(*) AS filas
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
  AND event_name IN ('view_item','add_to_cart','begin_checkout','purchase')
  AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
GROUP BY event_name, i.item_category
QUALIFY ROW_NUMBER() OVER (PARTITION BY event_name ORDER BY filas DESC) <= 2
ORDER BY filas DESC
evento item_category filas
view_item Home/Apparel/Men's / Unisex/ 462,115
view_item Home/Sale/ 409,702
add_to_cart Home/Apparel/Men's / Unisex/ 118,125
add_to_cart Home/Sale/ 111,599
begin_checkout Apparel 21,026
begin_checkout New 7,524
purchase Apparel 4,984
purchase Campus Collection 1,497

view_item y add_to_cart guardan la ruta de navegación por la que llegó el usuario. Un producto alcanzable desde ofertas, desde hombre y desde Apparel acumula tres rutas, y ocho de media. begin_checkout y purchase guardan una etiqueta plana.

Queda una duda razonable antes de fiarse de la etiqueta plana: en la página de compra no hay ruta de navegación, así que quizá ese valor no describa el producto sino la página. Si fuera así, todos los productos de una misma compra tendrían la misma categoría.

WITH productos_comprados AS (
  SELECT
    CONCAT(user_pseudo_id, '-', CAST(event_timestamp AS STRING)) AS compra,
    i.item_category                                              AS categoria
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name = 'purchase'
    AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
),
por_compra AS (
  SELECT
    compra,
    COUNT(*)                  AS productos,
    COUNT(DISTINCT categoria) AS categorias
  FROM productos_comprados
  GROUP BY compra
)
SELECT
  COUNT(*)                AS compras_con_2_o_mas_productos,
  COUNTIF(categorias = 1) AS todos_la_misma_categoria,
  COUNTIF(categorias > 1) AS con_categorias_distintas,
  MAX(categorias)         AS maximo_en_una_compra
FROM por_compra
WHERE productos >= 2
compras con 2+ productos mima_categoría categorías_distintas máximo
3,376 608 2,768 12

No es de la página: 2,768 compras llevan categorías distintas dentro del mismo evento, y una llega a doce. Si el valor viniera de la página, las 3,399 tendrían una sola.

`item_category` guarda **dos cosas distintas con el mismo nombre**. En `view_item` y `add_to_cart`, la ruta de navegación del usuario. En `begin_checkout` y `purchase`, la categoría del catálogo — y es del producto, no de la página.

3.10 ¿Todos los productos tienen categoría de catálogo?

La categoría buena solo aparece en begin_checkout y purchase. Un producto que se ve pero nunca llega a esas etapas no la tiene en ninguna parte.

WITH analizables AS (
  SELECT DISTINCT i.item_name AS producto
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name = 'view_item'
    AND i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
),
con_categoria AS (
  SELECT DISTINCT i.item_name AS producto
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name IN ('begin_checkout','purchase')
    AND i.item_name     IS NOT NULL AND i.item_name     NOT IN ('(not set)','')
    AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
)
SELECT
  (SELECT COUNT(*) FROM analizables)                                     AS productos_analizables,
  (SELECT COUNT(*) FROM analizables JOIN con_categoria USING (producto)) AS con_categoria_de_catalogo,
  (SELECT COUNT(*) FROM analizables
   WHERE producto NOT IN (SELECT producto FROM con_categoria))           AS sin_categoria_de_catalogo
productos analizables con categoría de catálogo sin categoría de catálogo
421 390 31

31 de los 421 productos no tienen categoría de catálogo en ningún evento. **Cualquier análisis por categoría se hace sobre 390 productos, no sobre 421.**

No se pueden recuperar desde estos datos. La mayoría sí tiene valores de categoría en item_category dentro de view_item, pero esos son rutas de navegación —la sección anterior lo demostró— y no describen al producto. No es una categoría que se esté desperdiciando: es otro campo que no sirve para esto.

Las vistas de producto

3.11 ¿Qué significa una vista?

Ya sabemos qué productos analizar. Falta comprobar qué mide el evento que va a hacer de denominador en todas las preguntas de conversión.

El supuesto es simple: view_item es alguien entrando a ver un producto. Si eso es cierto, cada evento debería traer exactamente un producto.

SELECT
  ARRAY_LENGTH(items) AS productos_en_el_evento,
  COUNT(*)            AS eventos
FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
  AND event_name = 'view_item'
GROUP BY productos_en_el_evento
ORDER BY productos_en_el_evento
productos en el evento eventos
0 144,192
1 8,726
2 a 11 13,517
12 219,633

Dos cosas rompen el supuesto por separado.

Solo 8,726 eventos traen un producto, un 2% del total. En el 98% restante, o no hay ninguno o hay doce.

El tope está en 12 y ahí se acumula el 57%. Un número redondo repetido en 219,633 eventos no es comportamiento de usuario: es el tamaño de algo.

`view_item` no significa "alguien vio este producto". Un evento trae doce productos o ninguno, así que **contar sus filas no cuenta visitas a fichas de producto**.

4. Las respuestas

El diagnóstico deja una base clara y un límite duro. Estas son las tres preguntas del principio, con lo que se puede afirmar de cada una.

4.1 ¿Cuál es la conversión del negocio?

Sí se puede. Es la única de las tres que no necesita saber qué producto se vio, así que el defecto de view_item no la afecta.

Antes de calcularla hay que comprobar que el evento de compra no se repita, porque si se repite el numerador queda inflado.

WITH e AS (
  SELECT
    CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value FROM UNNEST(event_params)
           WHERE key = 'ga_session_id') AS STRING)) AS sesion,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id') AS transaccion
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name = 'purchase'
)
SELECT
  COUNT(*)                                          AS eventos_de_compra,
  COUNT(DISTINCT transaccion)                       AS transacciones_distintas,
  COUNTIF(transaccion IS NULL)                      AS sin_transaction_id
FROM e
eventos de compra transacciones distintas sin transaction_id
5,692 4,466 906

El evento de compra se dispara varias veces para la misma transacción — recargar la página de confirmación basta. De 5,692 eventos salen 4,466 transacciones. **Todo cálculo de ingresos o de compras hay que deduplicar por `transaction_id`.**

Los 906 eventos sin transaction_id no se pueden deduplicar y se conservan tal cual. Con esa regla quedan 5,372 transacciones.

Ya se puede calcular. Hay dos formas de expresarla y no dicen lo mismo:

WITH transacciones AS (
  SELECT
    CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value FROM UNNEST(event_params)
           WHERE key = 'ga_session_id') AS STRING)) AS sesion,
    COALESCE(
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id'),
      CONCAT(user_pseudo_id, '-', CAST(event_timestamp AS STRING))
    ) AS transaccion,
    event_timestamp
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name = 'purchase'
  QUALIFY ROW_NUMBER() OVER (PARTITION BY transaccion ORDER BY event_timestamp) = 1
),
sesiones AS (
  SELECT CONCAT(user_pseudo_id, '-', CAST((SELECT value.int_value FROM UNNEST(event_params)
         WHERE key = 'ga_session_id') AS STRING)) AS sesion
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
  GROUP BY sesion
)
SELECT
  (SELECT COUNT(*) FROM sesiones)                    AS sesiones,
  (SELECT COUNT(*) FROM transacciones)               AS transacciones,
  (SELECT COUNT(DISTINCT sesion) FROM transacciones) AS sesiones_con_compra,
  ROUND((SELECT COUNT(*) FROM transacciones) /
        (SELECT COUNT(*) FROM sesiones) * 100, 2)    AS a_transacciones_por_sesion,
  ROUND((SELECT COUNT(DISTINCT sesion) FROM transacciones) /
        (SELECT COUNT(*) FROM sesiones) * 100, 2)    AS b_sesiones_que_compran
FROM sesiones LIMIT 1
cálculo resultado
A. Transacciones por sesión 5,372 / 360,129 1.49%
B. Sesiones que compran 4,848 / 360,129 1.35%

La A mide volumen: cuántas compras genera cada visita. Puede pasar de 100% si una sesión compra varias veces.

La B mide probabilidad: qué parte de las visitas acaba comprando. Nunca pasa de 100%, y es la que reporta GA4 como conversión de sesión y contra la que se comparan referencias del sector.

La diferencia entre las dos —1.49 frente a 1.35— son las 524 sesiones que hicieron más de una compra.

4.2 ¿Los productos que más facturan son los que mejor convierten?

Solo la mitad. La facturación por producto sale de purchase, que trae el ingreso y el producto, y no necesita vistas.

WITH compras AS (
  SELECT
    COALESCE(
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id'),
      CONCAT(user_pseudo_id, '-', CAST(event_timestamp AS STRING))
    ) AS transaccion,
    event_timestamp, items
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name = 'purchase'
  QUALIFY ROW_NUMBER() OVER (PARTITION BY transaccion ORDER BY event_timestamp) = 1
)
SELECT
  i.item_name                           AS producto,
  COUNT(*)                              AS unidades,
  ROUND(SUM(IFNULL(i.item_revenue, 0))) AS ingresos
FROM compras, UNNEST(items) AS i
WHERE i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
GROUP BY producto
ORDER BY ingresos DESC
LIMIT 8
producto unidades ingresos
Google Zip Hoodie F/C 243 13,152
Google Crewneck Sweatshirt Navy 209 9,878
Super G Unisex Joggers 273 9,133
Google Badge Heavyweight Pullover Black 163 8,674
Google Men's Tech Fleece Grey 107 8,668
Google Crewneck Sweatshirt Green 160 7,579
Google Sherpa Zip Hoodie Charcoal 100 6,122
Google Men's Puff Jacket Black 59 5,750

Ya asoma algo: los joggers venden más unidades que nadie —273— y quedan terceros en ingresos. El Tech Fleece vende 107, cuatro veces menos, y se queda a 465 dólares de ellos. Unidades e ingresos no ordenan igual.

Lo que no se puede es la otra mitad. La conversión por producto necesita saber cuánta gente vio cada uno, y la sección 11 demostró que view_item no mide eso.

**La pregunta no tiene respuesta con la medición actual.** Se sabe qué productos facturan más; no se sabe cuáles convierten mejor, así que no se puede decir si coinciden.

4.3 ¿Qué categoría convierte mejor? ¿Es la que más factura?

Mismo reparto. La facturación por categoría se puede, sobre los 390 productos que tienen categoría de catálogo.

WITH compras AS (
  SELECT
    COALESCE(
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id'),
      CONCAT(user_pseudo_id, '-', CAST(event_timestamp AS STRING))
    ) AS transaccion,
    event_timestamp, items
  FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
    AND event_name = 'purchase'
  QUALIFY ROW_NUMBER() OVER (PARTITION BY transaccion ORDER BY event_timestamp) = 1
),
categoria_de_catalogo AS (
  SELECT producto, categoria FROM (
    SELECT
      i.item_name     AS producto,
      i.item_category AS categoria,
      ROW_NUMBER() OVER (PARTITION BY i.item_name ORDER BY COUNT(*) DESC) AS rn
    FROM `bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`, UNNEST(items) AS i
    WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
      AND event_name IN ('begin_checkout','purchase')
      AND i.item_name     IS NOT NULL AND i.item_name     NOT IN ('(not set)','')
      AND i.item_category IS NOT NULL AND i.item_category NOT IN ('(not set)','')
    GROUP BY producto, categoria
  ) WHERE rn = 1
),
ventas AS (
  SELECT i.item_name AS producto, IFNULL(i.item_revenue, 0) AS ingreso
  FROM compras, UNNEST(items) AS i
  WHERE i.item_name IS NOT NULL AND i.item_name NOT IN ('(not set)','')
)
SELECT
  c.categoria,
  COUNT(DISTINCT v.producto) AS productos,
  COUNT(*)                   AS unidades,
  ROUND(SUM(v.ingreso))      AS ingresos
FROM ventas v
JOIN categoria_de_catalogo c USING (producto)
GROUP BY c.categoria
ORDER BY ingresos DESC
LIMIT 8
categoría productos unidades ingresos
Apparel 92 4,594 159,060
New 44 1,399 25,102
Bags 23 674 22,782
Uncategorized Items 13 679 21,139
Campus Collection 66 1,443 19,427
Accessories 41 1,224 17,161
Shop by Brand 17 811 17,142
Drinkware 11 673 14,976

Apparel factura seis veces más que la siguiente. Y aparece una categoría llamada Uncategorized Items que mueve 21,139 — el propio catálogo tiene productos sin clasificar, además de los 31 que no traen categoría en ningún sitio.

Misma respuesta parcial: se sabe qué categoría factura más, no cuál convierte mejor. Y la comparación se hace sobre 390 de los 421 productos, así que el reparto por categoría **no incluye a los 31 sin clasificar**.

5. Los hallazgos

Once comprobaciones, y cada una cambió algo. Se separan en dos grupos, porque el destinatario es distinto.

5.1 Lo que se resuelve analizando

Defectos del dato que se sortean eligiendo bien. Son decisiones del análisis y no requieren tocar nada del sitio.

Hallazgo Decisión
item_id no identifica productos: 416 de 429 tienen varios, uno llega a catorce Agrupar por item_name. La base pasa de 1,392 a 429
item_category guarda dos cosas: la ruta de navegación en view_item, la etiqueta del catálogo en purchase Tomar la categoría solo de begin_checkout y purchase

5.2 Lo que no se arregla desde SQL

Aquí el análisis no puede hacer nada. Son fallos de medición y quien los corrige es quien implementó el sitio.

Hallazgo Impacto
view_item no mide vistas de producto. Un evento trae doce productos o ninguno; solo el 2% trae uno Bloquea la conversión por producto y por categoría. Es el más grave
8 productos aparecen comprados sin haberse visto nunca 645 compras, un 4% del total, sin producto atribuible
31 productos no tienen categoría en ningún evento El análisis por categoría cubre 390 de 421 productos

5.3 Qué habría que arreglar, en orden

  1. Disparar view_item en la ficha de producto, con ese producto en items. Es lo único que desbloquea la conversión por producto, que es la pregunta original.
  2. Usar el mismo identificador de producto en las cuatro etapas. Hoy la mitad alta manda SKU y la baja un identificador por talla.
  3. Separar la categoría del catálogo de la ruta de navegación en dos campos distintos.

Consulta SQL