00:00

MySQL CROSS JOIN

Last updated: 3 min read

CROSS JOIN combines every row of one table with every row of another, producing what mathematicians call a Cartesian product. It has no join condition. That sounds odd, but it's exactly what you need to generate combinations: every size in every colour, every store for every month, every seat in every row.

1. How CROSS JOIN Works

CROSS JOIN pairs every row with every rowThree sizes S, M, L and two colours Red and Blue combine into six products: S-Red, S-Blue, M-Red, M-Blue, L-Red, L-Blue.sizessizeSMLcolourscolourRedBlue3 rows × 2 rows = 6 rowsS – RedS – BlueM – RedM – BlueL – RedL – Blue
Figure 1: a CROSS JOIN produces every combination, so the row count is the product of the two tables' sizes.
syntax.sql
SELECT columns FROM table1 CROSS JOIN table2;
-- same as
SELECT columns FROM table1, table2;

2. Generating Combinations

combinations.sql
SELECT s.size, c.colour, CONCAT('TSHIRT-', s.size, '-', UPPER(LEFT(c.colour, 3))) AS sku
FROM sizes s
CROSS JOIN colours c
ORDER BY s.sort_order, c.colour;

Result: 6 rows

sizecoloursku
SBlueTSHIRT-S-BLU
SRedTSHIRT-S-RED
MBlueTSHIRT-M-BLU
MRedTSHIRT-M-RED
LBlueTSHIRT-L-BLU
LRedTSHIRT-L-RED

3. Row Count Grows Fast

query.sql
SELECT (SELECT COUNT(*) FROM sizes) AS sizes, (SELECT COUNT(*) FROM colours) AS colours,
       (SELECT COUNT(*) FROM sizes CROSS JOIN colours) AS combinations;

Result: 1 row

sizescolourscombinations
326
Two tables of 10,000 rows each produce 100 million rows. Always check sizes before cross joining real tables.

4. Report Grids with No Gaps

Sales exist only for some store-month pairs. To show every store for every month (with 0 where nothing was sold), cross join stores with the months, then LEFT JOIN the facts:

grid.sql
SELECT st.store, m.sale_month, COALESCE(SUM(sa.amount), 0) AS revenue
FROM stores st
CROSS JOIN (SELECT '2026-01' AS sale_month UNION ALL SELECT '2026-02' UNION ALL SELECT '2026-03') m
LEFT JOIN sales sa ON sa.store = st.store AND sa.sale_month = m.sale_month
GROUP BY st.store, m.sale_month
ORDER BY st.store, m.sale_month;

Result: 9 rows

storesale_monthrevenue
Delhi2026-01300
Delhi2026-020
Delhi2026-030
Kochi2026-010
Kochi2026-020
Kochi2026-03420
Pune2026-01500
Pune2026-02650
Pune2026-030

5. Generating Number and Date Series

Cross joining a small digits table with itself creates a quick series of numbers, which can become dates:

dates.sql
WITH digits AS (SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
                UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9)
SELECT DATE('2026-03-01') + INTERVAL (tens.d * 10 + ones.d) DAY AS day
FROM digits tens CROSS JOIN digits ones
WHERE tens.d * 10 + ones.d < 7
ORDER BY day;

Result: 7 rows

day
2026-03-01
2026-03-02
2026-03-03
2026-03-04
2026-03-05
2026-03-06
2026-03-07

6. Accidental Cross Joins

Forgetting the join condition turns an intended inner join into a cross join and multiplies rows:

query.sql
SELECT COUNT(*) AS rows_returned FROM stores, sales;   -- forgot WHERE stores.store = sales.store

Result: 1 row

rows_returned
12
Use explicit JOIN … ON. MySQL accepts JOIN without ON as a cross join, but writing CROSS JOIN makes the intent unmistakable.

7. CROSS JOIN vs INNER JOIN

CROSS JOININNER JOIN
ConditionNoneON / USING
Rowsrows(A) × rows(B)Only matching pairs
Use forGenerating combinations and gridsRelating data through keys

In MySQL, CROSS JOIN with an ON clause behaves exactly like an INNER JOIN.

8. Summary

  • CROSS JOIN returns every combination of rows from both tables.
  • Result size = product of the table sizes, so use it on small tables or with care.
  • Great for SKUs, report grids (with LEFT JOIN) and number/date series.
  • A missing join condition creates an accidental cross join.