LEFT, RIGHT and FULL JOIN: Keep unmatched rows

An INNER JOIN works well when we are interested only in related rows. The problem changes when we also need to see what was left out: customers without orders, products that have never been sold, or records that appear in one source but not another.

Outer joins keep those rows even when they find no matching row. Columns from the unmatched side are filled with NULL, which represents an unknown or unavailable value.

The idea

The direction of the union indicates which set will be kept:

  • LEFT JOIN preserves all rows of the table written to the left.
  • RIGHT JOIN preserves all rows in the table written to the right.
  • FULL JOIN preserves the rows on both sides, whether they coincided or not.

LEFT JOIN: preserve the left side

LEFT JOIN keeps every row from the set written before the join and adds columns from matches found on the right.

FROM dbo.TablaIzquierda AS i
LEFT JOIN dbo.TablaDerecha AS d
    ON d.IdRelacionado = i.Id

We want to review all customers, even those who have not placed orders:

SELECT
    c.IdCliente,
    c.Nombre,
    p.IdPedido,
    p.Estado
FROM dbo.Cliente AS c
LEFT JOIN dbo.Pedido AS p
    ON p.IdCliente = c.IdCliente
ORDER BY
    c.IdCliente,
    p.IdPedido;
ClientId Name OrderId State
1 Ana Torres 1 Confirmed
1 Ana Torres 2 Sent
2 Bruno Diaz 3 Pending
3 Carla Ruiz NULL NULL
4 Diego Soto 4 Canceled
5 Elena Mora 5 Confirmed

Identifiers 1, 2, 4, and 5 find orders. The identifier 3, corresponding to Carla, also remains because LEFT JOIN preserves the entire Cliente set.

Full set of clients preserved by LEFT JOIN

Carla remains because Cliente is on the left. Since there is no order related to its identifier, the columns coming from Pedido contain NULL.

This behavior allows you to specifically find customers without orders:

SELECT
    c.IdCliente,
    c.Nombre
FROM dbo.Cliente AS c
LEFT JOIN dbo.Pedido AS p
    ON p.IdCliente = c.IdCliente
WHERE p.IdPedido IS NULL;

RIGHT JOIN

RIGHT JOIN retains the table written to the right. With Cliente before RIGHT JOIN and Pedido after, all rows from Pedido are preserved:

FROM dbo.TablaIzquierda AS i
RIGHT JOIN dbo.TablaDerecha AS d
    ON d.IdRelacionado = i.Id
SELECT
    c.IdCliente,
    c.Nombre,
    p.IdPedido,
    p.Estado
FROM dbo.Cliente AS c
RIGHT JOIN dbo.Pedido AS p
    ON p.IdCliente = c.IdCliente
ORDER BY
    p.IdPedido;

RIGHT JOIN retains all rows from Pedido. When a row does not find a customer, the columns from Cliente are completed with NULL.

Order Set preserved by RIGHT JOIN

Carla is left out because she has no order. With the data in the example, all orders find a customer; therefore the unique region of Pedido does not contain identifiers.

To identify only the orders that do not find a customer, we filter the rows without a match:

SELECT
    p.IdPedido,
    p.IdCliente,
    p.Estado
FROM dbo.Cliente AS c
RIGHT JOIN dbo.Pedido AS p
    ON p.IdCliente = c.IdCliente
WHERE c.IdCliente IS NULL
ORDER BY p.IdPedido;

This second query represents only the region unique to Pedido. In the current data it returns no rows; If there was an order without a related customer, it would appear here with its data of Pedido.

The same operation can be written with LEFT JOIN if the order of the tables is swapped. The preserved set depends on the table that remains on the side indicated by the union.

FULL JOIN

FULL JOIN is useful when comparing two sets. Preserve matches, left-exclusive rows, and right-exclusive rows:

FROM dbo.TablaIzquierda AS i
FULL JOIN dbo.TablaDerecha AS d
    ON d.IdRelacionado = i.Id
SELECT
    a.Clave,
    a.Valor AS ValorAnterior,
    n.Valor AS ValorNuevo
FROM dbo.DatosAnteriores AS a
FULL JOIN dbo.DatosNuevos AS n
    ON n.Clave = a.Clave;

All three regions are preserved here: rows that only exist in DatosAnteriores, matches, and rows that only exist in DatosNuevos.

Regions preserved by FULL OUTER JOIN

What should be checked

The optional table is the side of an outer join that may find no match and therefore contribute columns containing NULL. A condition on that side can accidentally turn a LEFT JOIN into the practical equivalent of an INNER JOIN:

-- Los clientes sin pedido quedan fuera porque p.Estado es NULL.
WHERE p.Estado = 'Confirmado';

When we want to preserve them, that condition can be part of ON or it must explicitly consider the null value. The choice depends on which rows we intend to keep, not on a mechanical rule.