The Order Logic Did Not Choose

Conversation 2.1 — From Equivalent Plans to Intermediate Work

The order logic did not choose

Ada · 01

Before choosing an algebraic tree, consider a delivery service. These are its recorded facts:

Drivers report to depots, depots stock items, and jobs need items.
The delivery-service facts.

A driver is eligible for a job when the driver's depot stocks the item that the job needs. Is Lin eligible for job1?

Alice

Yes. Lin reports to north, north stocks bolt, and job1 needs bolt. Those three facts connect Lin to job1.

Ada · 02

Is Lin eligible for job3?

Alice

No. job3 needs nut, but Lin's depot stocks bolt.

Ada · 03

Now find every eligible driver–job pair. If you begin with the drivers, how would you organize the reasoning?

Alice

For each driver, find the driver's depot, then the item stocked there, and finally the jobs needing that item. Lin and Moe reach job1 and job2 through north and bolt; Nia and Omar reach job3 and job4 through south and nut.

Ada · 04

How would you organize the reasoning starting from the jobs?

Alice

Yes. For each job, find its required item, then the depot stocking that item, and finally the drivers based at that depot. The reasoning runs in the opposite direction but produces the same eight driver–job pairs.

Ada · 05

Consider a third correct method: first pair every driver with every job, and only afterward check whether the driver's depot stocks the required item. How many pairs exist before that check?

Alice

There are four drivers and four jobs, so it begins with sixteen pairs. Eight survive after the depot and item facts are checked.

Ada · 06

Write the eligibility condition as a conjunctive query.

Alice
eligible(driver, job) :-
    based_at(driver, depot),
    stocks(depot, item),
    needs(job, item).
Ada · 07

Let \(D,S,N\) be the local atom expressions for based_at, stocks, and needs. Their schemas are

D = {driver, depot}
S = {depot, item}
N = {job, item}.

Throughout this conversation, each displayed body-join tree is followed by the query's common head projection. We compare the body-join trees because that projection is identical for both orders.

Translate the driver-first reasoning into relational algebra. How many tuples does each join produce on this instance?

Alice

The expression is

$$(D\bowtie S)\bowtie N. $$

The first join produces four (driver, depot, item) tuples: one stocked item for each driver. Joining needs produces eight complete valuations. Its join-output sizes are 4, 8.

Ada · 08

And the job-first reasoning?

Alice

It is

$$D\bowtie(S\bowtie N). $$

The first join produces four (depot, item, job) tuples: one supplying depot for each job. Joining based_at produces the same eight complete valuations. Its join-output sizes are also 4, 8.

Ada · 09

Before translating the third method, which shared variable could the natural join of D and N require agreement on?

Alice

None. D contains {driver, depot}, while N contains {job, item}. The stocks atom S is the only atom connecting depot to item.

Ada · 10

With no shared variable, what does the natural join of D and N produce on this instance?

Alice

It produces their Cartesian product: every one of the four D tuples combines with every one of the four N tuples, giving sixteen tuples.

Ada · 11

Now write the complete expression.

Alice

It is

$$(D\times N)\bowtie S. $$

The final join removes the unsupported pairs and leaves the same eight complete valuations. Its join-output sizes are 16, 8.

Ada · 12

What information did those extra product tuples lack?

Alice

They had a driver, that driver's depot, a job, and that job's item, but no fact yet established that the depot stocked the item. The product carried all sixteen possibilities until stocks could reject eight of them.

Ada · 13

The output of a nonfinal expression is called an intermediate relation. After the common head projection, all three body-join trees return the same eight answers. Why should we care that this one first produced sixteen tuples?

Alice

Because the final join must receive and check all sixteen candidates against stocks. Eight were constructed and carried forward only to be rejected.

In \((D\bowtie S)\bowtie N\), stocks is checked earlier. Only four supported (driver, depot, item) tuples reach the final join, which extends them into eight complete valuations.

Ada · 14

Compute the proxy for the three plans.

Alice

The two shared-variable plans each give

$$W_0=4+8=12, $$

while the Cartesian-first plan gives

$$W_0=16+8=24. $$
Ada · 15

Return to eligible, but change only its input instance. Suppose D, S, and N each contain three tuples. Both possible first joins share a variable and add an attribute. Must their first outputs therefore have the same cardinality?

Alice

I would expect so. With three tuples on each side, perhaps each first join also produces three tuples.

Ada · 16

Here is the instance:

based_at               stocks              needs
(Lin, north)            (north, bolt)       (job1, bolt)
(Moe, north)            (north, screw)      (job2, bolt)
(Nia, south)            (south, nut)        (job3, nut)

Trace D joined with S one shared depot value at a time.

Alice

For north, two drivers agree with two stocked items, producing four tuples. For south, one driver agrees with one stocked item, producing one more. Thus D joined with S produces five tuples:

(Lin, north, bolt)
(Lin, north, screw)
(Moe, north, bolt)
(Moe, north, screw)
(Nia, south, nut)
Ada · 17

Now trace S joined with N by item.

Alice

bolt produces two tuples, screw produces none, and nut produces one. Thus S joined with N produces three tuples:

(north, bolt, job1)
(north, bolt, job2)
(south, nut, job3)
Ada · 18

After the common head projection, both body-join trees return five driver–job pairs. Compare their logical row volumes.

Alice

The driver-first plan has join-output sizes 5, 5, so \(W_0=10\). The job-first plan has sizes 3, 5, so \(W_0=8\).

All three input relations had cardinality three, but the first joins had cardinalities five and three. Join cardinality depends on how many tuples agree at each shared value, not only on the input relation sizes.

Ada · 19

The ordinary first joins above both added attributes. Now change the query; approved uses only a variable already present in handles:

approved_task(person, task) :-
    member(person, team),
    handles(team, task),
    approved(task).

Its instance is

member                 handles              approved
(Ana, red)              (red, build)         (build)
(Ben, red)              (red, test)          (ship)
(Cy, blue)              (blue, ship)

Which handles tuple has no matching approved fact?

Alice

(red, test). The approved relation contains build and ship, but not test.

Ada · 20

If member joins handles before that tuple is removed, how many tuples reach the final approved join?

Alice

Five:

(Ana, red, build)
(Ana, red, test)
(Ben, red, build)
(Ben, red, test)
(Cy, blue, ship)

The unapproved test tuple has combined with both red-team members.

Ada · 21

Instead join handles with approved first. What reaches the member join?

Alice

Only the two supported tuples:

(red, build)
(blue, ship)
Ada · 22

After the common head projection, both body-join trees return (Ana, build), (Ben, build), and (Cy, ship). Compare their logical row volumes.

Alice

Joining member with handles first gives sizes 5, 3, so \(W_0=8\). Joining handles with approved first gives sizes 2, 3, so \(W_0=5\). The second plan removes (red, test) before it can expand into two tuples.

Recap

Three join-order effects

After the common head projection, each pair of body-join trees below returns the same final relation. The order changes which complete valuations are produced before that projection.

First-step situationComparison on the observed instanceEffect of the order
No shared variableD joined with S: 4, 8, \(W_0=12\); D times N: 16, 8, \(W_0=24\)With no agreement to check, the first step creates a Cartesian intermediate.
Shared variables and both joins add attributesD joined with S: 5, 5, \(W_0=10\); S joined with N: 3, 5, \(W_0=8\)Equal input sizes can still produce different join cardinalities because shared values occur with different frequencies.
Shared variable and the right side adds no attributemember joined with handles: 5, 3, \(W_0=8\); handles filtered by approved: 2, 3, \(W_0=5\)The semijoin prefilter removes (red, test) before it expands across two members.