| |

SQL 38 🛢️ Cartesian Products with CROSS JOIN

The CROSS JOIN produces the Cartesian product of two tables: every row from the left table paired with every row from the right table. It has no join condition, and it makes no attempt to match rows by any criterion. If the left table has 100 rows and the right table has 50 rows, the result has 5,000 rows. The cross join is rarely what a query wants by accident, but it is exactly what is needed when generating combinations: every size with every color, every date with every region, every employee with every training course.

The cross join is the simplest of the joins, and it is the one that a missing join condition produces by accident. When a query writes FROM a, b without a WHERE clause that connects them, the result is a Cartesian product. This is why the explicit JOIN ... ON syntax is preferred: it makes the join condition mandatory, and the absence of a condition is visible.

This chapter covers the syntax of CROSS JOIN, the Cartesian product, the difference between an explicit cross join and an accidental one, the use cases where the cross join is the right tool, the performance implications, and the patterns for generating combinations.

Key point: CROSS JOIN produces the Cartesian product: every row from one table paired with every row from the other. It has no ON clause. Use it deliberately for generating combinations, not accidentally by omitting a join condition. A cross join of two large tables produces a result that grows multiplicatively and can overwhelm the database.


Why CROSS JOIN exists

The combination problem. Some queries need every combination of two sets. A product catalog needs every size paired with every color. A scheduling system needs every employee paired with every shift. A reporting tool needs every month paired with every region. The cross join generates these combinations in a single query.

The accidental join problem. When a query lists two tables in the FROM clause without a join condition, the database produces the Cartesian product. This is almost never the intent, and the result is usually wrong: duplicated rows, incorrect aggregates, and a result set that grows without bound. The CROSS JOIN is the explicit form of this operation, and the explicit form makes the intent visible.

The generation problem. The cross join is used to generate sequences and grids: a calendar of dates, a matrix of numbers, a list of all pairs. It is the SQL mechanism for expanding two small sets into a larger one.

The testing problem. The cross join is used to generate test data: every combination of input parameters, every pair of users, every permutation of states. The result is a comprehensive test matrix.

The symmetry problem. The cross join treats both tables as equal. It makes no distinction between left and right, because it makes no comparison. The result is symmetric: reversing the table order produces the same set of combinations in a different order.


a. Basic CROSS JOIN syntax

The CROSS JOIN keyword connects the two tables without an ON clause.

SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;

The result has one row for each combination of a size and a color. If sizes has three rows and colors has four rows, the result has twelve rows.

The comma syntax produces the same result:

SELECT s.size, c.color
FROM sizes s, colors c;

The two forms are equivalent, but the explicit CROSS JOIN is clearer. It signals that the Cartesian product is intentional, not the result of a missing condition.


b. The Cartesian product

The Cartesian product is the set of all ordered pairs. In SQL, it is the result of joining two tables without a condition.

Left tableRight tableResult
3 rows4 rows12 rows
10 rows10 rows100 rows
100 rows50 rows5,000 rows
1,000 rows1,000 rows1,000,000 rows

The size of the result is the product of the sizes of the two tables. This is why a cross join of two large tables is dangerous: the result grows multiplicatively.

A small example:

-- sizes
-- ┌───────┐
-- │ size  │
-- ├───────┤
-- │ S     │
-- │ M     │
-- │ L     │
-- └───────┘

-- colors
-- ┌───────┐
-- │ color │
-- ├───────┤
-- │ Red   │
-- │ Blue  │
-- └───────┘

SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;

The result:

sizecolor
SRed
SBlue
MRed
MBlue
LRed
LBlue

Every size appears with every color. Six rows from three sizes and two colors.


c. Accidental Cartesian products

The most common cause of an accidental Cartesian product is a missing join condition in the implicit join syntax.

-- Missing WHERE clause: Cartesian product
SELECT c.name, o.order_id
FROM customers c, orders o;

This query returns every customer paired with every order. If there are 1,000 customers and 10,000 orders, the result has 10,000,000 rows. The query is almost certainly wrong.

The fix is to add the join condition:

SELECT c.name, o.order_id
FROM customers c, orders o
WHERE c.customer_id = o.customer_id;

Or to use the explicit join syntax:

SELECT c.name, o.order_id
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id;

The explicit syntax makes the missing condition a syntax error. The database rejects FROM a JOIN b without an ON clause, which prevents the accidental Cartesian product.

A partial join condition is another source of accidental Cartesian products. If a query joins three tables but connects only two of them, the third table is cross-joined with the result.

-- Connects customers to orders, but products is cross-joined
SELECT c.name, o.order_id, p.name
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
CROSS JOIN products p;  -- missing condition: intentional or not?

If the cross join with products is intentional, the explicit keyword documents it. If it is not, the missing condition produces a result that is much larger than expected.


d. Use cases for CROSS JOIN

The cross join is the right tool when the query needs every combination of two sets.

Generating a product matrix. A catalog that offers sizes and colors needs every combination available for purchase.

SELECT p.name, s.size, c.color
FROM products p
CROSS JOIN sizes s
CROSS JOIN colors c
WHERE p.has_sizes AND p.has_colors;

Generating a calendar. A report that needs every date in a range paired with every region uses a date table cross-joined with a region table.

SELECT d.date, r.region
FROM dates d
CROSS JOIN regions r
WHERE d.date BETWEEN '2026-01-01' AND '2026-12-31';

Generating a test matrix. A test suite that exercises every combination of input parameters uses a cross join.

SELECT b.browser, o.os, v.version
FROM browsers b
CROSS JOIN operating_systems o
CROSS JOIN versions v;

Pairing rows within a table. A self-cross-join produces every pair of rows, including pairs of a row with itself.

SELECT a.name AS person1, b.name AS person2
FROM people a
CROSS JOIN people b
WHERE a.id < b.id;  -- exclude self-pairs and duplicates

The a.id < b.id condition excludes the diagonal (a person paired with themselves) and the reverse pairs (Alice-Bob and Bob-Alice), leaving each unordered pair exactly once.


e. Performance implications

The size of the result is the product of the sizes of the tables. This makes the cross join the most expensive join when the tables are large.

Left rowsRight rowsResult rows
1010100
10010010,000
1,0001,0001,000,000
10,00010,000100,000,000

A cross join of two tables with 10,000 rows each produces 100 million rows. The database must generate, sort, and return all of them, which consumes memory, CPU, and network bandwidth.

The cross join is appropriate when at least one of the tables is small. A table of 365 dates cross-joined with a table of 10 regions produces 3,650 rows, which is manageable. A table of 1,000 products cross-joined with a table of 1,000 customers produces 1,000,000 rows, which is not.

When the cross join is used, the filter conditions should be applied after the cross join to reduce the result. If the filter can be applied before the join, it reduces the size of the input and the result.


Complete Example Session

-- ============================================
-- PART 1: CREATE SAMPLE TABLES
-- ============================================
CREATE TABLE sizes (
    size VARCHAR(10)
);

CREATE TABLE colors (
    color VARCHAR(20)
);

INSERT INTO sizes VALUES ('S'), ('M'), ('L');
INSERT INTO colors VALUES ('Red'), ('Blue');
-- ============================================
-- PART 2: BASIC CROSS JOIN
-- ============================================
SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;
-- 6 rows: every size with every color
-- ============================================
-- PART 3: COMMA SYNTAX
-- ============================================
SELECT s.size, c.color
FROM sizes s, colors c;
-- Same 6 rows
-- ============================================
-- PART 4: ACCIDENTAL CARTESIAN PRODUCT
-- ============================================
-- Missing WHERE clause
SELECT c.name, o.order_id
FROM customers c, orders o;
-- Every customer paired with every order
-- ============================================
-- PART 5: FIXING WITH EXPLICIT JOIN
-- ============================================
SELECT c.name, o.order_id
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id;
-- Only matched rows
-- ============================================
-- PART 6: PRODUCT MATRIX
-- ============================================
SELECT p.name, s.size, c.color
FROM products p
CROSS JOIN sizes s
CROSS JOIN colors c
WHERE p.has_sizes AND p.has_colors;
-- ============================================
-- PART 7: CALENDAR GENERATION
-- ============================================
SELECT d.date, r.region
FROM dates d
CROSS JOIN regions r
WHERE d.date BETWEEN '2026-01-01' AND '2026-12-31'
ORDER BY d.date, r.region;
-- ============================================
-- PART 8: TEST MATRIX
-- ============================================
SELECT b.browser, o.os, v.version
FROM browsers b
CROSS JOIN operating_systems o
CROSS JOIN versions v;
-- ============================================
-- PART 9: SELF-CROSS-JOIN FOR PAIRS
-- ============================================
SELECT a.name AS person1, b.name AS person2
FROM people a
CROSS JOIN people b
WHERE a.id < b.id;
-- Each unordered pair once
-- ============================================
-- PART 10: CROSS JOIN WITH FILTER
-- ============================================
SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c
WHERE c.color <> 'Blue' OR s.size = 'S';
-- Filters the combinations

These ten parts cover creating the tables, a basic cross join, the comma syntax, an accidental Cartesian product, fixing with an explicit join, a product matrix, calendar generation, a test matrix, a self-cross-join for pairs, and a cross join with a filter.


Quick Reference

CROSS JOIN Syntax

FormExample
ExplicitFROM a CROSS JOIN b
CommaFROM a, b
With filterFROM a CROSS JOIN b WHERE ...
SelfFROM t a CROSS JOIN t b

Result Size

Left rowsRight rowsResult rows
3412
1010100
100505,000
1,0001,0001,000,000
10,00010,000100,000,000

Cross Join vs Other Joins

JoinConditionResult
CROSS JOINNoneAll combinations
INNER JOINMatchingMatched rows only
LEFT JOINMatchingAll left + matched right
RIGHT JOINMatchingAll right + matched left
FULL OUTER JOINMatchingAll rows from both

Use Cases

Use caseExample
Product matrixSizes × colors
CalendarDates × regions
Test matrixBrowsers × OSes × versions
Self-pairsPeople × people

Accidental Cartesian Product

CauseFix
Missing WHERE clauseAdd join condition
Partial join conditionConnect all tables
Implicit join syntaxUse explicit JOIN … ON
Forgotten conditionAdd ON to every join

Best Practices

✅ Do This:

-- Use the explicit CROSS JOIN keyword
SELECT s.size, c.color
FROM sizes s CROSS JOIN colors c;

-- Filter after the cross join
SELECT s.size, c.color
FROM sizes s CROSS JOIN colors c
WHERE c.color <> 'Blue';

-- Exclude self-pairs and duplicates
SELECT a.name, b.name
FROM people a CROSS JOIN people b
WHERE a.id < b.id;

-- Cross join small tables
FROM dates d CROSS JOIN regions r

❌ Don’t Do This:

-- Accidental Cartesian product
FROM customers c, orders o  -- ❌ missing WHERE

-- Cross join large tables
FROM products p CROSS JOIN customers c  -- ❌ 1M+ rows

-- Forget the join condition for one table
FROM a JOIN b ON a.id = b.id CROSS JOIN c  -- ⚠️ intentional?

-- Use CROSS JOIN when INNER JOIN is intended
FROM a CROSS JOIN b WHERE a.id = b.id  -- ❌ use JOIN ... ON

Common Pitfalls

PitfallWhy It HappensFix
Result far larger than expectedMissing join conditionAdd the condition
Duplicate rowsAccidental Cartesian productUse explicit JOIN … ON
Slow queryCross join of large tablesReduce input size or filter early
Aggregates wrongCross join inflates row countVerify the join condition
Self-pairs duplicatedNo a.id < b.id filterAdd the filter
Filter applied too lateFilter after the cross joinFilter before if possible

Real-World Examples

1. Sizes and Colors

SELECT s.size, c.color FROM sizes s CROSS JOIN colors c;

2. Calendar Grid

SELECT d.date, r.region FROM dates d CROSS JOIN regions r;

3. Test Matrix

SELECT b.browser, o.os FROM browsers b CROSS JOIN operating_systems o;

4. Unordered Pairs

SELECT a.name, b.name FROM people a CROSS JOIN people b
WHERE a.id < b.id;

5. Product Availability

SELECT p.name, s.size FROM products p CROSS JOIN sizes s
WHERE p.has_sizes;

6. Date Range

SELECT d.date, r.region FROM dates d CROSS JOIN regions r
WHERE d.date BETWEEN '2026-01-01' AND '2026-12-31';

7. Filter Combinations

SELECT s.size, c.color FROM sizes s CROSS JOIN colors c
WHERE c.color <> 'Blue';

8. Accidental Fix

-- Before: FROM customers c, orders o
-- After:
FROM customers c JOIN orders o ON c.customer_id = o.customer_id

9. Explicit Documentation

FROM sizes s CROSS JOIN colors c  -- intentional cross join

10. Self-Cross-Join with Condition

SELECT a.id, b.id FROM items a CROSS JOIN items b
WHERE a.id <> b.id;

Visual

Cartesian Product

┌──────────────────────────────────────────────────────────────┐
│  sizes: S, M, L        colors: Red, Blue                     │
│                                                              │
│  CROSS JOIN:                                                 │
│  ┌──────┬───────┐                                            │
│  │ size │ color │                                            │
│  ├──────┼───────┤                                            │
│  │ S    │ Red   │                                            │
│  │ S    │ Blue  │                                            │
│  │ M    │ Red   │                                            │
│  │ M    │ Blue  │                                            │
│  │ L    │ Red   │                                            │
│  │ L    │ Blue  │                                            │
│  └──────┴───────┘                                            │
│                                                              │
│  3 rows × 2 rows = 6 rows                                    │
└──────────────────────────────────────────────────────────────┘

Accidental vs Intentional

┌──────────────────────────────────────────────────────────────┐
│  ACCIDENTAL:                                                 │
│  FROM customers c, orders o                                  │
│  └── No condition → Cartesian product                        │
│  └── Result: every customer × every order                    │
│                                                              │
│  INTENTIONAL:                                                │
│  FROM sizes s CROSS JOIN colors c                            │
│  └── Explicit keyword → documented intent                    │
│  └── Result: every size × every color                        │
│                                                              │
│  The explicit form makes the intent visible.                 │
└──────────────────────────────────────────────────────────────┘

Result Size Growth

┌──────────────────────────────────────────────────────────────┐
│  Left × Right = Result                                       │
│                                                              │
│  10 × 10     = 100                                           │
│  100 × 100   = 10,000                                        │
│  1,000 × 1,000 = 1,000,000                                   │
│  10,000 × 10,000 = 100,000,000                               │
│                                                              │
│  The result grows multiplicatively.                          │
│  Cross join only when at least one table is small.           │
└──────────────────────────────────────────────────────────────┘

Self-Cross-Join for Pairs

┌──────────────────────────────────────────────────────────────┐
│  people: Alice (1), Bob (2), Carol (3)                       │
│                                                              │
│  CROSS JOIN with a.id < b.id:                                │
│  ┌────────┬────────┐                                         │
│  │ Alice  │ Bob    │                                         │
│  │ Alice  │ Carol  │                                         │
│  │ Bob    │ Carol  │                                         │
│  └────────┴────────┘                                         │
│                                                              │
│  Each unordered pair appears once.                           │
│  Self-pairs and reverse pairs are excluded.                  │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
CROSS JOINCartesian product of two tables
ConditionNone
Result sizeLeft rows × right rows
SyntaxFROM a CROSS JOIN b
Comma syntaxFROM a, b
Accidental causeMissing join condition
FixAdd ON or WHERE
Use casesCombinations, calendars, test matrices
Self-pairsFROM t a CROSS JOIN t b WHERE a.id < b.id
PerformanceExpensive on large tables

Key takeaways:

  • CROSS JOIN produces the Cartesian product. Every row from the left table is paired with every row from the right table. The result size is the product of the two table sizes.
  • The cross join has no ON clause. It makes no comparison between the tables. The result is symmetric, and reversing the table order produces the same combinations in a different order.
  • The most common use is generating combinations. A product matrix, a calendar, a test matrix, or a list of pairs are all cross-join use cases. The cross join expands two small sets into a larger one.
  • The accidental Cartesian product is a common bug. A missing join condition in the implicit join syntax produces the Cartesian product. The explicit JOIN ... ON syntax makes the condition mandatory and prevents the mistake.
  • The result grows multiplicatively. A cross join of two tables with 1,000 rows each produces 1,000,000 rows. The cross join is appropriate only when at least one table is small.
  • Filter conditions reduce the result. A filter applied after the cross join reduces the output. If the filter can be applied before the join, it reduces the input and the cost.
  • Self-cross-joins generate pairs. The a.id < b.id condition excludes self-pairs and reverse pairs, leaving each unordered pair exactly once.

Remember: The CROSS JOIN is the simplest join and the most dangerous one. It produces every combination of two tables, and its result size is the product of the input sizes. When the cross join is intentional, it is the right tool for generating combinations, calendars, test matrices, and pairs. When it is accidental, it is the result of a missing join condition, and it produces a result that is larger than expected and usually wrong. The explicit CROSS JOIN keyword documents the intent, and the explicit JOIN ... ON syntax prevents the mistake. Use the cross join deliberately, on small tables, with filters that reduce the result.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!