Original article: SQL Joins Tutorial: Cross Join, Full Outer Join, Inner Join, Left Join, and Right Join.

Los joins o combinaciones de SQL permiten que nuestros sistemas de administración de base de datos relacionales estén bien "relacionados".

Los joins o combinaciones nos permiten reconstruir de nuevo nuestras tablas de base de datos separadas en relaciones que impulsan nuestras aplicaciones.

En este artículo, hablaremos de cada uno de los diferentes tipos de joins o combinaciones de SQL y como usarlas.

Lo que cubriremos:

  • ¿Qué es un join o combinación de SQL?
  • Configurando tu base de datos
  • CROSS JOIN
  • Configurando nuestra data ejemplo (directorios y películas)
  • FULL OUTER JOIN
  • INNER JOIN
  • LEFT JOIN / RIGHT JOIN
  • Filtrar usando LEFT JOIN
  • Múltiples joins o combinaciones
  • Joins o combinaciones con condiciones extra
  • La verdad sobre escribir queries con joins o combinaciones

(Alerta de spoiler: cubriremos cinco diferentes tipos—pero solamente necesitas conocer dos de ellos!)

¿Qué es un join o combinación en SQL?

Un join es una operación que combina dos filas juntas en una fila.

Estas filas usualmente pertenecen a dos tablas diferentes —pero no necesariamente tiene que ser así.

Antes de ver como escribir un join, veamos como se vería su resultado.

Tomemos como ejemplo un sistema que almacena información acerca de sus usuarios y sus direcciones.

Las filas de la tabla que almacena la información del usuario se vería así:

 id |     nombre     |        email        | edad
----+--------------+---------------------+-----
  1 | John Smith     | johnsmith@gmail.com |  25
  2 | Jane Doe       | janedoe@Gmail.com   |  28
  3 | Xavier Wills   | xavier@wills.io     |  3
...
(7 filas)

Y las filas de la tabla que almacena direcciones podría verse así:

 id |      calle        |     ciudad    | estado| id_usuario
----+-------------------+---------------+-------+---------
  1 | 1234 Main Street  | Oklahoma City | OK    |       1
  2 | 4444 Broadway Ave | Oklahoma City | OK    |       2
  3 | 5678 Party Ln     | Tulsa         | OK    |       3
(3 rows)

Podríamos escribir por separado consultas que nos traiga tanto información relacionada con los usuarios como la información de la dirección — pero idealmente podemos escribir una consulta y recibir todos los usuarios y las direcciones al mismo tiempo.

¡Esto es exactamente lo que nos permiten hacer los joins!

Veremos como escribir estos joins pronto, pero si combinamos nuestra información del usuario a la información de dirección conseguiremos un resultado como este:

 id |     nombre     |        email        | edad | id |      calle      |    ciudad      | estado | id_usuario
----+--------------+---------------------+-----+----+-------------------+---------------+-------+---------
  1 | John Smith   | johnsmith@gmail.com |  25 |  1 | 1234 Main Street  | Oklahoma City | OK    |       1
  2 | Jane Doe     | janedoe@Gmail.com   |  28 |  2 | 4444 Broadway Ave | Oklahoma City | OK    |       2
  3 | Xavier Wills | xavier@wills.io     |  35 |  3 | 5678 Party Ln     | Tulsa         | OK    |       3
(3 filas)

Aquí vemos todos los usuarios y sus direcciones en un buen conjunto de resultadoa.

Además de producir un conjunto de resultado combinado, otro uso importante de los joins es obtener información adicional contra la que podemos filtrar.

Por ejemplo, si queremos enviar un correo físico a todos los usuarios que viven en la ciudad de Oklahoma, podríamos usar el conjunto de resultado combinado y filtrarlo basado en la columna ciudad.

Ahora que sabemos el propósito de los Joins, ¡empecemos escribiendo algunos!

Configurando tu base de datos

Antes de que podamos escribir nuestras consultas necesitamos configurar nuestra base de datos.

Para estos ejemplos estaremos usando PostgreSQL, pero las consultas y conceptos mostrados aquí serán facilmente traducidos a cualquier sistema moderno de base de datos (como MySQL, SQL Server, etc.).

Para trabajar con nuestra base de datos en  PostgreSQL, podemos usar psql—el programa interactivo de línea de comandos de PostgreSQL. Si tienes otro cliente de base de datos con el que disfrutes trabajar puedes usarlo también.

Para comenzar vamos a crear nuestra base de datos. Con el PostgreSQL ya instalado, corremos el comando createdb <nombre de la base de datos> en nuestra terminal para crear nuestra base de datos. Llamé a la mia fcc:

$ createdb fcc

Ahora iniciemos la consola interactiva usando el comando psql y nos conectamos a la base de datos que acabamos de crear usando \c <nombre de la base de datos>:

$ psql
psql (11.5)
Type "help" for help.

john=# \c fcc
You are now connected to database "fcc" as user "john".
fcc=#
Nota: Limpié la salida de psql en estos ejemplos para hacerlos más fáciles de leer, así que no te preocupes si la salida que te muestro aquí no es exactamente igual a la que estás viendo en tu terminal.

Te recomiendo que hagas estos ejemplos conmigo y que corras estas consultas tu mismo. Aprenderás y recordarás mucho más trabajando sobre estos ejemplos en lugar de solo leerlos.  

¡Ahora veamos las combinaciones o joins!

CROSS JOIN

Los joins más simples que podemos hacer son los  CROSS JOIN o "Producto cartesiano."

Este join toma cada fila de una tabla y la une a cada fila de otra tabla.

Si tenemos dos listas —una que contiene 1, 2, 3 y otra que contiene A, B, C—el producto cartesiano de las dos listas sería:

1A, 1B, 1C
2A, 2B, 2C
3A, 3B, 3C

Cada valor de la primera lista es emparejado con cada valor de la segunda lista.

Escribamos este mismo ejemplo como un query en SQL.

Primero creamos dos tablas muy simples e insertemos algunos datos en ellas:

CREATE TABLE letras(
  letra TEXT
);

INSERT INTO letras(letra) VALUES ('A'), ('B'), ('C');

CREATE TABLE numeros(
  num TEXT
);

INSERT INTO numeros(num) VALUES (1), (2), (3);

Nuestras dos tablas, letras y numeros, solo tienen una columna: un campo de texto simple.

Ahora vamos a combinarlas con CROSS JOIN:

SELECT *
FROM letras
CROSS JOIN numeros;
 letra | num
--------+--------
 A      | 1
 A      | 2
 A      | 3
 B      | 1
 B      | 2
 B      | 3
 C      | 1
 C      | 2
 C      | 3
(9 filas)

Este es el  join mas simple que podemos hacer —pero aún en este simple ejemplo podemos ver como trabajan los joins: las dos filas separadas (una de letras y otra de numeros) han sido combinadas para formar una fila.

Mientras este tipo de join es discutido como un mero ejemplo académico, tiene al menos un buen caso de uso: rangos de fechas.

CROSS JOIN con rangos de fecha

Un buen caso de uso del CROSS JOIN es tomar cada fila de una tabla y aplicarla a todos los días dentro de un rango de fecha.

Digamos por ejemplo que estabas creando una aplicación que hiciera seguimiento a tareas diarias —cosas como cepillarte los dientes, comer tu desayuno, o ducharte.

Si quisieras generar un registro de cada tarea diaria y por cada día de la semana pasada, podrías usar un CROSS JOIN contra un rango de fechas.

Para hacer este rango de fechas, podemos usar la  función generate_series:

SELECT generate_series(
  (CURRENT_DATE - INTERVAL '5 day'),
  CURRENT_DATE,
  INTERVAL '1 day'
)::DATE AS dia;

La función generate_series toma tres parámetros.

El primer parámetro es el valor inicial. En este ejemplo estamos usando CURRENT_DATE - INTERVAL '5 day'. Esto devuelve la fecha actual menos cinco días —o lo que es lo mismo a "hace cinco días."

El segundo parámetro es la fecha actual (CURRENT_DATE).

El tercer parámetro es el "intervalo de paso"—o cuánto queremos incrementar el valor cada vez. Dado que estas son tareas diarias, usaremos el intervalo de un día (INTERVAL '1 day').

Poniéndolo todo junto, esto genera una serie de fechas que comienzan hace cinco días, terminan hoy y van un día a la vez.

Finalmente, eliminamos la porción de tiempo al convertir la salida de estos valores a una fecha usando ::DATE, y le asignamos un alias a esta columna usando AS dia para hacer la salida un poco más agradable.

El resultado de esta consulta son los últimos cinco días más hoy:

    dia
------------
 2020-08-19
 2020-08-20
 2020-08-21
 2020-08-22
 2020-08-23
 2020-08-24
(6 filas)

Volviendo a nuestro ejemplo de tareas por día, creemos una tabla simple que incluya las tareas que queremos completar e insertemos algunas tareas:

CREATE TABLE tareas(
  nombre TEXT
);

INSERT INTO tareas(nombre) VALUES
('Lavar dientes'),
('Comer desayuno'),
('Ducharse'),
('Vestirse');

Nuestra tablatareas solo tiene una columna, nombre, e insertamos cuatro tareas en esta tabla.

Ahora hagamosCROSS JOIN a nuestras tareas con la consulta para generar las fechas :

SELECT
  tareas.nombre,
  dates.day
FROM tareas
CROSS JOIN
(
  SELECT generate_series(
    (CURRENT_DATE - INTERVAL '5 day'),
    CURRENT_DATE,
    INTERVAL '1 day'
  )::DATE	AS dia
) AS fechas

(Dado que nuestra consulta de generación de fechas no es una tabla real, simplemente la escribimos como una subconsulta)

A partir de esta consulta, devolvemos el nombre de la tarea y el día, y el conjunto de resultados se ve así:

     nombre      |    dia
---------------+------------
 Lavar dientes   | 2020-08-19
 Lavar dientes   | 2020-08-20
 Lavar dientes   | 2020-08-21
 Lavar dientes   | 2020-08-22
 Lavar dientes   | 2020-08-23
 Lavar dientes   | 2020-08-24
 Comer desayuno  | 2020-08-19
 Comer desayuno  | 2020-08-20
 Comer desayuno  | 2020-08-21
 Comer desayuno  | 2020-08-22
 ...
 (24 filas)

Como esperábamos, obtenemos una fila para cada tarea por cada día en nuestro rango de fechas.

El CROSS JOIN es el más simple join que podemos hacer, pero para ver los siguientes tipos, necesitaremos una configuración de tabla más realista.

Creando directorios y películas

Para ilustrar los siguientes tipos de combinaciones o joins, usaremos el ejemplo de películas y directorios de películas.

En esta situación, una película tiene un director, pero no se requiere que una película tenga un director —imagínate que se anuncia una nueva película, pero la elección del director aún no se ha confirmado.

Nuestra tabladirectorios almacenará el nombre de cada director, y la tabla películas almacenará el nombre de la película así como una referencia al director de la película (si tuviera uno).

Vamos a crear esas dos tablas e insertar algunos datos en ellas:

CREATE TABLE directorios(
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL
);

INSERT INTO directorios(name) VALUES
('John Smith'),
('Jane Doe'),
('Xavier Wills')
('Bev Scott'),
('Bree Jensen');

CREATE TABLE peliculas(
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  director_id INTEGER REFERENCES directorios 
);

INSERT INTO peliculas(name, director_id) VALUES
('Pelicula 1', 1),
('Pelicula 2', 1),
('Pelicula 3', 2),
('Pelicula', NULL),
('Pelicula', NULL);

Tenemos cinco directorios, cinco películas, y tres de esas películas tienen directorios asignados. Director ID 1 tiene dos películas, y director ID 2 tiene una.

FULL OUTER JOIN

Ahora que tenemos algunos datos con los que trabajar, veamos el FULL OUTER JOIN.

UnFULL OUTER JOIN tiene algunas similitudes con un CROSS JOIN, pero tiene un par de diferencias clave.

La primera diferencia es que un FULL OUTER JOIN requiere una condición de combinación.

Una condición de combinación especifica cómo se relacionan entre sí las filas entre las dos tablas y según qué criterios deben unirse.

En nuestro ejemplo, nuetras tabla películas tiene una referencia al director a través de la columna director_id, y esta columna coincide con la columna id de la tabla directors. Estas son las dos columnas que usaremos como nuestra condición de combinación.

Así es como escribimos esta combinación entre nuestras dos tablas:

SELECT *
FROM peliculas
FULL OUTER JOIN directorios
  ON director.id = peliculas.director_id;

Observe la condición de combinación que especificamos que hace coincidir la película con su director: ON peliculas.director_id = directorios.id.

Nuestro conjunto de resultados parece una especie de producto cartesiano extraño:

  id  |  nombre   | director_id |  id  |     nombre
------+---------+-------------+------+--------------
    1 | Pelicula 1 |           1 |    1 | John Smith
    2 | Pelicula 2 |           1 |    1 | John Smith
    3 | Pelicula 3 |           2 |    2 | Jane Doe
    4 | Pelicula 4 |        NULL | NULL | NULL
    5 | Pelicula 5 |        NULL | NULL | NULL
 NULL | NULL       |        NULL |    5 | Bree Jensen
 NULL | NULL       |        NULL |    4 | Bev Scott
 NULL | NULL       |        NULL |    3 | Xavier Wills
(8 filas)

Las primeras filas que vemos son aquellas en las que la película tenía un director, y nuestra condición de combinación se evaluó como verdadera.

Sin embargo, después de esas filas, vemos cada una de las filas restantes de cada tabla—pero con valoresNULL  donde la otra tabla no tiene una coincidencia.

Note: Si no estás familiarizado con valores NULL , ve mi explicación aqui en este tutorial de operadores de SQL.

Aquí también vemos otra diferencia entre el CROSS JOIN y elFULL OUTER JOIN. Un FULL OUTER JOIN devuelve una fila distinta de cada tabla—a diferencia del CROSS JOIN que tiene múltiples.

INNER JOIN

El siguiente tipo de join, INNER JOIN, es uno de los tipos de join más utilizados.

Un inner join solo devuelve filas donde la condición de join es verdadera.

En nuestro ejemplo, un inner join entre nuestras tablas peliculas y directorios solo devolvería registros en los que a la película se le haya asignado un director.

La sintaxis es básicamente la misma que antes:

SELECT *
FROM peliculas
INNER JOIN directorios
  ON directorios.id = peliculas.director_id;

Nuestro resultado muestra las tres películas que tienen un director:

 id |  nombre   | director_id | id |    nombre
----+---------+-------------+----+------------
  1 | Pelicula 1 |           1 |  1 | John Smith
  2 | Pelicula 2 |           1 |  1 | John Smith
  3 | Pelicula 3 |           2 |  2 | Jane Doe
(3 filas)

Dado que un inner join solo incluye filas que coinciden con la condición del join, el orden de las dos tablas en el join no importa.

Si invertimos el orden de las tablas en la consulta, obtenemos el mismo resultado:

SELECT *
FROM directorios
INNER JOIN peliculas
  ON peliculas.director_id = directorios.id;
 id |    nombre    | id |  nombre   | director_id
----+------------+----+---------+-------------
  1 | John Smith |  1 | Pelicula 1 |           1
  1 | John Smith |  2 | Pelicula 2 |           1
  2 | Jane Doe   |  3 | Pelicula 3 |           2
(3 filas)

Dado que enumeramos la tabla  directorios primero en esta consulta y selecciona todas las columnas (SELECT *), vemos los datos de la columna directorios primero y luego las columnas de peliculas—pero el resultado es el mismo.

Esta es una propiedad útil de los inner joins, pero no es cierta para todos los tipos de joins—como nuestro próximo tipo de join.

LEFT JOIN / RIGHT JOIN

Los siguientes tipos de joins usan un modificador (LEFT or RIGHT) que afecta qué datos de la tabla se incluyen en el conjunto de resultados.

Nota: el LEFT JOIN yRIGHT JOIN también puede ser referido como LEFT OUTER JOIN y RIGHT OUTER JOIN.

Estos joins se utilizan en consultas en las que queremos devolver todos los datos de una tabla en particular y, si existe, los datos de la tabla asociada también.

Si los datos asociados no existen, aún recuperamos todos los datos de la tabla "primaria".

Es una consulta de información sobre algo en particular e información adicional si esta información adicional existe.

Esto será simple de entender con un ejemplo. Vamos a encontrar todas las películas y sus directorios, pero sin importar si tienen director o no—esto es adicional:

SELECT *
FROM peliculas
LEFT JOIN directorios
  ON directorios.id = peliculas.director_id;
pe

La consulta sigue nuestro mismo patrón que antes—acabamos de especificar el join como un LEFT JOIN.

En este ejemplo, la tabla peliculas es la tabla de la "derecha".

Si escribimos la consulta en una línea, hace que esto sea un poco más fácil de ver:

... FROM peliculas LEFT JOIN directorios ...

Un left join devuelve todos los datos de la tabla de la "izquierda".

Un left join devuelve cualquier fila de la tabla "derecha"  que coincida con la condición del join.

Las filas de la tabla de la "derecha"  que no coinciden con la condición del join se devuelven comoNULL.

 id |  nombre   | director_id |  id  |    nombre
----+---------+-------------+------+------------
  1 | Pelicula 1 |           1 |    1 | John Smith
  2 | Pelicula 2 |           1 |    1 | John Smith
  3 | Pelicula 3 |           2 |    2 | Jane Doe
  4 | Pelicula 4 |        NULL | NULL | NULL
  5 | Pelicula 5 |        NULL | NULL | NULL
(5 filas)

Mirando ese conjunto de resultados, podemos ver por qué este tipo de join es útil para el tipo de consultas "todo esto y, si existe, algo de eso" .

RIGHT JOIN

El RIGHT JOIN trabaja exactamente como LEFT JOIN—excepto que las reglas sobre las dos tablas están invertidas.

En un right join, se devuelven todas las filas de la tabla "derecha" . La tabla  "izquierda" se devuelve condicionalmente en función de la condición del join.

Usemos la misma consulta que arriba, pero sustituyamos LEFT JOIN por RIGHT JOIN:

SELECT *
FROM peliculas
RIGHT JOIN directorios
  ON directorios.id = peliculas.director_id;
  id  |  nombre   | director_id | id |     nombre
------+---------+-------------+----+--------------
    1 | Pelicula 1 |           1 |  1 | John Smith
    2 | Pelicula 2 |           1 |  1 | John Smith
    3 | Pelicula 3 |           2 |  2 | Jane Doe
 NULL | NULL    |        NULL |  5 | Bree Jensen
 NULL | NULL    |        NULL |  4 | Bev Scott
 NULL | NULL    |        NULL |  3 | Xavier Wills
(6 filas)

Nuestro conjunto de resultados ahora devuelve la fila de cada director en la tabla directorios y, si existe, los datos de las peliculas.

Todo lo que hemos hecho es cambiar qué tabla estamos considerando como la "primaria" —la tabla de la que queremos ver todos los datos, independientemente de si existen sus datos asociados.

LEFT JOIN / RIGHT JOIN en aplicaciones de producción

En una aplicación de producción, solo uso LEFT JOIN y nunca uso RIGHT JOIN.

Hago esto porque, en mi opinión, un LEFT JOIN hace que la consulta sea más fácil de leer y comprender.

Cuando escribo consultas, me gusta pensar en comenzar con un conjunto de resultados "base", digamos que todas las películas, y entonces traer o sustraer) grupos de cosas de esa base.

Debido a que me gusta comenzar con una base, el LEFT JOIN se ajusta a esta línea de pensamiento. Quiero todas las filas de mi tabla base (la tabla "izquierda") y condicionalmente quiero las filas de la tabla "derecha".

En la práctica, no creo haber visto nunca un RIGHT JOINen una aplicación de producción. No hay nada de malo con un RIGHT JOIN—solo creo que hace que la consulta sea más difícil de entender.

Reescribiendo RIGHT JOIN

Si quisiéramos cambiar nuestro escenario anterior y, en su lugar, devolver a todos los directorios y condicionalmente sus películas, podemos reescribir fácilmente RIGHT JOIN en LEFT JOIN.

Todo lo que tenemos que hacer es cambiar el orden de las tablas en la consulta y cambiar de RIGHT a LEFT:

SELECT *
FROM directorios
LEFT JOIN peliculas
  ON peliculas.director_id = directorios.id;
Nota: Me gusta poner ON en la tabla que se está uniendo (la tabla "derecha" —en el ejemplo anterior peliculas) primero en la condición del join (ON peliculas.director_id = ...)—pero esa es solo mi preferencia personal.

Filtrado usando LEFT JOIN

Hay dos casos de uso para usar un LEFT JOIN (oRIGHT JOIN).

El primer caso de uso que ya lo hemos cubierto: devolver todas las filas de una tabla y condicionalmente de otra.

El segundo caso de uso es devolver filas de la primera tabla donde los datos de la segunda tabla no están presentes.

El escenario sería así: encontrar directorios que no pertenezcan a una película.

Para hacer esto, comenzaremos con un LEFT JOIN y nuestra tabla de directorios será la tabla primaria o la tabla "izquierda":

SELECT *
FROM directorios
LEFT JOIN peliculas
  ON peliculas.director_id = directorios.id;

Para un director que no pertenece a una película, las columnas de la tabla peliculas son NULL:

 id |     nombre     |  id  |  nombre   | director_id
----+--------------+------+---------+-------------
  1 | John Smith   |    1 | Pelicula 1 |           1
  1 | John Smith   |    2 | Pelicula 2 |           1
  2 | Jane Doe     |    3 | Pelicula 3 |           2
  5 | Bree Jensen  | NULL | NULL    |        NULL
  4 | Bev Scott    | NULL | NULL    |        NULL
  3 | Xavier Wills | NULL | NULL    |        NULL
(6 filas)

En nuestro ejemplo, los ID de director 3, 4, y 5 no pertenecen a una película.

Para filtrar nuestro conjunto de resultados solo a estas filas, podemos agregar una cláusula WHERE  para devolver solo las filas donde los datos de la película son NULL:

SELECT *
FROM directorios
LEFT JOIN peliculas
  ON peliculas.director_id = directorios.id
WHERE peliculas.id IS NULL;
 id |     nombre     |  id  | nombre | director_id
----+--------------+------+------+-------------
  5 | Bree Jensen  | NULL   | NULL |        NULL
  4 | Bev Scott    | NULL   | NULL |        NULL
  3 | Xavier Wills | NULL   | NULL |        NULL
(3 filas)

¡Y allí están nuestros tres directorios sin película!

Es común usar la columnaid de la tabla para filtrar (WHERE peliculas.id IS NULL), pero todas las columnas de la tabla  peliculas son NULL—por lo que cualquiera de ellas funcionaría.

(Dado que sabemos que todas las columnas de la tabla peliculas serán NULL, en la consulta anterior podríamos escribir SELECT directorios.* en lugar de SELECT * para devolver toda la información del director.)

UsandoLEFT JOIN para encontrar coincidencias

En nuestra consulta anterior encontramos directorios que no pertenecían a películas.

Usando nuestra misma estructura, podríamos encontrar directorios que si pertenecieran a películas cambiando nuestra condición WHERE  para buscar filas donde los datos de la película no sean NULL:

SELECT *
FROM directorios
LEFT JOIN peliculas
  ON peliculas.director_id = directorios.id
WHERE peliculas.id IS NOT NULL;
 id |    nombre    | id |  nombre   | director_id
----+------------+----+---------+-------------
  1 | John Smith |  1 | Pelicula 1 |           1
  1 | John Smith |  2 | Pelicula 2 |           1
  2 | Jane Doe   |  3 | Pelicula 3 |           2
(3 filas)

Esto puede parecer útil, pero en realidad acabamos de volver a implementar INNER JOIN!

Múltiple joins o combinaciones

Hemos visto cómo unir dos tablas, pero ¿qué pasa con las combinaciones múltiples seguidas?

En realidad es bastante simple, pero para ilustrar esto necesitamos una tercera tabla: tickets.

Esta tabla representará las entradas vendidas para una película:

CREATE TABLE tickets(
  id SERIAL PRIMARY KEY,
  movie_id INTEGER REFERENCES peliculas NOT NULL
);

INSERT INTO tickets(pelicula_id) VALUES (1), (1), (3);

La tabla tickets solo tiene un id y una referencia a la película: pelicula_id.

También hemos insertado dos boletos vendidos para la película ID 1, y un boleto vendido para la película ID 3.

¡Ahora, unamos losdirectorios a las peliculas—y luego peliculas a tickets!

SELECT *
FROM directorios
INNER JOIN peliculas
  ON peliculas.director_id = directors.id
INNER JOIN tickets
  ON tickets.pelicula_id = peliculas.id;

Dado que se trata de inner joins, el orden en que escribimos las combinaciones no importa. Podríamos haber comenzado con los tickets, luego combinar a las peliculas, y luego combinar a los directorios.

De nuevo, todo se reduce a lo que se está tratando de consultar y lo que hace que la consulta sea más comprensible.

En nuestro conjunto de resultados, notaremos que hemos reducido aún más las filas que se devuelven:

 id |    nombre    | id |  nombre   | director_id | id | pelicula_id
----+------------+----+---------+-------------+----+----------
  1 | John Smith |  1 | Pelicula 1 |           1 |  1 |        1
  1 | John Smith |  1 | Pelicula 1 |           1 |  2 |        1
  2 | Jane Doe   |  3 | Pelicula 3 |           2 |  3 |        3
(3 filas)

Esto tiene sentido porque hemos agregado otro INNER JOIN. En efecto, esto agrega otra condición "AND"a nuestra consulta.

Nuestra consulta esencialmente dice: "devuelve todos los directorios que pertenezcan a películas pero que también tienen tickets vendidos."

Si, en cambio, quisiéramos encontrar directorios que pertenezcan a películas que no hayan vendido tickest todavía, podríamos sustituir nuestro último INNER JOIN por un LEFT JOIN:

SELECT *
FROM directorios
JOIN peliculas
  ON peliculas.director_id = directorios.id
LEFT JOIN tickets
  ON tickets.pelicula_id = peliculas.id;

Podemos ver que la Pelicula 2 hora está de vuelta en el conjunto de resultados:

 id |    nombre    | id |  nombre   | director_id |  id  | pelicula_id
----+------------+----+---------+-------------+------+----------
  1 | John Smith |  1 | Pelicula 1 |           1 |    1 |        1
  1 | John Smith |  1 | Pelicula 1 |           1 |    2 |        1
  2 | Jane Doe   |  3 | Pelicula 3 |           2 |    3 |        3
  1 | John Smith |  2 | Pelicula 2 |           1 | NULL |     NULL
(4 filas)

Esta película no tuvo ninguna venta de boletos, por lo que anteriormente se excluyó del conjunto de resultados debido a INNER JOIN.

Dejaré esto como Ejercicio para el lector, pero ¿cómo encontraría directorios que pertenezcan a películas que no tienen venta de boletos?

Orden de ejecución de la combinación

Al final, realmente no nos importa en qué orden se ejecutan las combinaciones.

Una de las diferencias clave entre SQL y otros lenguajes de programación modernos es que SQL es un lenguaje declarativo.

Esto significa que especificamos el resultado que queremos, pero no especificamos los detalles de ejecución; esos detalles se dejan al planificador de consultas de la base de datos. Especificamos las combinaciones que queremos y las condiciones en ellas y el planificador de consultas se encarga del resto.

Pero, en realidad, la base de datos no une tres tablas al mismo tiempo. En su lugar, probablemente unirá las dos primeras tablas en un resultado intermedio y luego unirá ese conjunto de resultados intermedios a la tercera tabla.

(Nota: Esta es una explicación algo simplificada.)

Entonces, como estamos trabajando con múltiples joins en las consultas, podemos pensar en ellas como una serie de joins entre dos tablas, aunque una de esas tablas puede llegar a ser bastante grande.

Joins con condiciones extra

El último tema que cubriremos es un join con condiciones adicionales.

Similar a una cláusula WHERE, podemos agregar tantas condiciones como queramos a nuestras condiciones de join.

Por ejemplo, si quisiéramos encontrar películas con directorios que no se llamen "John Smith", podríamos agregar esa condición extra a nuestra join con un AND:

SELECT *
FROM peliculas
INNER JOIN directorios
  ON directors.id = peliculas.director_id
  AND directorios.name <> 'John Smith'

Podemos usar cualquier operador que pondríamos en una cláusula WHEREen esta condición de join.

También obtenemos el mismo resultado de esta consulta si colocamos la condición en una cláusula WHERE en su lugar:

SELECT *
FROM peliculas
INNER JOIN directorios
  ON directorios.id = peliculas.director_id
WHERE directorios.name <> 'John Smith';

Hay algunas diferencias sutiles que ocurren bajo el capó aquí, pero para el propósito de este artículo, el conjunto de resultados es el mismo.

(Si no está familiarizado con todas las formas en que puede filtrar una consulta SQL, consulte el artículo mencionado anteriormente aquí.)

La realidad acerca de escribir consultas con joins

En realidad, solo uso combinaciones de tres maneras diferentes:

INNER JOIN

El primer caso de uso son los registros donde existe la relación entre dos tablas. Esto lo cumple el INNER JOIN.

Estas son situaciones como encontrar "películas que tienen directorios" o "usuarios con publicaciones".

LEFT JOIN

El segundo caso de uso son los registros de una tabla —y si existe la relación—los registros de una segunda tabla. Esto lo cumple el LEFT JOIN.

Estas son situaciones como encontrar "películas con directorios si es que tienen uno" o "usuarios con publicaciones si tienen alguna."

LEFT JOIN exclusión

El tercer caso de uso más común es nuestro segundo caso de uso para un LEFT JOIN: encontrar registros en una tabla que no tienen una relación en la segunda tabla.

Estas son situaciones como "películas sin directorios" o "usuarios sin publicaciones."

Dos tipos de combinaciones o joins muy útiles

Creo que nunca he usado un FULL OUTER JOIN o un RIGHT JOIN en una aplicación de producción. El caso de uso simplemente no aparece con la frecuencia suficiente o la consulta se puede escribir de una manera más clara (en el caso de RIGHT JOIN).

Ocasionalmente, he usado un CROSS JOIN para cosas como distribuir registros en un rango de fechas (como lo miramos al principio), pero ese escenario tampoco aparece con demasiada frecuencia.

Entonces, ¡buenas noticias! En realidad, solo hay dos tipos de combinaciones que debe comprender para el 99,9 % de los casos de uso que encontraras: INNER JOIN y LEFT JOIN!

Si te gustó este artículo, me puedes seguir en Twitter donde hablo de base de datos y temas relacionados con el desarrollo.

¡Gracias por leer!

John

P.S. un consejo adicional por haber leido hasta el final: la mayoría de los sistemas de base de datos te permitiran escribir simplemente JOIN en lugar deINNER JOIN—lo que te ahorrará un poco de tipeo adicional. :)