Before combining tables, state their grain: what does one row represent? In this build, one left row is an order and one right row is a customer. The intended relationship is many orders to one customer. Enriching an order with a customer name should leave the number of orders and the sum of their amounts unchanged. This is a correctness condition, not just a performance preference.
A repeated right key can multiply evidence in a general join. Two orders for customer A and three customer rows for A produce six joined pairs. Summing order amounts afterward counts each order three times. Selecting an arbitrary right row avoids that multiplication but silently chooses evidence. Validate or resolve the relationship before reporting the result.
Prove uniqueness before lookup
orders = [{'customer_id': 'A', 'cents': 100}, {'customer_id': 'A', 'cents': 200}]
customers = [{'id': 'A', 'name': 'Ada'}, {'id': 'A', 'name': 'Ada'}]
pairs = [(order, customer) for order in orders for customer in customers
if order['customer_id'] == customer['id']]
print(len(pairs), sum(order['cents'] for order, customer in pairs))There are four pairs and 600 cents, even though the two orders total 300 cents. Equal right payloads still violate the unique-key contract. In the build, inspect the complete right input and raise on every repeated id before enriching orders. That includes an unused duplicate key. Upstream deduplication can supply a clean unique right table with its own ledger; the join must not silently do that work.
Keys arrive already normalized here. The join rejects missing, blank, non-string
or boundary-padded ids. Keeping normalization outside the join makes its key
comparison explicit and reusable. Case stays significant. Joining "A" to "a"
without an agreed normalization rule can connect different entities.
Match absence explicitly
A left join emits one fresh shallow dictionary for every order. Add the matched
name, or None when no customer exists. The unmatched ledger records original
1-based order positions. An empty string name is still a match under this join
contract; testing the name's truthiness would confuse a payload value with key
absence. Compare the lookup result with None instead.
The output field itself is another boundary. If an order already contains
customer_name, overwriting it could hide prior enrichment or a source schema
change. The build rejects that collision, even when its value is None. Other
left payload fields are preserved, and the function's own enrichment does not
mutate source dictionaries. Nested payloads remain shallowly shared.
After joining, verify the left count and unmatched count, then check totals at the intended grain. Matching counts do not prove the keys or sources are true, but failing counts disprove the stated relationship. Try identical right customers, two left orders for one customer, an unused duplicate, an unknown customer and an existing output field before implementing the function.