Skip to content
wannacode

UNION, INTERSECT, EXCEPT and CROSS JOIN

In short: UNION combines the results of two queries, INTERSECT keeps the common rows, EXCEPT removes the rows of the second from the first, and CROSS JOIN returns every pair of rows.

Problem

One list from two places

The shop sends an autumn newsletter to its active customers. Active means anyone who has ordered since 1 September 2026 or written a review since 1 June.

Orders and reviews are in different tables. If you join them with JOIN, only the customers who have both remain. We need the two lists, put one under the other.

The idea

UNION

UNION puts the result of the second query under that of the first and removes the repeats:

SQLdatabase: shop

11 customers. Each of the two queries is whole, with its own FROM, JOIN and WHERE. The column names come from the first.

UNION ALL keeps the repeats too:

SQLdatabase: shop

17 rows: five customers appear more than once (with several orders, or with both an order and a review). The newsletter needs UNION, or someone will get the email three times. UNION ALL is for when every row is a separate event and has to be counted.

Try it

A coffee tasting

The shop is inviting everyone who has bought coffee beans (product 17) or written a review of them to a tasting. Show their names, each once.

SQLdatabase: shop

See it

The columns must match

The two queries give rows for one and the same table, so they must have the same number of columns:

SQLdatabase: shop

Columns are paired by position, not by name. If the first query returns name, email, the second must also return two columns, and in a sensible order: name under name.

For a customer's whole history, add a column that says where the row comes from. Maria's orders and reviews, by date:

SQLdatabase: shop

ORDER BY at the end sorts the whole result, not just the second query. The columns are called day and event, as in the first query.

The idea

INTERSECT and EXCEPT

INTERSECT keeps the rows that are in both results. The products that have been ordered and also have a review:

SQLdatabase: shop

13 products. These are exactly the products with a review, because every product has been ordered (you saw it in the lesson on LEFT JOIN).

EXCEPT keeps the rows of the first result that are not in the second. The products without a review:

SQLdatabase: shop

12 products. LEFT JOIN reviews … WHERE r.id IS NULL gives the same. EXCEPT reads like a sentence: “all products, minus those with a review”. With EXCEPT the order of the queries matters, with UNION and INTERSECT it does not.

Try it

Asking for a review

Which products have been bought since 1 September 2026 but have no review at all? Their buyers will be asked for one. Show the product names.

SQLdatabase: shop

The idea

CROSS JOIN

Sometimes you need every pair. The report by city and category must have a row for every city and every subcategory, even where there are no sales. The grid is made with CROSS JOIN: every row of one table with every row of the other, without ON.

SQLdatabase: shop

6 cities times 6 subcategories, 36 rows. DISTINCT is needed because the city repeats in customers: without it there are 114 rows, one for every customer with a city and every subcategory.

You will be able to fill the grid with the sales (and the zeros) in the module on subqueries. The grid is the first half.

See it

Watch the number of rows

CROSS JOIN multiplies. 60 orders and 152 order lines:

SQLdatabase: shop

9,120 pairs, almost all of them meaningless. The same happens if you forget ON. In SQLite a JOIN without ON is a CROSS JOIN, with no error:

SQLdatabase: shop

1,200 rows (60 × 20) instead of 60. PostgreSQL gives an error in this case. If a report suddenly shows many more rows or much more money than expected, check the conditions in ON first.

Exercise

The grid of top categories

The yearly report is by city and top category (“Food”, “Home”). Make the grid: every city (without the empty one) with every top category, each pair once.

SQLdatabase: shop
More precisely, if you are curious

UNION in SQLite often returns the rows sorted, because that is how it removes repeats. This is not promised. Whenever the order matters, write ORDER BY at the end.

INTERSECT and EXCEPT exist in SQLite, PostgreSQL and SQL Server. In Oracle EXCEPT is called MINUS, and newer versions understand EXCEPT too. MySQL has them since version 8.0.31.

Test

Check yourself

1. What is the difference between UNION and UNION ALL?
2. Which products does A EXCEPT B give, if A are the ordered ones and B those with a review?
3. Why does SELECT name, email … UNION SELECT name … give an error?
4. A table with 60 rows CROSS JOIN a table with 152 rows. How many rows come out?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·