A SQL join combines rows from two relational inputs, which can be tables, views, subqueries, or common table expressions. The join condition defines which rows match, commonly by comparing business keys or identifiers. For example, SELECT o.order_id, c.customer_name FROM orders o INNER JOIN customers c ON o.customer_id = c.customer_id enriches orders with customer names. Joining does not require a declared foreign key, but the relationship and data quality must support the intended result.
An INNER JOIN returns matching row combinations only. A LEFT JOIN preserves every row from its left input and supplies NULL values for right-side columns when no match exists. RIGHT JOIN preserves the right input, while FULL OUTER JOIN preserves unmatched rows from both inputs and is useful for reconciliation. A CROSS JOIN produces every possible row combination, which can be intentional for generating combinations but can also create enormous intermediate results. A self-join references the same table more than once, such as matching employees to their managers.
Cardinality is central to correctness. If one customer has five orders, joining that customer to orders produces five rows. If a customer identifier occurs twice in the customer input, each matching order appears twice. Joining orders simultaneously to multiple order items and multiple payments can multiply both sets and inflate aggregate totals. Establish the intended grain of the output, test key uniqueness, and aggregate inputs to compatible grains before joining when necessary. DISTINCT is not a general repair for an incorrect relationship.
NULL values require careful handling because ordinary equality does not match NULL to NULL. After a left join, WHERE c.status = 'active' removes unmatched customers because the predicate is not true for their NULL values. To retain all orders while attaching only active customers, place that condition in the ON clause instead. This distinction is a frequent source of incorrect transformations. Null-safe equality exists in some SQL dialects, but its syntax and intended business meaning must be checked.
Use EXISTS when the requirement is to retain rows that have at least one match without multiplying them by the number of matches. Use NOT EXISTS to identify missing relationships, such as orders with no corresponding customer. NOT IN can produce unexpected results when its subquery contains NULL. Joins combine related rows horizontally; UNION and UNION ALL combine compatible result sets vertically and are not interchangeable with joins.
In cloud pipelines, SQL can join object-storage datasets through query engines, enrich data in a warehouse, or transform distributed datasets using Spark SQL. Python or Scala applications can create Spark DataFrames, register temporary views, and execute equivalent SQL transformations. Normalize key types and representations before joining, and use all required key columns, such as tenant_id together with customer_id, to avoid matching unrelated records.
Physical execution affects cost and runtime without changing the intended relational result. Engines may use hash joins, merge joins, or nested loops. Distributed joins can require expensive network shuffles; broadcasting a sufficiently small input can avoid shuffling the larger input but consumes worker memory. Skewed keys can overload individual workers. Filter unnecessary rows, project only needed columns, maintain useful statistics where supported, and inspect execution plans. Indexes can help relational database joins, while partition pruning and distribution choices matter in analytical systems. Validate row counts, unmatched records, and aggregate totals before publishing the result.