Conceptos clave

JOINs explicados para SEO (cruzar GA4 con GSC)

5 min de lectura

El problema que resuelven los JOINs

Los datos de SEO están repartidos en múltiples fuentes. Google Search Console almacena keywords, clics, impresiones y posiciones. GA4 almacena sesiones, engagement, conversiones y comportamiento del usuario. Cada fuente tiene información valiosa, pero por separado ninguna responde la pregunta más importante: qué keywords generan valor real en el sitio.

Los JOIN en SQL permiten combinar datos de dos o más tablas en una sola consulta. La conexión se establece a través de un campo común: en el caso de SEO, ese campo suele ser la URL.

Anatomía de un JOIN

La estructura básica es:

SELECT
  tabla_a.columna1,
  tabla_b.columna2
FROM
  tabla_a
  JOIN tabla_b ON tabla_a.campo_comun = tabla_b.campo_comun

La cláusula ON define la condición de unión: qué campo de la primera tabla debe coincidir con qué campo de la segunda. Solo las filas donde hay coincidencia en ambas tablas aparecen en los resultados (en el caso de INNER JOIN).

INNER JOIN: solo coincidencias

INNER JOIN (o simplemente JOIN) devuelve solo las filas donde hay coincidencia en ambas tablas. Si una URL existe en GSC pero no en GA4, esa fila no aparece. Si existe en GA4 pero no en GSC, tampoco.

-- Cruce básico: keywords de GSC con sesiones de GA4
WITH gsc AS (
  SELECT url, query, SUM(clicks) AS clics
  FROM `your-project.searchconsole.searchdata_url_impression`
  WHERE data_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY) AND CURRENT_DATE()
  GROUP BY url, query
),
ga4 AS (
  SELECT
    REGEXP_EXTRACT(
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location'),
      r'(https?://[^?#]+)'
    ) AS url_normalizada,
    COUNT(DISTINCT user_pseudo_id) AS usuarios
  FROM `your-project.analytics_XXXXXXXXX.events_*`
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY))
    AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
    AND traffic_source.medium = 'organic'
  GROUP BY url_normalizada
)
SELECT gsc.query, gsc.url, gsc.clics, ga4.usuarios
FROM gsc
INNER JOIN ga4 ON gsc.url = ga4.url_normalizada
ORDER BY gsc.clics DESC
LIMIT 50

Este patrón de CTEs separados (uno para cada fuente) seguidos de un JOIN es exactamente el que usan las queries de cruce del catálogo de Queryteca.

LEFT JOIN: todas las de la izquierda, coincidan o no

LEFT JOIN devuelve todas las filas de la tabla izquierda (la primera), y las completa con datos de la tabla derecha cuando hay coincidencia. Si no hay coincidencia, las columnas de la tabla derecha muestran NULL.

Es la opción correcta cuando se quieren ver todas las URLs de una fuente, incluyendo aquellas que no tienen datos en la otra fuente:

-- Todas las URLs de GSC, con datos de GA4 si existen
SELECT
  gsc.url,
  gsc.clics,
  IFNULL(ga4.sesiones, 0) AS sesiones_ga4
FROM gsc
LEFT JOIN ga4 ON gsc.url = ga4.url_normalizada

Las URLs de GSC que no tienen tráfico registrado en GA4 aparecerán con sesiones_ga4 = 0 (gracias a IFNULL que convierte NULL en 0). Esto es útil para detectar páginas zombi: URLs indexadas en Google que no generan tráfico real.

FULL OUTER JOIN: todo de ambas tablas

FULL OUTER JOIN devuelve todas las filas de ambas tablas, tengan o no coincidencia. Es útil para comparaciones completas, como la variación porcentual entre periodos:

-- URLs que existen en periodo actual, anterior, o ambos
SELECT
  COALESCE(actual.url, anterior.url) AS url,
  IFNULL(anterior.clics, 0) AS clics_antes,
  IFNULL(actual.clics, 0) AS clics_ahora
FROM periodo_actual actual
FULL OUTER JOIN periodo_anterior anterior ON actual.url = anterior.url

COALESCE toma el primer valor no nulo: si la URL existe en el periodo actual la toma de ahí, si no la toma del anterior.

El problema del matching de URLs

El punto más delicado de los cruces entre GSC y GA4 es que las URLs pueden no coincidir exactamente. GSC almacena URLs limpias (https://ejemplo.com/pagina), mientras que GA4 almacena la URL completa que el navegador registra, incluyendo parámetros de query string (https://ejemplo.com/pagina?utm_source=twitter&ref=header).

Por eso todas las queries de cruce del catálogo normalizan la URL de GA4 antes de hacer el JOIN:

-- Extraer solo scheme + host + path, sin parámetros
REGEXP_EXTRACT(page_location, r'(https?://[^?#]+)') AS url_normalizada

Sin esta normalización, muchos cruces fallarían silenciosamente: las URLs simplemente no coincidirían y los resultados estarían incompletos sin que el error fuera evidente.

Otro aspecto relevante es el rendimiento. Los JOINs sobre tablas muy grandes pueden consumir recursos significativos en BigQuery. Una buena práctica consiste en aplicar filtros (WHERE) dentro de cada subconsulta antes de realizar el cruce, de modo que las tablas intermedias sean lo más pequeñas posible. Reducir el volumen de datos antes del JOIN no solo acelera la ejecución, sino que también disminuye el consumo de cuota de procesamiento.

Cuándo usar cada tipo de JOIN

Siguiente paso

Los JOINs combinan tablas, pero para obtener resúmenes útiles se necesitan funciones de agregación. COUNT, SUM y AVG convierten miles de filas en métricas accionables como totales de clics, promedios de posición o tasas de conversión.

Queries para practicar

Principiante

Top 50 landing pages orgánicas por sesiones

Identifica las 50 páginas de entrada con mayor volumen de sesiones orgánicas. Útil para priorizar esfuerzos de optimización en las URLs que más tráfico captan.

¿Listo para practicar? Explora el catálogo de queries

Ver catálogo