DataFrame join takes another DataFrame, a condition or shared key and a join type. A left join keeps the left records; left_semi and left_anti test for the presence or absence of a match without adding right-side fields. A cross join produces every combination, which is useful for a small planning grid and expensive for large inputs.
Before joining orders to customers, rename the customer id to customer_id and select only the customer fields you need. This avoids ambiguous id references afterward. A union stacks rows instead of matching keys. union aligns by position, while unionByName aligns names; neither removes duplicates automatically. The examples use named alignment where source columns have different positions. Distributed Spark may shuffle data to execute a join. This local simulator checks the rows, not a claimed production runtime.
o=spark.table('main.bronze.orders').select('id','customer_id')
c=spark.table('main.bronze.customers').select('id','name').withColumnRenamed('id','customer_id')
display(o.join(c,'customer_id','left').select('id','name'))
Use left join so the order with a missing customer key survives.