JOIN genera una tabla nueva al combinar columnas de una o varias tablas mediante valores comunes entre ellas. Es una operación habitual en las bases de datos con soporte para SQL, que se corresponde con la operación join del álgebra relacional. El caso especial de unir una tabla consigo misma suele denominarse “autounión”.
Sintaxis
ON y las columnas de la cláusula USING se denominan “claves de JOIN”. A menos que se indique lo contrario, un JOIN produce un producto cartesiano de las filas con “claves de JOIN” coincidentes, lo que puede generar resultados con muchas más filas que las tablas de origen.
Tipos de unión compatibles
Se admiten todos los tipos estándar de SQL JOIN:JOINsin especificar un tipo implicaINNER.- La palabra clave
OUTERpuede omitirse sin problema. - Una sintaxis alternativa para
CROSS JOINconsiste en especificar varias tablas en la cláusulaFROM, separadas por comas. - Si no hay columnas coincidentes para un
NATURAL JOIN, funciona como unCROSS JOIN.
Al usar el analizador, si se deshabilita la configuración
semi_join_include_columns_from_both_sides o anti_join_include_columns_from_both_sides, la unión correspondiente solo expone su lado preservado a las expresiones que se resuelven una vez formado el resultado de la unión.LEFT SEMI JOINyLEFT ANTI JOINsolo exponen las columnas del lado izquierdo.RIGHT SEMI JOINyRIGHT ANTI JOINsolo exponen las columnas del lado derecho.- Esto afecta a cláusulas como
SELECT,PREWHERE,WHERE,GROUP BY,HAVING,QUALIFY,ORDER BYyLIMIT BY, incluidos los comodines calificados comot1.*. - La expresión
ONdel mismoJOINpuede seguir haciendo referencia a ambos lados.
SELECT * expande las columnas de ambas tablas.Cuando join_algorithm se establece en
partial_merge, RIGHT JOIN y FULL JOIN solo son compatibles con estrictez ALL (SEMI, ANTI, ANY y ASOF no son compatibles).LATERAL JOIN
JOIN LATERAL permite que la subconsulta situada en el lado derecho de una unión haga referencia a columnas de las expresiones de tabla
de su lado izquierdo; la subconsulta se evalúa para cada combinación distinta de los valores de columna del lado izquierdo
a los que hace referencia, y su resultado se une a cada fila del lado izquierdo que presente esa combinación:
ON true es obligatorio, al igual que en cualquier otro INNER o LEFT JOIN; omitirlo produce un error de sintaxis.
Es una funcionalidad experimental y está deshabilitada de forma predeterminada; habilítela con la configuración
allow_experimental_lateral_join.
Por ahora solo se admite el siguiente subconjunto; cualquier otra cosa se rechaza con un error:
- Solo
INNER JOIN LATERALyLEFT JOIN LATERAL; las unionesRIGHT,FULL,PASTEyNATURALno son compatibles, yLATERALno puede combinarse en ningún caso con una uniónCROSSo con coma. - Solo la estrictez predeterminada
ALL;ANY,SEMI,ANTIyASOFno son compatibles. - No se admite ningún predicado de unión distinto de
ON true(también se aceptaON 1);USINGno es compatible y el predicado no puede omitirse. Coloque los filtros que relacionan ambos lados en la cláusulaWHEREde la subconsulta lateral. - Los modificadores de unión
GLOBALyLOCALno son compatibles. - La subconsulta lateral debe hacer referencia al menos a una columna del lado izquierdo. Para una subconsulta no correlacionada, use una unión normal.
- La subconsulta lateral se evalúa una vez por cada valor distinto de las columnas del lado izquierdo a las que hace referencia, no
una vez por cada fila izquierda, por lo que no debe contener funciones que no sean deterministas dentro de una consulta, como
randogenerateUUIDv4, ni funciones de tabla que generen filas aleatorias, comogenerateRandom. Sí se permiten las funciones que son constantes dentro de una consulta, comonow. - Solo se admite una subconsulta como expresión de tabla lateral. Las formas de origen de tabla de PostgreSQL
LATERAL unnest(...)yCROSS JOIN UNNEST(...)no son compatibles; en su lugar, use la cláusulaARRAY JOIN. - Los
GROUP BYyORDER BYde la subconsulta lateral se ejecutan una sola vez sobre el conjunto de todas las evaluaciones, por lo que los límitesmax_rows_to_group_by,max_rows_to_sortymax_bytes_to_sortcuentan las filas de todas las evaluaciones, no las de una sola. Solo se admiten con el modo de desbordamientothrow;anyybreakse rechazan. - Las filas de todas las evaluaciones se emparejan con las filas izquierdas mediante una única unión. Como en cualquier hash unión,
max_rows_in_joinymax_bytes_in_joinlimitan el lado de esta unión que se mantiene en memoria, y es el planificador quien elige ese lado: con la configuración predeterminada (correlated_subqueries_use_in_memory_buffer = 1) es el lado izquierdo deJOIN LATERAL, ya que las filas izquierdas deben leerse por completo antes de evaluar la subconsulta lateral; de lo contrario, pueden ser los resultados de todas las evaluaciones en conjunto. Los límites siempre se aplican como sijoin_overflow_modefuerathrow: conbreak, la unión descartaría silenciosamente filas izquierdas no relacionadas.
Configuración
El tipo de JOIN predeterminado puede sobrescribirse mediante la configuraciónjoin_default_strictness.
El comportamiento del servidor ClickHouse en las operaciones ANY JOIN depende de la configuración any_join_distinct_right_table_keys.
Véase también
join_algorithmjoin_any_take_last_rowjoin_use_nullspartial_merge_join_rows_in_right_blocksjoin_on_disk_max_files_to_mergeany_join_distinct_right_table_keys
cross_to_inner_join_rewrite para definir el comportamiento cuando ClickHouse no consigue reescribir un CROSS JOIN como un INNER JOIN. El valor predeterminado es 1, lo que permite que el JOIN continúe, aunque será más lento. Establezca cross_to_inner_join_rewrite en 0 si desea que se genere un error, y en 2 si no quiere ejecutar los cross joins, sino forzar la reescritura de todos los comma/cross joins. Si la reescritura falla cuando el valor es 2, recibirá un mensaje de error que indica “Please, try to simplify WHERE section”.
Condiciones de la sección ON
Una sección ON puede contener varias condiciones combinadas con los operadores AND y OR. Las condiciones que especifican join keys deben:
- hacer referencia tanto a la tabla izquierda como a la derecha
- usar el operador de igualdad
JOIN. Tenga en cuenta que, si las mismas condiciones se colocan en una sección WHERE y no se cumplen, las filas siempre se excluyen del resultado.
El operador OR dentro de la cláusula ON funciona con el algoritmo hash unión: para cada argumento OR con join keys para JOIN, se crea una tabla hash independiente, por lo que el consumo de memoria y el tiempo de ejecución de la consulta aumentan linealmente a medida que crece el número de expresiones OR en la cláusula ON.
Solo el operador de igualdad (
=) convierte una condición en una join key. Se admiten otros operadores entre columnas de distintas tablas, pero si no hay ninguna igualdad, la unión tiene que examinar cada par de filas y resulta mucho más lento; consulte Unión con una condición ON arbitraria.table_1 y table_2:
table_2:
Query
C y la columna de texto vacía. Se incluye en el resultado porque se usa una unión de tipo OUTER.
Response
INNER y múltiples condiciones:
Query
Response
INNER y una condición con OR:
Query
Response
INNER y condiciones con OR y AND:
Una condición de desigualdad que usa columnas de una sola tabla, como
t1.a = t2.key AND t1.b > 0 AND t2.b > t2.c, se aplica únicamente a esa tabla: t1.b > 0 usa únicamente columnas de t1 y t2.b > t2.c usa únicamente columnas de t2.
También se admiten condiciones de desigualdad que comparan columnas de tablas diferentes, como t1.a = t2.key AND t1.b > t2.key; consulte las secciones siguientes para obtener más información.Query
Response
Unión con condiciones de desigualdad para columnas de diferentes tablas
ClickHouse admiteALL/ANY/SEMI/ANTI INNER/LEFT/RIGHT/FULL JOIN con condiciones de desigualdad además de las condiciones de igualdad.
Cuando la sección ON contiene, junto a la desigualdad, una igualdad entre las dos tablas, la igualdad actúa como join key y la desigualdad se comprueba sobre las filas que esta empareja. Este tipo de condición mixta solo se ejecuta con los algoritmos de unión hash, parallel_hash y grace_hash.
Cuando la sección ON no contiene ninguna igualdad entre las dos tablas, no hay ninguna join key con la que emparejar filas. Este tipo de unión se ejecuta mediante ie_join cuando la condición es un par de desigualdades o, en caso contrario, como una block nested loop unión.
Una condición evaluada durante la unión no puede contener arrayJoin, ya que debe conservar el número de filas; en su lugar, utilice ARRAY JOIN en una subconsulta.
Ejemplo
Tabla t1:
t2
Unión con una condición ON arbitraria
La secciónON puede ser cualquier expresión boolean sobre las columnas de ambas tablas, como una comprobación de rango, una comparación aritmética o una llamada a una función escalar. Si no contiene ninguna igualdad entre las dos tablas, no hay ninguna join key con la que emparejar las filas. Un par de condiciones de desigualdad se ejecuta mediante ie_join cuando ese algoritmo figura en join_algorithm, como sucede de forma predeterminada. Cualquier otra condición se ejecuta como una block nested loop unión: la tabla derecha se materializa y la condición se evalúa para cada par de filas. Un ALL INNER JOIN equivale a un CROSS JOIN con la condición aplicada como filtro.
La block nested loop unión admite todos los tipos de unión y niveles de estrictez, salvo ASOF JOIN, PASTE JOIN y ANY FULL JOIN. No figura entre los valores de join_algorithm: es el último recurso y solo se utiliza cuando ningún otro algoritmo puede ejecutar la condición. En la salida de EXPLAIN aparece como un paso BlockNestedLoopJoin. Cuando allow_block_nested_loop_join está deshabilitado, cualquier consulta que lo necesite se rechaza con INVALID_JOIN_ON_EXPRESSION.
Ejemplo
Query
Response
join_use_nulls, como en cualquier otra unión: con un valor predeterminado en el ejemplo anterior y con NULL cuando la configuración está habilitada.
Estrictez
ANY y SEMI normalmente conservan una fila por cada grupo de filas que comparten una join key. Aquí no existe tal grupo, por lo que conservan una fila por cada fila de la tabla que dirige la unión: LEFT ANY y LEFT SEMI emiten cada fila izquierda como máximo una vez, y RIGHT ANY y RIGHT SEMI, cada fila derecha como máximo una vez. ANY INNER limita ambos lados a la vez: cada fila de cualquiera de las dos tablas se usa como máximo una vez, por lo que el resultado no puede tener más filas que la tabla más pequeña. Qué pares forman dicho resultado es arbitrario, tal como implica ANY, y puede variar entre dos ejecuciones de la misma consulta. En el caso de ANY INNER, esto se extiende también al número de filas: al emparejar de forma voraz cada fila de ambos lados, en el orden en que se examinen las filas, puede quedar un número distinto de filas sin emparejar, por lo que el resultado de count() sobre una unión ANY INNER sin join key no es reproducible y depende del número de threads y del orden físico de la tabla derecha.
Rendimiento
Se examina cada par de filas, por lo que el trabajo crece con el producto del número de filas de ambas tablas y no con su suma. Por ello, una block nested loop unión es varios órdenes de magnitud más costosa que una hash unión sobre los mismos datos, y la diferencia aumenta a medida que crecen las tablas. Si una consulta puede escribirse con al menos una igualdad en su sección ON, escríbala así.
Las uniones LEFT ANY, ANY INNER, LEFT SEMI y LEFT ANTI dejan de escanear la tabla derecha al encontrar la primera fila coincidente para cada fila izquierda, lo que normalmente las hace menos costosas que la unión ALL correspondiente. Sus equivalentes dirigidas por la derecha (RIGHT ANY, RIGHT SEMI, RIGHT ANTI) examinan todos los pares, porque el resultado depende de qué filas derechas coincidieron. join_any_take_last_row no tiene efecto aquí: sin join key no existe ningún grupo de filas coincidentes del que tomar la última.
El consumo de memoria está acotado por la tabla derecha materializada y no crece con el tamaño del resultado. La tabla derecha está sujeta a la misma configuración que en otros algoritmos de unión, descrita en Limitaciones de memoria: max_rows_in_join, max_bytes_in_join y join_overflow_mode la limitan, y max_bytes_before_external_join hace que se vuelque a disco.
Valores NULL y NaN en las claves de JOIN
NULL no es igual a ningún valor, incluido él mismo. Esto significa que, si una clave de JOIN tiene un valor NULL en una tabla, no coincidirá con un valor NULL de la otra tabla.
Ejemplo
Tabla A:
B:
Charlie de la tabla A y la fila con puntuación 88 de la tabla B no aparecen en el resultado debido al valor NULL en la clave del JOIN.
Si quiere hacer coincidir valores NULL, use la función isNotDistinctFrom para comparar las claves del JOIN.
NaN en claves JOIN de tipo float no siguen la regla de NULL anterior.
La comparación escalar de dos valores NaN (NaN = NaN) da como resultado 0; sin embargo, las claves de JOIN no se comparan con semántica escalar, por lo que las claves NaN pueden coincidir.
Que realmente coincidan es un detalle de implementación y depende del algoritmo de join, el tipo de clave y la configuración de sesión.
No dependa de un comportamiento específico.
Si necesita que las filas con NaN no coincidan, asígnelas a valores NULL: ON if(isNaN(A.id), NULL, A.id) = B.id.
En un ASOF JOIN, la columna de coincidencia más próxima se compara mediante ordenación, algo que NaN no admite; filtre esas filas en ambos lados.
Uso de ASOF JOIN
ASOF JOIN es útil cuando necesitas unir registros que no tienen una correspondencia exacta.
Este algoritmo de JOIN requiere una columna especial en las tablas. Esta columna:
- Debe contener una secuencia ordenada.
- Puede ser de uno de los siguientes types: Int, UInt, Float, Date, DateTime, Decimal.
- En el caso del algoritmo de join
hash, no puede ser la única columna de la cláusulaJOIN.
ASOF JOIN ... ON:
SELECT count() FROM table_1 ASOF LEFT JOIN table_2 ON table_1.a == table_2.b AND table_2.t <= table_1.t.
Condiciones admitidas para la coincidencia más próxima: >, >=, <, <=.
Sintaxis de ASOF JOIN ... USING:
ASOF JOIN usa equi_columnX para unir por igualdad y asof_column para unir por la coincidencia más cercana con la condición table_1.asof_column >= table_2.asof_column. La columna asof_column siempre es la última de la cláusula USING.
Por ejemplo, considere las siguientes tablas:
ASOF JOIN puede tomar el timestamp de un evento de usuario de table_1 y encontrar un evento en table_2 cuyo timestamp sea el más cercano al del evento de table_1 que corresponda a la condición de coincidencia más próxima. Si existen, los valores de timestamp iguales son los más cercanos. Aquí, la columna user_id puede usarse para hacer el JOIN por igualdad y la columna ev_time para hacer el JOIN por coincidencia más próxima. En nuestro ejemplo, event_1_1 puede unirse con event_2_1 y event_1_2 puede unirse con event_2_3, pero event_2_2 no puede unirse.
ASOF JOIN solo es compatible con los algoritmos de join hash y full_sorting_merge.
No es compatible con el motor de tabla Join.Uso de PASTE JOIN
El resultado dePASTE JOIN es una tabla que contiene todas las columnas de la subconsulta izquierda, seguidas de todas las columnas de la subconsulta derecha.
Las filas se emparejan según su posición en las tablas originales (el orden de las filas debe estar definido).
Si las subconsultas devuelven un número distinto de filas, las filas sobrantes se descartan.
Ejemplo:
JOIN distribuido
Hay dos formas de ejecutar un JOIN que implica tablas distribuidas:- Al usar un
JOINnormal, la consulta se envía a los servidores remotos. Las subconsultas se ejecutan en cada uno de ellos para crear la tabla del lado derecho, y el JOIN se realiza con esa tabla. En otras palabras, la tabla del lado derecho se forma por separado en cada servidor. - Al usar
GLOBAL ... JOIN, primero el servidor solicitante ejecuta una subconsulta para calcular uno de los lados del JOIN y recopila el resultado en una tabla temporal. Esta tabla temporal se envía luego a cada servidor remoto, y las consultas se ejecutan en ellos usando los datos temporales transmitidos. En los JOINLEFTeINNER, la tabla del lado derecho se calcula como la subconsulta. En los JOINRIGHT, en cambio, se calcula la tabla del lado izquierdo, ya que la tabla del lado derecho es la que se conserva y debe leerse desde los segmentos.
GLOBAL. Para obtener más información, consulte la sección Subconsultas distribuidas.
Conversión implícita de tipos
Las consultasINNER JOIN, LEFT JOIN, RIGHT JOIN y FULL JOIN admiten la conversión implícita de tipos para las “claves de JOIN”. Sin embargo, la consulta no puede ejecutarse si las claves de JOIN de las tablas izquierda y derecha no pueden convertirse a un único tipo (por ejemplo, no existe ningún tipo de dato que pueda contener todos los valores de UInt64 e Int64, o de String e Int32).
Ejemplo
Considere la tabla t_1:
t_2:
Recomendaciones de uso
Procesamiento de celdas vacías o NULL
Al unir tablas, pueden aparecer celdas vacías. La configuración join_use_nulls define cómo ClickHouse completa estas celdas. Si las claves deJOIN son campos Nullable, las filas en las que al menos una de las claves tiene el valor NULL no se unen.
Sintaxis
Las columnas especificadas enUSING deben tener los mismos nombres en ambas subconsultas, y las demás columnas deben llamarse de forma diferente. Puede usar alias para cambiar los nombres de las columnas en las subconsultas.
La cláusula USING especifica una o varias columnas para el join, lo que establece la igualdad entre ellas. La lista de columnas se indica sin paréntesis. No se admiten condiciones de join más complejas.
Limitaciones de sintaxis
Para varias cláusulasJOIN en una sola consulta SELECT:
- Seleccionar todas las columnas con
*solo está disponible cuando se unen tablas, no subconsultas. - La cláusula
PREWHEREno está disponible. - La cláusula
USINGno está disponible.
ON, WHERE y GROUP BY:
- No se pueden usar expresiones arbitrarias en las cláusulas
ON,WHEREyGROUP BY, pero puede definir una expresión en una cláusulaSELECTy luego usarla en estas cláusulas mediante un alias.
Rendimiento
Al ejecutar unJOIN, no se optimiza el orden de ejecución con respecto a otras etapas de la consulta. El JOIN (una búsqueda en la tabla de la derecha) se ejecuta antes del filtrado en WHERE y antes de la agregación.
Cada vez que se ejecuta una consulta con el mismo JOIN, la subconsulta se vuelve a ejecutar porque el resultado no se almacena en caché. Para evitarlo, use el motor de tabla especial Join, que es un array preparado para operaciones de join y que siempre está en RAM.
En algunos casos, es más eficiente usar IN en lugar de JOIN.
Si necesita un JOIN para unir tablas de dimensiones (tablas relativamente pequeñas que contienen propiedades de dimensión, como nombres de campañas publicitarias), puede que JOIN no sea la opción más adecuada, ya que se vuelve a acceder a la tabla de la derecha en cada consulta. Para esos casos, existe la funcionalidad de “diccionarios”, que debería usar en lugar de JOIN. Para obtener más información, consulte la sección Diccionarios.
Limitaciones de memoria
De forma predeterminada, ClickHouse usa el algoritmo hash join. ClickHouse toma la right_table y crea una tabla hash para ella en la RAM. Sijoin_algorithm = 'auto' está habilitado, al superar cierto umbral de consumo de memoria, ClickHouse cambia al algoritmo merge join. Para ver la descripción de los algoritmos JOIN, consulte la configuración join_algorithm.
Si necesita restringir el consumo de memoria de la operación JOIN, use la siguiente configuración:
- max_rows_in_join — Limita el número de filas de la tabla hash.
- max_bytes_in_join — Limita el tamaño de la tabla hash.
enable_adaptive_memory_spill_scheduler aún puede volcar un join que el umbral inferior hizo
capaz de volcarse, y
legacy_join_size_limits_trigger_spilling vuelve a convertir los dos límites en activadores de volcado a disco.
Para permitir que un join siga ejecutándose al volcar el lado derecho al disco en lugar de generar un error, use:
- max_bytes_before_external_join — Umbral de volcado absoluto.
- max_bytes_ratio_before_external_join — Umbral de volcado como proporción de la memoria disponible.
grace_hash; bajo presión de
memoria, enable_adaptive_memory_spill_scheduler puede volcar antes de lo que indican, pero solo cuando uno de
ellos no es cero, ya que un join sin ningún umbral nunca se vuelca. El join_algorithm que elija
decide cómo se vuelca un join — grace_hash particiona la tabla derecha desde el primer bloque, hash y parallel_hash la recopilan
en memoria y cambian cuando se supera el umbral — no si se aplican estas configuraciones. La única excepción es
legacy_join_size_limits_trigger_spilling: al activarlo, grace_hash independiente ignora ambos umbrales y se vuelca según los dos límites estrictos.
Ejemplos
Ejemplo:Contenido relacionado
- Blog: ClickHouse: un DBMS rapidísimo con soporte completo para SQL JOIN - Parte 1
- Blog: ClickHouse: un DBMS rapidísimo con soporte completo para SQL JOIN - Por dentro - Parte 2
- Blog: ClickHouse: un DBMS rapidísimo con soporte completo para SQL JOIN - Por dentro - Parte 3
- Blog: ClickHouse: un DBMS rapidísimo con soporte completo para SQL JOIN - Por dentro - Parte 4