A join is the operation that combines rows from two or more tables based on a relationship between their columns. Because normalization deliberately splits data across many narrow tables, the join is the step that puts the pieces back together: it follows the keys linking the tables to produce a single result containing columns from each.
The PostgreSQL documentation lays out the standard join types. A cross join produces, for every possible combination of rows from the two tables, a row containing all columns of the first followed by all columns of the second, which is the Cartesian product. A qualified join adds a condition: for an inner join, the result has a row for each pair of rows from the two tables that satisfies the join condition, so unmatched rows are dropped.
Outer joins preserve rows that have no match. The documentation explains that a left outer join first performs an inner join, then adds a row, with null values in the columns of the second table, for each row in the first table that found no partner; a right outer join does the converse, and a full outer join keeps the unmatched rows from both sides. This is how a query can list every customer even those with no orders, padding the missing side with nulls.
The join generalizes operations from Codd’s relational model, whose 1970 paper framed data manipulation in terms of operations on relations rather than navigation through pointers. By expressing “which rows go together” as a condition over columns rather than as stored links, joins let the same normalized tables be recombined in whatever shape a particular question requires.