Basis Lab

Answers · Where the data lives

Why does the order I join tables in matter if joins are commutative?

In shortThe final result is the same, but the rows carried between joins are not, and that is what the work is paid on.

The final table is the same, but the number of rows carried between steps is not. Join order fixes the size of every intermediate result, and each join pays its cost on those intermediate rows. Relational join is commutative: A⋈BA \bowtie B has the same rows as B⋈AB \bowtie A, and it is associative: (A⋈B)⋈C(A \bowtie B)\bowtie C has the same rows as A⋈(B⋈C)A\bowtie(B\bowtie C). That statement is about the relation at the end. Execution is a sequence of binary joins, and the input to the second join is whatever the first join produced.

cost∝∑i=1n−1∣Ri∣\mathrm{cost} \propto \sum_{i=1}^{n-1} |R_i|

The cost is in the middle

Take the same four joins in two orders. Order A joins accounts filtered by region to regions first, then to orders, then to line items, then to products. Order B joins orders to line items first, then to accounts, then to regions, then to products. The final count is identical. The rows entering each step are not.

FIG 1
steporder A rowsorder B rows
join 11,2004,100,000
join 29,4004,100,000
join 331,000390,000
join 431,000390,000
total rows touched72,6008,980,000

Read the first row. Order B starts by joining the two largest tables with no filter between them and carries 4,100,000 rows into the next step. Order A starts with a filtered accounts relation and carries 1,200 rows. By the third step Order B has applied a filter and is down to 390,000 rows, but it has already carried millions through two joins. Order A never exceeds 31,000 rows. A filter that removes ninety nine per cent of a million-row table leaves 10,000 rows: 1,000,000×0.01=10,0001,000,000 \times 0.01 = 10,000. Applied before the join it saves 990,000 rows from every later step. Applied after three joins it saves none of that work.

The natural guess is that if A⋈B=B⋈AA \bowtie B = B \bowtie A, the database can start anywhere and pay the same. That treats a join as one operation over whole tables. It is not. A filter applied after a join has already paid to read, hash, and pass every row that the filter later discards. A hash join also has a build side and a probe side, so even for one pair the two directions need not cost the same. Commutativity preserves the result; it does not preserve the intermediate cardinalities or the work.

Why trying every order fails

Three tables give 3!=3×2×1=63! = 3 \times 2 \times 1 = 6 orders, which is trivial. Six tables give 6!=7206! = 720. Ten give 10!=3,628,80010! = 3,628,800. Twelve give 12!=3,628,800×11×12=3,628,800×132=479,001,60012! = 3,628,800 \times 11 \times 12 = 3,628,800 \times 132 = 479,001,600. Thirteen give 13!=479,001,600×13=6,227,020,80013! = 479,001,600 \times 13 = 6,227,020,800. That is the count before choosing a join method for each step and before allowing joins between two intermediate results. No hardware improvement catches a factorial.

Planners therefore do not enumerate complete orders. They build upwards by subsets. For each single table they keep the cheapest access path. For each pair they keep the cheapest join of two singles. For each triple they keep the cheapest join of a kept pair to a single. The work is proportional to the number of subsets, roughly 2nn2^n n, not to n!n!. At ten tables 210×10=1,024×10=10,2402^{10} \times 10 = 1,024 \times 10 = 10,240, against 3,628,800 orders. At twelve tables 212×12=4,096×12=49,1522^{12} \times 12 = 4,096 \times 12 = 49,152, against 479,001,600. That gap is why the method is usable.

The assumption stops holding when the planner gives up on exhaustive search. Above about a dozen tables the subset method itself becomes too expensive to finish inside the planning budget. The planner switches to a sampling search: start from a plausible order, make small changes, keep improvements, stop when time runs out. The plan is then a good order, not the best order. A slow large query may need to be split into smaller queries with a temporary result, so each piece is small enough to be planned exhaustively. A plan on a many-table query can also change without any statistic changing, because the search is not guaranteed to return the same order twice.

Worth keeping

  • Join commutativity preserves the final relation, not the size of the intermediate relations.
  • A filter applied after a join has already paid to carry the rows it removes.
  • Planners stop enumerating orders once the count of subsets becomes too large and settle for a good plan rather than the best one.

Where this goes next

This question is one step of What the Planner Decides, a course that works through its subject from the beginning. The page that teaches this part of it is Choosing the Order, and it reads the whole way through whether or not you have an account.

What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.

Read that pageThe whole course

Other questions people ask about this

  1. How does the database estimate how many rows a WHERE clause will return?It does not scan the table: it uses stored summary statistics to turn each filter into a fraction of rows and multiplies the fractions.
  2. If SQL is declarative, who decides how the join actually gets run?The database's query planner chooses the join method and order; your SQL only says which rows the answer must contain.
  3. Why does the planner underestimate rows when I filter on two columns that are related?It multiplies the two filter shares as if the columns were unrelated, so when one column already implies the other the estimate comes out far too low.
  4. Will refreshing statistics fix a bad query plan?Only if the summary was taken before the data changed; otherwise refreshing re-measures the same assumptions and the plan stays wrong.
  5. Why is the date off by one day after I save it and read it back?You saved a calendar date as if it were a precise moment, so showing it in another time zone can move it to the day before or after.
  6. Is UTF-8 the same thing as Unicode, or what's the difference?No: Unicode assigns a number to each character, while UTF-8 is a way of writing those numbers as bytes, using one to four bytes per character.

Read alongside