PySpark join(): combine two DataFrames

PySpark join() combines DataFrames on one or more keys. Choose inner, left, right, outer, left_semi or left_anti according to which unmatched rows you need. A broadcast hint can help when one input is small enough for every executor.

Join types

inner keeps matching rows from both sides; left keeps all left rows; outer keeps everything; left_anti keeps left rows with no match. Choose based on whether you must retain unmatched rows.

Broadcast join for performance

Use broadcast(small_df) to suggest broadcasting a small input. Inspect the physical plan and executor memory before using the hint on real data; Spark does not support every strategy for every join type.

Join types and avoiding duplicate key columns

Spark supports inner, left, right, full outer, left semi, left anti and cross joins. Semi and anti joins are underused: left semi keeps rows from the left side that have a match without adding any right-hand columns, and left anti keeps the rows that do not match, which is the cleanest way to express 'find records missing from the other table'. When the join key has the same name on both sides, pass it as a string or list — df1.join(df2, 'customer_id') — and Spark returns one key column. Using a condition such as df1.id == df2.id instead returns both, and you then have to drop one to avoid ambiguity.

Skew, broadcast joins and row count surprises

Repeated keys can legitimately produce several matches. Check the expected relationship before deduplicating: dropping rows can discard valid information. Ordinary equality does not match null keys; use eqNullSafe() explicitly when matching nulls is required. Broadcast and adaptive execution may change the join plan, so inspect explain() instead of assuming every shuffle disappears.

Join choices for orders and customers
JoinRows retainedRight-side columns
innerOrders with a matching customerYes
leftAll orders, including unknown customersYes; null when unmatched
left_semiOrders with a matching customerNo
left_antiOrders without a matching customerNo

Example (PySpark)

from pyspark.sql import SparkSession

spark = SparkSession.builder.getOrCreate()
orders = spark.createDataFrame([(1, 101), (2, 102), (3, 999)], ["order_id", "customer_id"])
customers = spark.createDataFrame([(101, "Alice"), (102, "Bob")], ["customer_id", "name"])

result = orders.join(customers, "customer_id", "left").orderBy("order_id")
for row in result.select("order_id", "name").collect():
    print(row.order_id, row.name)

A left join keeps the third order even though customer 999 is missing. Try inner, left_semi and left_anti next; semi/anti results contain only the original order columns. This example has three rows, so collecting its result is bounded.

Run this example in the free online PySpark compiler

Expected output

1 Alice
2 Bob
3 None

Frequently asked questions

What is the default join type in PySpark?

inner. Rows are kept only when the join key exists in both DataFrames.

How do I do a broadcast join in PySpark?

Use large_df.join(broadcast(small_df), "key") for a supported join. Only broadcast an input that fits comfortably in executor memory; verify the resulting plan.

How do I join on multiple columns in PySpark?

Pass a list of column names, e.g. df1.join(df2, ["col1", "col2"], "inner").

How do I keep unmatched rows in a PySpark join?

Use how="left" to keep all left rows, or how="outer" to keep all rows from both DataFrames; unmatched columns become null.

Practice challenges

Open the free PySpark compiler · Data Engineering challenges · Data Engineering jobs

Further reading

Follow the PySpark course