skip to content

USING and NATURAL Joins

Shorthand join syntaxes that match columns by name instead of an explicit ON. Interviewers ask about them to see if you know USING merges the join column into one output column and why NATURAL JOIN is a schema-change time bomb.

part ofSQLoverview, primer and where to startread it →
on this pageshow

questions

5

How does JOIN ... USING (customer_id) differ from an equivalent JOIN ... ON predicate?

level: juniorimportance: must knowfreq 55%

answer

  1. same rows, different result shape
  2. count the columns SELECT * returns
  3. names must be identical on both sides
  4. the matched pair becomes one column
  5. referenced unqualified, no ambiguity error

basics

~20 s

USING applies the same equality test as the matching ON predicate, but merges the two same-named columns into a single output column: SELECT * returns customer_id once, and the rest of the query references it unqualified.

solid answer

~40 s

`orders o JOIN customers c USING (customer_id)` is shorthand for `ON o.customer_id = c.customer_id`, and the same rows survive either way. The difference is the shape of the result. `USING` requires the column to carry the *same name* on both sides, compares the pair with `=`, and then merges them into one column that belongs to the join itself. So `SELECT *` returns `customer_id` once instead of twice, the merged column is listed first, and `SELECT customer_id` is unambiguous — after an `ON` join the same select fails with an ambiguous-column error. `USING` takes a list, `USING (order_id, line_no)`, but expresses nothing except equality between identically named columns; different names, inequalities or extra predicates need `ON`.

code

sql · 5 lines
sql
-- USING: one customer_id in the output, referenced unqualified
SELECT customer_id, customer_name, SUM(order_total) AS lifetime_value
FROM orders o
JOIN customers c USING (customer_id)
GROUP BY customer_id, customer_name;

go deeper

for a junior

Be ready to say that USING (col) is shorthand for an equality on a same-named column, and that the column then appears once in the output and is written without a table prefix.

for a middle

Explain the mechanics: the merged column belongs to the join, not to either table, which is why SELECT * returns it once and a bare reference is unambiguous. Know what USING cannot express.

for a senior

Show the judgment call — USING for readable multi-table chains on a shared key, ON when names differ, the predicate is not pure equality, or the target engine lacks USING.

for a principal

Own the convention: a naming standard where foreign keys match their referenced column makes USING viable team-wide, but it also constrains portability and every future rename. Decide which you are buying.

## Two spellings of the same test A join needs a source of rows on each side and a condition saying which pairs survive. `ON` accepts any boolean expression over the two sides: `ON o.customer_id = c.customer_id`, `ON a.x = b.x AND a.y > b.y`, `ON o.placed_at BETWEEN c.valid_from AND c.valid_to`. `USING` is shorthand for one specific shape of that condition — equality between columns that have the same name in both tables. ```sql SELECT * FROM orders o JOIN customers c USING (customer_id); -- join condition is exactly: o.customer_id = c.customer_id ``` Which rows come out is identical between the two spellings. What differs is the shape of the result. ## The merged column With `ON`, both `o.customer_id` and `c.customer_id` remain columns of the join result. `SELECT *` therefore returns `customer_id` twice, and a bare `SELECT customer_id` fails with an ambiguous-column error, because two visible columns answer to that name. With `USING`, the standard merges each listed pair into **one** column that belongs to the join itself rather than to either table. `SELECT *` returns `customer_id` once; the merged columns are listed first, followed by the remaining columns of the left table and then the right; and `SELECT customer_id` is unambiguous. That single difference in result shape is what most interview questions about `USING` are really testing. ```sql -- 3 output columns: customer_id, order_total, customer_name SELECT * FROM orders o JOIN customers c USING (customer_id); -- 4 output columns: customer_id appears twice SELECT * FROM orders o JOIN customers c ON o.customer_id = c.customer_id; ``` That also means `SELECT *` over a chain of joins stays readable: joining five tables on `customer_id` with `USING` yields one `customer_id`, not five. ## Referring to the column afterwards Because the merged column belongs to the join, the portable way to reference it in `SELECT`, `WHERE`, `GROUP BY` and `ORDER BY` is unqualified — just `customer_id`. Engines differ on whether a qualified reference such as `o.customer_id` is still legal after `USING`: Oracle rejects a qualifier on a `USING` column, while PostgreSQL still resolves it to that table's own copy. Writing it unqualified is the habit that works everywhere. ## What USING cannot express `USING` accepts a list of columns — `USING (order_id, line_no)` merges and equates both, ANDed together. It cannot express: - **different names on the two sides** (`orders.customer_ref` versus `customers.customer_id`) — that needs `ON`, or a rename upstream; - **anything but equality** — no `>`, no `BETWEEN`, no function calls; - **extra predicates** you want evaluated as part of the join condition (which matters for outer joins, where a predicate in `ON` runs before NULL-extension and the same predicate in `WHERE` runs after). Nothing about `USING` changes NULL comparison: it is still `=`, so a row whose key is NULL matches nothing on the other side. The two columns must also be comparable types; the merge produces one value, so pairing a `varchar` key with an `integer` key is either an error or an implicit coercion depending on the engine. ## Under outer joins In an outer join the two sides can disagree about whether a value exists. The standard defines the merged column as the coalesce of the two: in `a LEFT JOIN b USING (id)` an unmatched left row shows the left `id`, and in a `FULL JOIN` a row present on only one side shows whichever `id` exists. With `ON` you would write `COALESCE(a.id, b.id)` yourself. ## Portability `USING` is standard SQL and is available in PostgreSQL, MySQL, SQLite and Oracle. SQL Server's join syntax has no `USING` clause at all (its `USING` keyword belongs to `MERGE`), so code that must run there is written with `ON`. ## Choosing between them Prefer `USING` when the key genuinely carries the same name on both sides: it is shorter, it removes the ambiguous-column trap, and it gives one clean key column — especially valuable in outer joins and long join chains. Prefer `ON` when the names differ, when the condition is not pure equality, or when portability to an engine without `USING` matters. What `USING` should *not* tempt you into is `NATURAL JOIN`, which throws away the explicit column list entirely and lets a later migration redefine the join for you.

  • Can USING join columns whose names differ, or whose types differ?
    Names must be the same identifier in both tables — `orders.customer_ref` and `customers.customer_id` cannot be joined with `USING`, you need `ON o.customer_ref = c.customer_id`. Types must be comparable, since the merged column has to hold one value; pairing a text key with a numeric key is an error or an implicit coercion depending on the engine.
  • Does USING change how NULL join keys behave?
    No. `USING` is still an equality comparison, so a NULL key matches nothing on the other side, exactly as with `ON a.k = b.k`. `USING` only changes the output shape of the join — it merges the matched pair into one column. If you need NULLs to match each other, you need a different predicate, not a different join spelling.
  • How do you add a second, non-equality condition to a join written with USING?
    You cannot put it in `USING` — the clause takes only column names. Either move the whole condition to `ON` (`ON o.customer_id = c.customer_id AND o.placed_at >= c.signed_up_at`), or keep `USING` and put the extra predicate in `WHERE`. For an inner join those are equivalent; for an outer join they are not, because `WHERE` filters after NULL-extension.

ON introduces two people who happen to share a name and seats them both; USING recognises they are the same person and sets one place at the table.

saying these in an interview costs you the question

  • Claims USING and ON return different rows
  • Thinks USING can compare differently named columns
  • Expects SELECT * to show the key column twice after USING
  • Qualifies the USING column as o.customer_id everywhere and calls it portable
  • Believes USING makes NULL keys match each other

context

open as a page

Which columns does NATURAL JOIN match on, and what does it return if the tables share none?

level: middleimportance: should knowfreq 45%

basics

~20 s

NATURAL JOIN implicitly equates every column name the two tables share and merges each matched pair into one output column. If they share no column names the join condition is empty, so the result is a Cartesian product.

open as a page

In JOIN ... USING (dept_id), how must you reference dept_id, and is d.dept_id portable?

level: middleimportance: should knowfreq 35%

basics

~20 s

The standard makes the USING column a column of the join, referenced unqualified as dept_id. Qualifying it is not portable: Oracle rejects a qualifier on a USING column, while PostgreSQL resolves it to that table's own copy.

open as a page

A NATURAL JOIN report returned rows until a migration added created_at to both tables — why, and how do you fix it?

level: seniorimportance: should knowfreq 32%

basics

~20 s

NATURAL JOIN re-derives its condition from whatever column names the two tables currently share, so created_at silently joined the key list and rows now match only when both timestamps are equal — almost never. Replace it with an explicit ON or USING.

open as a page

With FULL OUTER JOIN ... USING (product_id), what does the single product_id column hold for unmatched rows?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

The merged column is the coalesce of both sides, so a row present on only one side shows that side's product_id rather than NULL. With an ON join you would have to write COALESCE(a.product_id, b.product_id) yourself.

open as a page