Distinguish lookup attempts from matching pairs
An inner join produces a row for each pair that satisfies its conditions. It does not necessarily produce one row per customer. Consider three customers named A, B, and C, with zero, two, and five matching orders. A contributes no pair, B contributes two, and C contributes five. The result has seven pairs even though the outer input contains three customers.
With customers outside and orders inside, the execution needs an order lookup for each of the three customers. The lookup for A finds nothing. The other two find different numbers of entries. Three lookup attempts therefore produce seven matches. Do not multiply three by seven, because seven is already the total across customers, not a per-customer count. If you use an average, state that its denominator is the three qualifying outer customers.
Without an index, a simple full inner scan can compare each outer customer against every order. If the orders table contains two thousand records, three complete scans inspect six thousand order candidates in this model. Only seven become result pairs. The equality join condition discards the rest. A low result count does not prevent a high number of unsuccessful comparisons.
An indexed inner access can find the relevant customer group instead of repeating all two thousand candidates. It still performs a lookup for the empty group, reads the matching entries for B and C, and may retrieve table records for missing selected values. Index lookup cost is not zero. The benefit is avoiding irrelevant candidates, not avoiding the necessary output pairs.
In the join demonstration, distinguish the temporary-index construction from the later searches. Before playback, predict whether changing the number of outer rows should multiply the build work or the lookup work. The temporary structure is built for the statement and then reused during its inner lookups. More outer rows create more lookup opportunities, which can make the initial build worthwhile. They do not require building a fresh index for every customer in this example.
Now change the requirement to ask only whether each customer has any matching order. That is a different question from returning every customer/order pair. An existence test can stop after finding a qualifying match for a customer, whereas a pair-producing join must enumerate all the matches requested. Do not rewrite a report join as an existence check unless the caller needs only existence. The apparent speedup might otherwise come entirely from discarding valid result rows.
Finally, inspect which columns come from each input. Requesting an order amount absent from the lookup index can require table access even though the join key is indexed. The explanation of repeated work must include both how matches are located and how the output values are obtained. Join performance is not determined by counting indexes in the schema.