---
name: join_tables
kind: function
lang: cpp
domain: core
version: "1.0.0"
purity: pure
signature: "data_table::StageOutput data_table::join_tables(const char* const* left_cells, int left_rows, int left_cols, const std::vector<std::string>& left_headers, const std::vector<ColumnType>& left_types, const TableInput& right, const Join& jn)"
description: "Hash join multi-key entre dos tablas con 4 estrategias: inner, left, right y full outer. Soporta clave compuesta (N pares left_col/right_col), produce producto cartesiano para claves duplicadas, prefija columnas del derecho con alias cuando se especifica, y permite seleccionar un subconjunto de columnas del derecho via fields."
tags: [tables, join, tql, pure, cpp-tables]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: true
tests:
  - "inner join basico"
  - "left join con orphans"
  - "right join con orphans"
  - "full outer join"
  - "multi-key join con 2 keys"
  - "key con duplicados produce producto cartesiano"
  - "alias prefija columnas del derecho"
  - "fields subset incluye solo columnas especificadas"
  - "right table vacia con left join devuelve todas las filas izquierda"
test_file_path: "cpp/tests/test_join_tables.cpp"
file_path: "cpp/functions/core/join_tables.cpp"
params:
  - name: left_cells
    desc: "Puntero row-major a las celdas de la tabla izquierda. Cada celda es const char* (null se trata como cadena vacia)."
  - name: left_rows
    desc: "Numero de filas de la tabla izquierda."
  - name: left_cols
    desc: "Numero de columnas de la tabla izquierda."
  - name: left_headers
    desc: "Nombres de columna de la tabla izquierda, en el mismo orden que left_cells."
  - name: left_types
    desc: "Tipos de columna de la tabla izquierda (ColumnType::Auto si no se conocen)."
  - name: right
    desc: "TableInput de la tabla derecha: name, headers, types, cells (row-major), rows, cols."
  - name: jn
    desc: "Join spec: strategy (Inner/Left/Right/Full), on (lista de pares {left_col,right_col}), alias (prefijo para columnas del derecho), fields (subconjunto de columnas del derecho; vacio = todas)."
output: "StageOutput con todas las filas resultado: cell_backing (strings owned), cells (punteros row-major), rows, cols, headers y types del output combinado."
---

# join_tables

Extrae e implementa la logica de join del playground TQL
(`primitives_gallery/playground/tables/data_table_logic.cpp`).
Pura: sin ImGui, sin I/O, sin estado global.

## Ejemplo

```cpp
#include "core/join_tables.h"
using namespace data_table;

// Tabla izquierda: usuarios
std::vector<std::string> lhdr = {"id", "name"};
std::vector<ColumnType>  ltyp = {ColumnType::Int, ColumnType::String};
const char* lcells[] = { "1","Alice",  "2","Bob",  "3","Carol" };

// Tabla derecha: pedidos
TableInput right;
right.name    = "orders";
right.headers = {"user_id", "amount"};
right.types   = {ColumnType::Int, ColumnType::Float};
const char* rcells[] = { "1","100",  "2","200",  "2","150" };
right.cells = rcells;
right.rows  = 3;
right.cols  = 2;

// Inner join por id=user_id
Join jn;
jn.strategy = JoinStrategy::Inner;
jn.on       = {{"id", "user_id"}};
jn.alias    = "orders";

StageOutput res = join_tables(lcells, 3, 2, lhdr, ltyp, right, jn);
// res.rows == 3  (Alice->100, Bob->200, Bob->150; Carol sin match no aparece)
// res.headers == {"id","name","orders.user_id","orders.amount"}
```

## Cuando usarla

Cuando necesites combinar dos StageOutput o TableInput de distinto origen
antes de pasarlos a `compute_stage`. Por ejemplo, enriquecer una tabla de
eventos con metadatos de usuarios, o cruzar dos queries de Metabase antes
de agregar.

## Gotchas

- **Key con NULL/vacia**: una celda null o vacía en la clave se trata como
  cadena vacía. Dos filas con clave vacía SI se emparejan entre sí (comportamiento
  SQL-atípico; SQL NULL != NULL). Filtra filas con clave vacía antes del join
  si no quieres este comportamiento.
- **Key compuesta con columna ausente**: si un nombre de columna en `jn.on`
  no existe en los headers, ese componente de la clave se reemplaza por `\x1f|`
  (placeholder distinto de cualquier valor real), lo que impide matches para
  esa columna. Verifica los nombres de columna antes de llamar.
- **Tipos mixtos**: la comparación de claves es siempre lexicográfica sobre
  strings. `"10"` y `"10.0"` NO hacen match aunque sean el mismo número.
  Normaliza los tipos antes del join si es necesario.
- **Producto cartesiano**: con claves duplicadas en ambos lados, el output
  crece como N_left_matches × N_right_matches. Un join sin filtrar sobre
  tablas grandes con claves duplicadas puede producir salida muy grande.
- **Columnas del output**: el output tiene siempre left_cols + len(right_fields)
  columnas, en ese orden. Las columnas de la clave del derecho aparecen
  duplicadas si no se usa `fields` para excluirlas.
