23rd Jul 2022 Lectura de 11 minutos ¿Qué es Self Join en SQL? Una explicación con siete ejemplos Martyna Sławińska SQL aprender SQL JOIN Índice ¿Qué es una autounión o Self Join en SQL? Alias de tablas en Self Join Ejemplos Escenario 1: Procesamiento de una jerarquía en SQL Escenario 2: Listado de pares dentro de una tabla Escenario 3: Autounión en combinación con otra tabla La autounión: un tipo especial de unión Self join es una alternativa muy popular de JOIN (unión) en SQL. Mientras que la mayoría de los JOIN enlazan dos o más tablas entre sí para presentar sus datos de forma conjunta, una self join (autounión) enlaza una tabla consigo misma. Esto se suele hacer uniendo una tabla consigo misma una sola vez dentro de una consulta SQL, pero es posible hacerlo varias veces dentro de la misma consulta. Normalmente, cada tabla de una base de datos almacena un tipo específico de información. Por lo tanto, a menudo hay cientos de tablas relacionadas entre sí en una base de datos. Esto implica la necesidad de realizar uniones. Se pueden unir diferentes tablas por sus columnas comunes utilizando la palabra clave JOIN. También es posible unir una tabla a sí misma, lo que se conoce como autounión (self-join). En este artículo, veremos qué es una autounión, cómo funciona y cuándo lo necesitas en tus consultas SQL. Para practicar uniones JOIN en SQL, incluyendo las autouniones, recomiendo nuestro curso interactivo Tipos de JOIN en SQL. Contiene más de 90 ejercicios sobre diferentes tipos de JOIN, incluyendo secciones dedicadas sólo a las autouniones. ¿Qué es una autounión o Self Join en SQL? Una autounión, como su nombre lo indica, une una tabla consigo misma. Para utilizar una autounión, la tabla debe contener una columna (llámese X) que actúe como clave primaria, y otra columna (llámese Y) que almacene valores que puedan coincidir con los valores de la columna X. Los valores de las columnas X e Y no tienen por qué ser los mismos para una fila determinada, y el valor de la columna Y puede ser incluso null. Veamos un ejemplo. Consideremos la tabla empleados: Idnombre_completosalarioid_gerente 1John Smith100003 2Jane Anderson120003 3Tom Lanon150004 4Anne Connor20000 5Jeremy York90001 Cada empleado tiene su propia Id, que es nuestra "columna X". Para un empleado determinado (es decir, una fila), la columna id_gerente contiene el Id de su gerente; ésta es nuestra "columna Y". Si rastreamos los pares de empleados-gerentes en esta tabla utilizando estas columnas: El gerente del empleado John Smith es el empleado con Id 3, es decir, Tom Lanon. El gerente del empleado Jane Anderson es el empleado con Id 3, es decir, Tom Lanon. El gerente del empleado Tom Lanon es el empleado con Id 4, es decir, Anne Connor. La empleada Anne Connor no tiene gerente; su id_del_gerente es nulo. El gerente del empleado Jeremy York es el empleado con Id 1, es decir, John Smith. Este tipo de estructura de tabla es muy común en las jerarquías. Ahora bien, para mostrar el nombre del gerente de cada empleado en la misma fila, podemos ejecutar la siguiente consulta: SELECT empleado.Id, empleado.nombre_completo, empleado.id_gerente, gerente.nombre_completo as nombre_gerente FROM empleados empleado JOIN empleados gerente ON empleado.id_gerente = gerente.Id Que devuelve el siguiente resultado: Idnombre_completoid_gerentenombre_gerente 1John Smith3Tom Lanon 2Jane Anderson3Tom Lanon 3Tom Lanon4Anne Connor 5Jeremy York1John Smith La consulta selecciona las columnas Id, nombre_completo y id_gerente de la tabla con alias empleado. También selecciona la columna nombre_completo de la tabla con alias gerente y designa esta columna como nombre_gerente. Como resultado, cada empleado que tiene un gerente se muestra junto con su ID y nombre. En esta consulta, la tabla empleados está unida a sí misma y cumple dos roles diferentes: Rol 1: Almacena los datos del empleado (alias empleado). Rol 2: Almacena los datos del gerente (alias gerente). Al hacerlo, estamos considerando las dos copias de la tabla empleados como si fueran dos tablas distintas, una para los empleados y otra para los gerentes. Puedes encontrar más información sobre el concepto de self join o autounión en nuestro artículo Guía ilustrada de Self Join de SQL. Alias de tablas en Self Join Cuando se hace referencia a la misma tabla más de una vez en una consulta SQL, necesitamos una forma de distinguir cada referencia de las demás. Por esta razón, es importante utilizar alias para identificar de forma única cada referencia de la misma tabla en una consulta SQL. Como buena práctica, los alias deben indicar el papel de la tabla para cada referencia específica en una consulta. Los alias están en rojo en la siguiente consulta. Puedes ver su declaración en las cláusulas FROM y JOIN. SELECT empleado.Id, empleado.nombre_completo, empleado.id_gerente, gerente.nombre_completo as nombre_gerente FROM empleados empleado JOIN empleados gerente ON empleado.id_gerente = gerente.Id La palabra clave JOIN conecta dos tablas y suele ir seguida de una cláusula ON o USING que especifica las columnas comunes utilizadas para vincular las dos tablas. En este caso, vemos que las dos referencias a la tabla empleados se vinculan condicionando a que el id_gerente del empleado coincida con el ID del empleado del gerente. Ejemplos Veamos algunos escenarios comunes que utilizan Self Join. Escenario 1: Procesamiento de una jerarquía en SQL Self Join suele usarse en el procesamiento de una jerarquía. Como vimos anteriormente, una jerarquía asigna una fila en una tabla a otra fila dentro de la misma tabla. Se puede pensar que hay filas madres e hijas. Volvamos al ejemplo con los empleados y sus gerentes. Aquí está la tabla empleados nuevamente: Idnombre_completosalarioid_gerente 1John Smith100003 2Jane Anderson120003 3Tom Lanon150004 4Anne Connor20000John Smith 5Jeremy York90001 Y el código para listar cada empleado que tiene un gerente, junto al nombre de su gerente: SELECT empleado.Id, empleado.nombre_completo, empleado.id_gerente, gerente.nombre_completo as nombre_gerente FROM empleados empleado JOIN empleados gerente ON empleado.id_gerente = gerente.Id Este es el resultado al ejecutar el código: Idnombre_completoid_gerentenombre_gerente 1John Smith3Tom Lanon 2Jane Anderson3Tom Lanon 3Tom Lanon4Anne Connor 5Jeremy York1John Smith Esta consulta utiliza la unión estándar, también conocida como INNER JOIN. Para leer más sobre INNER JOIN, consulta nuestro artículo Guía ilustrada de INNER JOIN de SQL. Si queremos listar todos los empleados, tengan o no tengan gerentes, podemos usar un LEFT OUTER JOIN en su lugar. La consulta de abajo hace lo siguiente: SELECT empleado.Id, empleado.nombre_completo, empleado.id_gerente, gerente.nombre_completo as nombre_gerente FROM empleados empleado LEFT OUTER JOIN empleados gerente ON empleado.id_gerente = gerente.Id Al ejecutar esta consulta, se obtiene el siguiente resultado: Idnombre_completoid_gerentenombre_gerente 1John Smith3Tom Lanon 2Jane Anderson3Tom Lanon 3Tom Lanon4Anne Connor 4Anne Connor 5Jeremy York1John Smith La diferencia entre JOIN y LEFT OUTER JOIN queda clara cuando comparamos esto con el resultado de la consulta SQL anterior. En el resultado de la consulta (inner o interna) de JOIN, sólo se incluyen los empleados con gerentes. En cambio, la consulta LEFT OUTER JOIN devuelve todos los empleados, con o sin gerentes. Para saber más sobre OUTER JOIN, lee nuestro artículo Guía ilustrada de OUTER JOIN de SQL. Otro ejemplo de jerarquía es la relación entre padres e hijos. Considera la tabla humano que se muestra aquí: Idnombreedadid_padre 1Jonathan53 2Alexandra73 3Barbara30 En la siguiente consulta, los hijos se asignan a sus respectivos padres uniendo la tabla humano a sí misma: SELECT hijo.Id as id_hijo, hijo.nombre as nombre_hijo, hijo.edad as edad_hijo, hijo.id_padre, padre.nombre as nombre_padre, padre.edad as edad_padre FROM humano hijo INNER JOIN humano padre ON hijo.id_padre = padre.Id Este es el resultado de esta consulta: id_hijonombre_hijoedad_hijoid_padrenombre_padreedad_padre 1Jonathan53Barbara30 2Alexandra73Barbara30 El resultado de la consulta incluye sólo los hijos que tienen padres. Al igual que en el ejemplo de la jerarquía empleado-gerente, podríamos utilizar un LEFT OUTER JOIN para incluir todas las filas de la tabla alias hijo. He aquí otro ejemplo de jerarquía. Considera la tabla categoria que se muestra a continuación: Idcantidadcategoriaid_categoria_padres 160Comida 250Fruta1 340Manzana2 420Manzana verde3 5100Leche1 660Leche de soja5 740Leche de vaca5 830Leche entera7 910Leche descremada7 Asignemos una categoría padre a cada categoría, siempre que sea posible. Aquí hay una consulta para hacer eso: SELECT categoria.Id, categoria.cantidad, categoria.categoria, categoria.id_categoria_padre, padrecategoria.categoria as categoria_padre FROM categoria categoria JOIN categoria padrecategoria ON categoria.id_categoria_padre = padrecategoria.Id Y aquí está el resultado: Idcantidadcategoriaid_categoria_padrescategoria_padre 250Fruta1Comida 340Manzan2Fruta 420Manzana verde3Manzana 5100Leche1Comida 660Leche de soja5Leche 740Leche de vaca5Leche 830Leche entera7Leche de vaca 910Leche descremada7Leche de vaca Las cuatro primeras columnas del resultado anterior proceden de la referencia a la tabla de alias categoria. La última columna procede de la tabla alias padrecategoria y contiene el nombre de la categoría padre que coincide con la respectiva Id. La tabla categoria tiene dos funciones diferentes, como indican las dos referencias separadas. La columna id_categoria_padre de la tabla alias categoria se empareja con Id de la tabla alias padrecategoria. La cláusula ON especifica que id_categoria_padres de categoria debe ser igual a Id de padrecategoria para conectar las filas correspondientes. Escenario 2: Listado de pares dentro de una tabla Se puede utilizar Self Join para generar pares de filas basados en la condición de la cláusula ON. Empecemos con un ejemplo sencillo que genera todos los pares posibles entre colegas. Considera la siguiente tabla, colegas: Idnombre_completoedad 1Bart Thompson43 2Catherine Anderson44 3John Burkin35 4Nicole McGregor29 Supongamos que necesitamos generar todas los pares posibles entre los colegas para que todos tengan la oportunidad de hablar con todos los demás en la presentación de la empresa. Aquí está el código SQL: SELECT companero1.nombre_completo as companero_equipo1_nombre_completo, companero1.edad as companero_equipo1edad, companero2.nombre_completo as companero_equipo2nombre_completo, companero2.edad as companero_equipo2edad FROM colegas companero1 CROSS JOIN colegas companero2 ON companero1.nombre_completo <> companero2.nombre_completo Y aquí está el resultado: companero_equipo1_nombre_completocompanero_equipo1edadcompanero_equipo2nombre_completocompanero_equipo2edad Catherine Anderson44Bart Thompson43 John Burkin35Bart Thompson43 Nicole McGregor29Bart Thompson43 Bart Thompson43Catherine Anderson44 John Burkin35Catherine Anderson44 Nicole McGregor29Catherine Anderson44 Bart Thompson43John Burkin35 Catherine Anderson44John Burkin35 Nicole McGregor29John Burkin35 Bart Thompson43Nicole McGregor29 Catherine Anderson44Nicole McGregor29 John Burkin35Nicole McGregor29 El resultado empareja a cada persona con cada persona de la tabla. Como no queremos que nadie se empareje consigo mismo, tenemos la condición de la cláusula ON companero1.nombre_completo <> companero2.nombre_completo. Esto significa que cada persona será emparejada con otros tres compañeros, porque hay cuatro compañeros en este evento. Ahora, veamos un ejemplo un poco más complicado. Considera la tabla humano que se muestra a continuación. Queremos emparejar todos los antepasados de cada persona siempre que los datos lo permitan, donde una persona es un antepasado si tiene un valor Id mayor. A continuación, se muestra la tabla humano utilizada en este ejemplo. Idnombreedadid_padre 1Jonathan53 2Alexandra73 3Barbara304 4Tom506 5George556 6Amy807 7Josephine9935 Busquemos todos los pares descendiente-antepasado en la tabla anterior. Aquí está el código SQL: SELECT descendiente.Id, descendiente.nombre, descendiente.edad, descendiente.id_padre, antepasado.Id as id_antepasado, antepasado.nombre as nombre_antepasado, antepasado.edad as edad_antepasado FROM humano descendiente LEFT JOIN humano antepasado ON descendiente.id_padre <= antepasado.Id Y el resultado: Idnombreedadid_padreid_antepasadonombre_antepasadoedad_antepasado 1Jonathan533Barbara30 1Jonathan534Tom50 1Jonathan535George55 1Jonathan536Amy80 1Jonathan537Josephine99 2Alexandra733Barbara30 2Alexandra734Tom50 2Alexandra735George55 2Alexandra736Amy80 2Alexandra737Josephine99 3Barbara3044Tom50 3Barbara3045George55 3Barbara3046Amy80 3Barbara3047Josephine99 4Tom5066Amy80 4Tom5067Josephine99 5George5566Amy80 5George5567Josephine99 6Amy8077Josephine99 7Josephine99 Especificando la condición de la cláusula ON descendiente.id_padre <= antepasado.Id, encontramos todos los antepasado de cada persona en la tabla donde existen; de lo contrario, la consulta devuelve null para la información de los antepasados. Las cuatro primeras columnas se toman de la tabla alias descendiente, que contiene la información de la persona para la que se buscan los antepasados. Las tres últimas columnas se toman de la tabla alias antepasado y contienen detalles sobre cada antepasado. Escenario 3: Autounión en combinación con otra tabla En SQL, es posible tener un Self Join en combinación con una o más tablas diferentes. Aunque no se trata de un Self Join limpia, esto es muy común en la práctica. Un ejemplo de la vida real es la información de los vuelos en los aeropuertos, con una enorme cantidad de datos cada hora. Supongamos que queremos buscar un número de identificación de vuelo junto con los detalles de sus aeropuertos de salida y destino. Consideremos las siguientes tablas: Tabla aeropuerto: id_aeropuertopaisciudad 1USANew York 2CanadaToronto 3GermanyFrankfurt 4FranceParis 5ItalyRome Tabla vuelo: id_vueloid_avionhora_iniciohora_finid_aeropuerto_inicioid_aeropuerto_fin 25558772020-01-14 13:00:002020-01-14 15:00:0034 32225362020-02-04 01:00:002020-02-04 16:00:0015 41117452020-02-15 09:00:002020-02-15 12:00:0054 57775242020-02-24 03:00:002020-02-24 19:00:0042 68885212020-03-25 10:00:002020-03-25 12:00:0021 74449372020-04-01 00:00:002020-04-01 17:00:0031 2431116542020-01-01 02:00:002020-01-01 04:00:0012 Aquí, observa que la columna id_aeropuerto de la tabla aeropuerto es la clave externa de las columnas id_aeropuerto_inicio e id_aeropuerto_fin de la tabla vuelo. Vamos a unir la tabla aeropuerto a la tabla vuelo dos veces por separado, como se indica a continuación: En el primer JOIN, aeropuerto toma el rol de la tabla con los aeropuertos de partida (inicio). En el segundo JOIN, aeropuerto toma el rol de la tabla con los aeropuertos de arribo (fin). La consulta tiene el siguiente aspecto: SELECT vuelo.id_vuelo, vuelo.id_avion, vuelo.id_aeropuerto_inicio, aeropuertoinicio.pais as inicio_aeropuerto_pais, aeropuertoinicio.ciudad as inicio_aeropuerto_ciudad, vuelo.id_aeropuerto_fin, aeropuertofin.pais as pais_aeropuerto_fin, aeropuertofin.ciudad as ciudad_aeropuerto_fin FROM vuelo vuelo JOIN aeropuerto aeropuertoinicio ON vuelo.id_aeropuerto_inicio = aeropuertoinicio.id_aeropuerto JOIN aeropuerto aeropuertofin ON vuelo.id_aeropuerto_fin = aeropuertofin.id_aeropuerto Y el resultado de la consulta es el siguiente: id_vueloid_avionid_aeropuerto_inicioinicio_aeropuerto_paisinicio_aeropuerto_ciudadid_aeropuerto_finpais_aeropuerto_finciudad_del_aeropuerto_final 11116541USANew York2CanadaToronto 25558773GermanyFrankfurt4FranceParis 32225361USANew York5ItalyRome 41117455ItalyRome4FranceParis 57775244FranceParis2CanadaToronto 68885212CanadaToronto1USANew York 74449373GermanyFrankfurt1USANew York Analicemos el resultado. Las tres primeras columnas provienen de un sencillo SELECT de la table vuelo. Las dos columnas siguientes proceden de aeropuerto en el rol de tabla de aeropuerto de partida; las filas se emparejan en función de id_aeropuerto y id_aeropuerto_inicio de aeropuerto y vuelo respectivamente. A esto le sigue una columna de la tabla vuelo. Las dos últimas columnas proceden de aeropuerto, esta vez en el rol de la tabla del aeropuerto de destino; las filas se emparejan en base a id_aeropuerto y id_aeropuerto_fin de las tablas aeropuerto y vuelo respectivamente. ¿Te confunden un poco todos los JOIN? Hay muchos más artículos en los que puedes buscar ayuda. Te recomiendo especialmente el artículo sobre Cómo aprender Tipos de JOIN en SQL. Y si necesitas empezar a practicar, visita nuestro artículo sobre Cómo practicar Tipos de JOIN en SQL. La autounión: un tipo especial de unión Como hemos visto, la autounión es un tipo importante de unión. Hemos visto ejemplos de varias aplicaciones del Self Join, incluyendo el procesamiento de una jerarquía en una tabla y el emparejamiento de las filas dentro de una tabla. Podemos unir la misma tabla varias veces, pero es importante dar a cada referencia un alias que indique su función. Estos alias de tabla se utilizan para obtener columnas de esta tabla única en función del rol al que se hace referencia. Las uniones son una parte vital de SQL, a la vez que una característica muy útil y frecuentemente utilizada para combinar diferentes tablas. ¡Están en todas partes! No olvides consultar nuestro curso sobre Tipos de JOIN en SQL para dominar esta poderosa herramienta. Tags: SQL aprender SQL JOIN