SQL Joins and Table Relationships

Learn how to combine data from related tables in SQL. Follow primary and foreign keys from table to table, write inner joins, and keep unmatched rows with left, right and full outer joins. Join a table to itself, build every combination with a cross join, and answer many-to-many questions through junction tables and chains of three or more tables. Finally, learn to spot duplicated rows, inflated totals and lost rows, and to check a join result before you trust it. Most examples use one small store database, with small extra tables for hierarchies, many-to-many relationships and combinations, and every example shows its real result rows.

BeginnerLesson 5 of 6 in SQL Basics Course

What you will learn

  1. Read how tables are linked by keys and explain what a join does to their rows before you write one.
    • Find the links between tables in a schema
      Use primary and foreign keys to see how tables relate, name each relationship as one-to-one, one-to-many or many-to-many, and plan which columns a join should match.
    • Explain what a join does to rows
      Describe a join as pairing rows that meet a condition, predict the width and length of the result, and use table aliases and qualified column names.
  2. Write inner joins that return only rows with a match in both tables, using ON, USING and range conditions, and filter and sort the combined rows.
    • Write an inner join with ON
      Join two tables on their key columns with JOIN ... ON, predict exactly which rows return, and add filters, sorting and clear column names.
    • Use USING, NATURAL JOIN, comma joins and range conditions
      Recognize other ways to write an inner join, explain the risk of each, and join rows on a range instead of an equal key.
  3. Use left, right and full outer joins to keep rows that have no match, find missing data, and place each condition where it filters what you intend.
    • Keep every row of one table with LEFT JOIN
      Write a left outer join, predict its NULL-filled rows, and use it to find rows that have no match in another table.
    • Use RIGHT JOIN and FULL OUTER JOIN
      Keep unmatched rows from the right table or from both tables, rewrite a right join as a left join, and use a full join to compare two lists.
    • Place conditions in ON or WHERE correctly
      Explain why a condition behaves differently in ON and in WHERE of an outer join, and choose the right place for each condition.
  4. Use self joins to relate rows within one table, use cross joins to build every combination, and recognize a cross join that happened by accident.
    • Relate rows of the same table with a self join
      Join a table to itself with two aliases to follow references inside the table, such as employees and their managers, and to pair rows of the same table.
    • Build every combination with a cross join
      Use CROSS JOIN to pair every row of one table with every row of another, predict the size of the result, and spot accidental Cartesian products.
  5. Use junction tables to answer many-to-many questions, and join three or more tables in one query while keeping the right rows at every step.
    • Query many-to-many relationships through a junction table
      Explain how a junction table links two tables, and join through it to answer questions in both directions, including rows with no partner.
    • Join three or more tables in one query
      Plan a path of keys across several tables, write one join per link, and read what each row of a multi-table result stands for.
    • Keep unmatched rows across a chain of joins
      Combine outer and inner joins in a chain without losing rows you meant to keep, and check each step of the chain.
  6. Recognize when a join multiplies or loses rows, fix inflated counts and totals, and check a join result before you trust it.
    • Fix duplicated rows and inflated totals
      Explain why joins along one-to-many links repeat rows, and count and add up values at the right level so totals stay correct.
    • Check a join result before trusting it
      Predict how many rows a join should return, recognize the symptoms of common join mistakes, and work through a short checklist for every join query.

SQL Joins study guide

6 chapters

1

Keys and combined rows

A join starts with a relationship between rows. Customer details and order details live in separate tables, and keys tell you how to bring them together. Read the small store schema, distinguish one-to-one, one-to-many, and many-to-many links, and trace a path for a report. Then follow how matching pairs become result rows, why names can repeat, and how aliases make each selected column's origin clear.

1.1 Keys and paths between tables

A customer's address belongs with the customer. An order's date belongs with the order. Keeping these facts in separate tables reduces repetition, but a report often needs facts from both. A join combines their rows inside a query.

Read the keys

A primary key identifies one row. It may be one column or several columns together. A foreign key refers to a primary key or another unique key, either in another table or in the same table. In the store database, orders.customer_id points to customers.customer_id.

customer_idnamecity
1AnaDenver
2BenAustin
3ChloeDenver
4DevBoston
order_idcustomer_idorder_datetotal
10112026-03-0222.00
10212026-03-1525.50
10322026-03-2060.00
104NULL2026-03-2115.00

Orders 101 and 102 both refer to customer 1, Ana. Order 103 refers to Ben. Order 104 is a guest checkout: its customer key is NULL, so it refers to no customer. The order number identifies the order; it does not identify the customer. Joining order_id to customer_id would compare unrelated identifiers.

Name the relationship
RelationshipWhat it meansWhere the link lives
One-to-oneEach row has at most one partner on the other side.A foreign key is also unique, as with one badge per employee.
One-to-manyOne parent can have several children; each child has at most one parent.The foreign key is on the many side, as in orders.
Many-to-manyRows on either side can have several partners.A junction table holds a foreign key to each side.

The customer–order relationship is one-to-many. A customer can have no orders, one order, or several. That does not mean the customer table stores an order ID for every purchase. Each order stores its own customer reference.

For students and courses, neither side can hold just one reference to describe all the links. An enrollments row records one student–course pair. Several enrollment rows can refer to the same student, and several can refer to the same course.

Trace a route before writing SQL

To list products bought by each customer, follow customers → orders → order_items → products. The links use customer_id, then order_id, then product_id. Each link needs its own condition. An order line supplies the missing connection between an order and a product.

Read the schema's meaning, not just the spelling of columns. employees.manager_id can refer to employees.emp_id, even though the names differ. Conversely, customers.name and products.name name different kinds of things and should not be matched just because both are called name.

A declared foreign key is a useful guide, but SQL can also join tables without a declared constraint. A price can match a price band, and two employees can match because they share a manager. The condition must express the relationship needed for the question.

Key Takeaways

  • A primary key identifies a row; a foreign key refers to a primary or unique key.
  • The foreign key sits on the many side of a one-to-many relationship.
  • A junction table records the pairs in a many-to-many relationship.
  • Follow the schema links and give every link a meaningful condition.

1.2 How matching rows become a result

A join builds new rows by combining input rows. Start with the matching rule, then ask how many partners each row has. The answer controls both which rows appear and how often their values repeat.

Think in pairs

For an inner join, imagine every customer beside every order. Keep a pair only when o.customer_id = c.customer_id is true. Order 101 pairs with Ana; it does not pair with Ben, Chloe, or Dev. Order 102 also pairs with Ana. One customer can therefore contribute several result rows.

The condition can be true, false, or unknown. An equality involving NULL is unknown, so guest order 104 finds no customer match. An outer join also preserves unmatched rows from its chosen side and fills the missing partner's columns with NULL.

This pair-by-pair picture explains joins better than overlapping circles. A join does not merely collect shared items: it constructs combined rows, and repeated keys can create more output rows than either input contains. If two rows on one side and three on the other share a key, that key gives six matching pairs.

Choose columns independently of matches

The store's customer table has three columns and its order table has four. With JOIN ... ON, SELECT * returns all seven, including both copies of customer_id. You can instead select only the fields the report needs:

SELECT c.name, o.order_id, o.total
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id
ORDER BY o.order_id;

The result is (Ana, 101, 22.00), (Ana, 102, 25.50), and (Ben, 103, 60.00). The key columns do their matching work without having to appear in the selected output. Selecting fewer columns changes the width of the result, not which input pairs match.

Give tables short, clear names

In FROM customers c, c is a table alias. Refer to that table's columns as c.name and c.customer_id. The expression c.name is a qualified column name: the part before the period says which table role supplies the value.

Both joined tables contain customer_id. Writing only SELECT customer_id with the ON form leaves the database unable to choose between them and causes an ambiguous-column error. Write c.customer_id or o.customer_id according to the value you need. Qualifying other selected columns also makes their origin easy to see.

AS before a table alias is optional in PostgreSQL, SQLite, MySQL, and SQL Server. Oracle does not accept it, so FROM customers c is a useful shared form. After giving a table an alias, use that alias consistently.

Read the query's logical order

FROM and its joins build the combined rows. WHERE filters those rows. SELECT chooses the output columns, and ORDER BY sorts the result. A filter can use a joined column even when that column is not selected. For example, WHERE c.city = 'Denver' can filter a report that displays only names and order totals.

Without ORDER BY, the database promises no particular row order. A query that displays Ana first today may display another row first later; sorting is a separate instruction from matching.

Key Takeaways

  • An inner join keeps each pair whose condition is true.
  • Repeated key values can produce several rows for one input row.
  • Aliases and qualified names identify which table supplies a column.
  • Joins build rows before WHERE filters them and SELECT chooses columns.
  • Use ORDER BY when row order matters.
2

Writing inner joins

Inner joins answer questions that require matching facts on both sides, such as a customer's name beside an order. Build an explicit condition, inspect its real result rows, and add filters and sorting. Then read the shorthand and older forms you may meet in existing queries. Range matches introduce a different kind of condition, where overlapping bands or gaps can change how many rows each value produces.

2.1 Matching rows with JOIN and ON

Use an inner join when a result row needs a match in both tables. JOIN and INNER JOIN mean the same thing. Neither one preserves a row that finds no partner.

Build the query in three parts

First name the tables and their roles. Then state the relationship in ON. Finally choose the columns to display.

SELECT c.name, o.order_id, o.total
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id
ORDER BY o.order_id;

Read the condition as “the order's customer reference equals this customer's identifier.” For order 101, the reference is 1, so it pairs with Ana. Order 102 also has reference 1, and order 103 has reference 2. The complete result is:

nameorder_idtotal
Ana10122.00
Ana10225.50
Ben10360.00

Ana appears twice because she has two matching orders. Chloe and Dev have none, so they do not appear. Guest order 104 has a NULL customer reference, which satisfies no equality with a customer key.

Filter and sort the combined rows

To show only Denver customers, place WHERE c.city = 'Denver' after the join and before ORDER BY. The result becomes (Ana, 101, 22.00) and (Ana, 102, 25.50). Chloe lives in Denver too, but the inner join never produced a row for her.

For an inner join, putting this filter in ON gives the same pairs. A readable convention is to keep table-matching conditions in ON and report filters in WHERE. This equivalence does not extend to outer joins.

You can sort by a joined column that is absent from the selected output. You can also omit the join keys from the output: the query above matches on customer_id while displaying a name, an order number, and a total. A column's role in matching is separate from its role in the report.

Match every part of a key

Some records are identified by a pair of columns. A shipment line that refers to an order line may need both the order and product identifiers:

SELECT s.order_id, s.product_id, oi.quantity
FROM shipment_lines s
JOIN order_items oi
  ON s.order_id = oi.order_id
 AND s.product_id = oi.product_id;

Both comparisons must be true for a match. Matching only order_id would attach each shipment line to every product line in that order. The query could run successfully while describing the wrong product quantities.

Change the order without changing the meaning

Writing FROM orders o JOIN customers c ON c.customer_id = o.customer_id gives the same customer–order pairs. Reversing the two sides of = also preserves an equality. SELECT * may arrange columns differently after a table swap, and row order remains unspecified unless sorted.

A much more serious change is ON o.order_id = c.customer_id. Both columns contain numbers, so this is legal SQL, but they identify different things. In the store data it returns zero rows: order IDs 101–104 never equal customer IDs 1–4. Check the schema relationship even when the database reports no syntax error.

Key Takeaways

  • JOIN means INNER JOIN: unmatched rows on either side are excluded.
  • Put the relationship in ON, then add report filters and sorting.
  • Match all parts of a composite key with AND.
  • Legal SQL can still match the wrong columns.

2.2 Other ways to state a match

ON makes a condition explicit, but existing SQL may use shorter or older forms. Read each form by asking which pairs it keeps. Some forms also change how shared columns appear in the output.

USING names the shared key

When the key columns have the same name, this query uses a shorthand equality:

SELECT c.name, o.order_id
FROM customers c
JOIN orders o USING (customer_id)
ORDER BY o.order_id;

It returns (Ana, 101), (Ana, 102), and (Ben, 103). USING (customer_id) means to compare the two customer_id columns. With SELECT *, it returns six columns, showing the shared key once; JOIN ... ON includes both input key columns.

For a composite key, list both names: USING (order_id, product_id). Each listed column must exist under that name in both inputs.

PostgreSQL, SQLite, MySQL, and Oracle support USING; SQL Server does not. In Oracle, a column named in USING must be unqualified when referenced, so write SELECT customer_id, not SELECT c.customer_id. Other columns can still use their table aliases.

NATURAL JOIN chooses columns for you

A NATURAL JOIN compares every column name shared by the inputs, combines those equality tests with AND, and shows the shared columns once. It is supported by PostgreSQL, SQLite, MySQL, and Oracle, but not SQL Server.

That automatic choice can silently change a query. Customers and orders currently share only customer_id. Adding a city column to orders makes a natural join require both customer ID and city to match. A delivery city different from the customer's city can then remove a formerly matching order.

Customers and products both have name, but one names a person and the other names a product. Their natural join would compare those unrelated names and returns no rows in the store data. With no shared column names, a natural join instead returns every combination. Use ON or an intentional USING list to keep the matching rule visible.

Read older comma joins
SELECT c.name, o.order_id
FROM customers c, orders o
WHERE o.customer_id = c.customer_id;

This returns the same three pairs as the inner join. The condition now lives in WHERE. Forget it and all four customers pair with all four orders, producing 16 rows. JOIN ... ON keeps each table beside its own condition, which makes longer queries easier to check.

Match a value to a range

An equijoin uses equality. A non-equijoin uses another comparison, such as a range test:

SELECT p.name, b.band
FROM products p
JOIN price_bands b
  ON p.price BETWEEN b.min_price AND b.max_price;

The bands are Budget 0.00–4.99, Standard 5.00–19.99, and Premium 20.00–999.99. Mug at 8.00 and Stapler at 12.00 match Standard; Pen at 1.50 and Notebook at 4.25 match Budget. Premium has no matching product and contributes no row.

BETWEEN includes both endpoints. A new Gift band from 8.00 to 15.00 would give Mug and Stapler a second match each. The gap between 4.99 and 5.00 would leave a price of 4.995 unmatched. Ranges determine the result just as keys do: a value can have zero, one, or several matches.

Key Takeaways

  • USING compares the listed same-named columns and shows each shared key once.
  • NATURAL JOIN depends on every shared column name, so schema changes can alter its result.
  • A comma join needs its matching condition in WHERE.
  • A non-equijoin can match ranges; overlaps create extra matches and gaps leave values unmatched.
  • SQL Server uses ON, with no USING or NATURAL JOIN syntax.
3

Preserving missing matches

Reports often need missing relationships to stay visible: customers with no orders, unsold products, or records absent from one of two lists. Outer joins preserve those rows and mark the missing partner with NULLs. Choose which side must survive, then follow how conditions change the result when they run during matching or afterward. The placement of one date test can decide whether an entire customer disappears.

3.1 All rows from the left, including missing matches

A report asking for “all customers, including customers without orders” needs a left outer join. LEFT JOIN keeps every left row. It attaches each matching right row, or supplies one row of NULL values when there is no match. The word OUTER is optional.

Follow the kept table
SELECT c.name, o.order_id, o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
ORDER BY c.customer_id, o.order_id;
nameorder_idtotal
Ana10122.00
Ana10225.50
Ben10360.00
ChloeNULLNULL
DevNULLNULL

Ana still supplies two rows. A left join does not reduce a customer's orders to one. Chloe and Dev each supply exactly one row because neither has a match. There are five rows overall: three matching pairs and two unmatched customers.

The join returns at least as many rows as its left input before later filters. For each left row, count its matches; if it has none, count one NULL-filled row. This explains why four customers can produce five rows.

Guest order 104 is absent. Keeping every customer does not keep unmatched orders too. To keep every order, start with orders o LEFT JOIN customers c ON c.customer_id = o.customer_id. Selecting o.order_id, c.name then gives (101, Ana), (102, Ana), (103, Ben), and (104, NULL). Chloe and Dev no longer appear.

Find rows with no partner

A left anti-join keeps only rows that failed to match:

SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL;

The left join first makes all five rows. WHERE then keeps Chloe and Dev, whose order keys are NULL. Their missing partner is exactly what the query is looking for.

Choose a right-side column that cannot be NULL in a real match. An order's primary key is a good test. Testing an optional coupon_code would also keep real matching orders with no coupon, so it would not mean “no order.” Use IS NULL: = NULL evaluates to unknown and keeps no rows in WHERE.

The same pattern finds unsold products:

SELECT p.name
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.product_id
WHERE oi.order_id IS NULL;

It returns (Stapler), the only product with no order line. Here a real matching line necessarily has an order identifier, so the test detects the missing line.

Interpret a missing value before replacing it

A NULL-filled outer-join row means no partner matched. A stored NULL in an actual order can instead mean that a field was not supplied. These are different situations even though both display as NULL.

COALESCE(o.total, 0) chooses the first non-NULL argument. It would display 0 for Chloe and Dev. That can be helpful for a spending report, but a zero in a column labeled “order total” can suggest a real zero-value order. Keep the label and the meaning of the report aligned.

Key Takeaways

  • A left join preserves the left rows and supplies NULLs for missing right partners.
  • Count every matching pair, plus one row for each unmatched left row.
  • For an anti-join, use WHERE right_key IS NULL on a key that cannot be NULL in a match.
  • An unmatched right row is not preserved by a left join.
  • COALESCE can change the display of missing values; choose a label that keeps their meaning clear.

3.2 Keeping the right side or both sides

Sometimes the required rows sit on the right of a join. Sometimes both inputs must survive so that missing records can be compared. Right and full outer joins make these choices explicit.

Rewrite a right join as a left join
SELECT c.name, o.order_id
FROM customers c
RIGHT JOIN orders o ON o.customer_id = c.customer_id;

This keeps every order: (Ana, 101), (Ana, 102), (Ben, 103), and (NULL, 104). The right table is orders, so the guest order survives. Chloe and Dev are unmatched left rows and do not survive.

Swapping the inputs gives the equivalent orders o LEFT JOIN customers c ON o.customer_id = c.customer_id. Keeping the same explicit select list gives the same values. With SELECT *, the default column order can change. Using left joins throughout a report often makes the must-keep side easier to follow.

Keep missing rows from both inputs
SELECT c.name, o.order_id
FROM customers c
FULL OUTER JOIN orders o ON o.customer_id = c.customer_id;

The result contains (Ana, 101), (Ana, 102), (Ben, 103), (Chloe, NULL), (Dev, NULL), and (NULL, 104). These are three matching pairs, two unmatched customers, and one unmatched order: six rows. The customer columns are NULL for guest order 104; the order columns are NULL for Chloe and Dev.

JoinUnmatched rows preserved
LEFT JOINLeft input only
RIGHT JOINRight input only
FULL OUTER JOINBoth inputs

A full join is useful for reconciliation, such as checking a warehouse stock list against a physical count. Matching keys appear together; keys found on only one side remain visible. In the store query, adding WHERE c.customer_id IS NULL OR o.order_id IS NULL keeps just Chloe, Dev, and order 104.

When reconciling two lists of the same kind of key, either input key can be NULL in the output. COALESCE(w.sku, k.sku) AS sku selects the available SKU from a warehouse alias w or count-list alias k. A combined key column lets the report identify records missing from either list.

Check syntax support

PostgreSQL, SQL Server, and Oracle support right and full outer joins. SQLite supports both beginning with version 3.39.0, released in 2022. Older SQLite versions reject them. MySQL supports right joins but has no FULL OUTER JOIN; a workaround combines left- and right-join results with UNION.

Older Oracle SQL may instead mark the optional side with (+) in WHERE. For example, WHERE c.customer_id = o.customer_id(+) preserves customers and fills missing order columns with NULL, like a left join from customers. The marker belongs to the side allowed to be missing. Use the named LEFT, RIGHT, and FULL join forms when writing new queries.

Key Takeaways

  • A right join becomes a left join when the tables swap places.
  • Full-join rows are matching pairs plus unmatched rows from both sides.
  • A full join can expose mismatches during reconciliation.
  • SQLite needs version 3.39.0 or later for right and full joins; MySQL has no full-join keyword.

3.3 Deciding whether a condition belongs in ON or WHERE

An outer join separates two decisions: which pairs count as matches, and which completed result rows the report keeps. ON makes the first decision. WHERE makes the second, after unmatched rows have received NULLs.

Keep everyone, but limit their matching orders

Suppose a report needs all customers and only their orders from March 10 onward:

SELECT c.name, o.order_id
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.customer_id
 AND o.order_date >= '2026-03-10';

The date test is part of matching. Ana's order 101 is too early, but order 102 qualifies. Ben's order 103 qualifies. Chloe and Dev have no qualifying partner, so each receives a NULL-filled row. The result is (Ana, 102), (Ben, 103), (Chloe, NULL), and (Dev, NULL).

Filter the rows after the join

Move the same date test to WHERE:

SELECT c.name, o.order_id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_date >= '2026-03-10';

Now the join first creates five rows, including the NULL-filled rows for Chloe and Dev. The date comparison is unknown for those NULL values, so WHERE removes them. It also removes Ana's early order. Only (Ana, 102) and (Ben, 103) remain.

The WHERE version produces the same matching rows as an inner join with that date filter.

Filter the kept table on purpose

A left-side condition in ON cannot remove left rows either. Consider ON o.customer_id = c.customer_id AND c.city = 'Denver'. Ana can match both orders, Chloe has none, and Ben and Dev fail the city part of every possible match. All four customers remain, with NULL order columns for Ben, Chloe, and Dev.

To request only Denver customers, put WHERE c.city = 'Denver' after the ordinary key condition. The result is (Ana, 101), (Ana, 102), and (Chloe, NULL). Chloe survives because the filter tests her own city, not a missing order field.

What the condition should doTypical place in a left join
Limit eligible right-side matches while keeping every left rowON
Limit which left rows belong in the reportWHERE
Find rows whose right partner is missingWHERE with right_key IS NULL

The anti-join test belongs after the join because it examines the NULL-filled result. Inner joins have no preserved unmatched rows, so moving a filter between ON and WHERE does not change their matching pairs. When reviewing an outer join, read each optional-side WHERE test and check what it does to a NULL-filled row.

Key Takeaways

  • ON decides matches; WHERE filters completed result rows.
  • Put a right-side restriction in ON to preserve left rows without qualifying matches.
  • Put left-side report restrictions in WHERE.
  • A right-side comparison in WHERE removes NULL-filled rows when it is unknown.
  • The anti-join IS NULL test deliberately runs after the join.
4

Self joins and cross joins

The same join machinery can connect two roles in one table or build every combination of two tables. Employee–manager queries need aliases that keep the roles clear; colleague pairs also need a rule for self-pairs and reversed duplicates. Cross joins deliberately generate all possibilities, such as sizes and colors. Predicting their size helps you recognize when that multiplication was intended and when a condition is missing.

4.1 Two roles for one table

An employee's manager is also an employee. A self join reads the same table under different aliases so one row can play the employee and another can play the manager. It uses ordinary join syntax; there is no SELF JOIN keyword.

Name the roles before the columns

The employee data is:

emp_idnamemanager_id
1GraceNULL
2Hassan1
3Iris1
4Jamal2
SELECT e.name AS employee, m.name AS manager
FROM employees e
JOIN employees m ON e.manager_id = m.emp_id;

e supplies the employee row. Its manager_id points to the manager's emp_id in role m. Hassan points to 1, so he pairs with Grace; Iris also pairs with Grace. Jamal points to 2 and pairs with Hassan. The result is (Hassan, Grace), (Iris, Grace), and (Jamal, Hassan).

Grace has no manager. Her manager_id is NULL, so the inner join excludes her. Replacing JOIN with LEFT JOIN adds (Grace, NULL), producing four rows. Distinct aliases let the database tell the roles apart even though both inputs come from the same stored table.

Ask the question in the other direction

To find people who manage nobody, start with the manager role and look for a missing employee:

SELECT m.name
FROM employees m
LEFT JOIN employees e ON e.manager_id = m.emp_id
WHERE e.emp_id IS NULL;

This returns (Iris) and (Jamal). Grace matches Hassan and Iris; Hassan matches Jamal. Iris and Jamal have no direct reports. Testing the employee key after the join finds their missing partner.

A third alias follows one more level. employees e JOIN employees m ON e.manager_id = m.emp_id JOIN employees g ON m.manager_id = g.emp_id, selecting their names, returns (Jamal, Hassan, Grace). Each additional role follows one more link. A fixed query reaches a fixed number of levels; a hierarchy of arbitrary depth needs a recursive query.

Pair colleagues once

The same table can also supply pairs of coworkers:

SELECT a.name, b.name
FROM employees a
JOIN employees b
  ON a.manager_id = b.manager_id
 AND a.emp_id < b.emp_id;

Hassan and Iris share manager 1. The less-than test excludes a person paired with themselves and keeps only one ordering of each pair. The result is (Hassan, Iris).

With only the manager equality, the result has five rows: Hassan–Hassan, Hassan–Iris, Iris–Hassan, Iris–Iris, and Jamal–Jamal. Grace's NULL manager key matches nothing, including another NULL. Replacing < with <> excludes self-pairs but keeps both (Hassan, Iris) and (Iris, Hassan). Choose < when the question needs each unordered pair once.

Key Takeaways

  • Use different aliases for the different roles in a self join.
  • Match an employee's manager_id to the manager role's emp_id.
  • A left self join can preserve the top of a hierarchy.
  • Each extra self join follows another fixed level.
  • For same-group pairs, < removes self-pairs and reversed duplicates; <> removes only self-pairs.

4.2 Every possible combination

A cross join builds every pair of rows, with no matching condition. This is useful when the question asks for possibilities, such as every size–color combination a shop could stock.

Build the combination list

sizes contains S, M, and L. colors contains Red and Blue.

SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;

The six pairs are (S, Red), (S, Blue), (M, Red), (M, Blue), (L, Red), and (L, Blue). Each size appears beside both colors. These are the result rows; their display order is not guaranteed without ORDER BY.

For two inputs, multiply their row counts. Three sizes and two colors give six rows. With a third table containing four options, the result has 24 rows. If any input is empty, no complete combination can be made and the result has zero rows.

Start with a complete reporting grid

Sales records show only combinations that occurred. To show every store in every month, first form a grid with stores CROSS JOIN months. Then use a left join to attach matching sales records:

SELECT s.store_id, m.month, x.total
FROM stores s
CROSS JOIN months m
LEFT JOIN sales x
  ON x.store_id = s.store_id
 AND x.month = m.month;

The grid keeps a store–month pair even when there is no sale, with NULL in x.total. Both key parts matter: matching only the store would attach that store's sales from other months too. The cross join defines which combinations the report must show, and the left join supplies available facts about them.

Spot an unintended cross join

FROM customers, orders without a joining condition makes 16 pairs from the four customers and four orders. Ana pairs with her own orders, Ben's order, and the guest order. Every customer does the same. That is useful only if every customer–order combination is actually wanted.

A large row count is a clue, but inspect the pairings as well. With 1,000 customers and 1,000 products, an intentional cross join makes 1,000,000 rows. A join with a wrong but selective condition can also return a smaller, incorrect result. Compare the count and the meaning of each pair with the question.

Recognize equivalent forms

CROSS JOIN, a comma join without a matching condition, and an inner join with ON 1 = 1 all keep every pair. SQLite and MySQL also accept a plain JOIN with no ON and treat it as a cross join; PostgreSQL requires ON or USING for a plain join. Write CROSS JOIN when every combination is intentional.

DISTINCT removes identical selected rows, not incorrect relationships. If an accidental cross join pairs Ana with Ben's order, those values remain a distinct row. Repair the missing condition instead of hiding some repeated output.

Key Takeaways

  • A cross join returns every combination; multiply the input row counts.
  • An empty input makes the cross product empty.
  • A cross join can create a complete grid for a later left join.
  • A missing matching condition can create an accidental cross join.
  • DISTINCT does not remove wrong pairings.
5

Junctions and longer paths

Some reports need a route through several tables before the requested facts can meet. Enrollment rows link students to courses, and order lines link orders to products. Follow one key at a time, keep track of what each output row represents, and choose which links are required. A left join early in a chain can lose its unmatched rows at a later step, so the whole route must preserve the report's intended records.

A student can take several courses, and a course can have several students. A junction table records these many-to-many links as individual pairs. It is also called a bridge, link, or associative table.

Read one link per row

The example data contains students 1 Mia, 2 Noah, and 3 Omar; courses 10 Algebra, 20 Biology, and 30 Chemistry; and enrollment pairs (1, 10), (1, 20), and (2, 10).

student_idcourse_id
110
120
210

Student 1 appears twice because Mia takes two courses. Course 10 appears twice because Algebra has two students. Neither column must be unique on its own. The pair can form a composite primary key, PRIMARY KEY (student_id, course_id), preventing the same student from being enrolled in the same course twice.

A link can have its own facts. enrolled_on belongs to the enrollment because it describes when a particular student joined a particular course. Likewise, order_items.quantity describes how much of a particular product belongs to a particular order.

Cross the junction with two joins
SELECT s.name, c.title
FROM students s
JOIN enrollments e ON e.student_id = s.student_id
JOIN courses c ON c.course_id = e.course_id;

The first condition attaches each student's enrollment rows. The second replaces each enrollment's course reference with facts from the course table. The complete result is (Mia, Algebra), (Mia, Biology), and (Noah, Algebra). Each row stands for one enrollment.

There is no student-to-course key in either endpoint table. Matching s.student_id = c.course_id would compare unrelated identifiers and skip the enrollment facts. The junction table supplies the relationship.

The same path works in either direction. Add WHERE c.title = 'Algebra' to obtain (Mia, Algebra) and (Noah, Algebra). Add WHERE s.name = 'Mia' instead to obtain (Mia, Algebra) and (Mia, Biology). The links stay the same; the filter selects which links answer the question.

Include people or courses without links

Start from the side that must survive. students s LEFT JOIN enrollments e ON e.student_id = s.student_id keeps Omar with NULL enrollment columns. To show course titles while keeping him, the later join from enrollments to courses must also preserve the missing enrollment.

To find courses nobody takes, use the course side and an anti-join:

SELECT c.title
FROM courses c
LEFT JOIN enrollments e ON e.course_id = c.course_id
WHERE e.student_id IS NULL;

This returns (Chemistry). In a real matching enrollment, student_id is part of the primary key and cannot be NULL, so the test identifies the missing link.

Store one relationship per junction row. A text list such as "10,20" in one student field cannot be matched by equality to individual course IDs 10 and 20. Separate pairs let each join condition compare one reference with one key.

Key Takeaways

  • A junction row records one pair and can hold facts about that relationship.
  • The two foreign keys can form a composite key that prevents duplicate pairs.
  • Two joins connect the endpoint tables through the junction.
  • Start from the endpoint that must survive when finding missing relationships.

5.2 A path across four tables

A longer join follows the same rule as a two-table join: attach the next table using a meaningful condition. Before writing the query, trace the keys and decide what one result row should represent.

Add the detailed store data
product_idnameprice
1Mug8.00
2Pen1.50
3Notebook4.25
4Stapler12.00

The order lines (order_id, product_id, quantity) are (101, 1, 2), (101, 2, 4), (102, 3, 6), (103, 1, 6), (103, 2, 8), and (104, 2, 10). Each line links an order to a product and records a quantity.

For the question “What did each customer buy?”, follow customers → orders → order_items → products. The keys along the path are customer ID, order ID, and product ID. Four tables along this chain require three links.

Attach one new table at a time
SELECT c.name, o.order_id, p.name AS product,
       oi.quantity, oi.quantity * p.price AS line_total
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.product_id = oi.product_id
ORDER BY o.order_id, p.product_id;

Each ON refers to the newly introduced table and one already available. Products attach through order items, not directly through customers. The result is:

nameorder_idproductquantityline_total
Ana101Mug216.00
Ana101Pen46.00
Ana102Notebook625.50
Ben103Mug648.00
Ben103Pen812.00

The first join excludes guest order 104 because its customer reference is NULL. The remaining three orders contain five lines, so the final result has five rows. For order 101, two mugs cost 16.00 and four pens cost 6.00, matching its order total of 22.00.

Know what a row counts

One result row represents an order line. Ana and order 101 appear twice because that order has two products. Repeated customer names are therefore expected. Counting these rows counts lines, not distinct customers or orders.

A filter may refer to any joined table. Adding WHERE p.name = 'Pen' before ORDER BY keeps (Ana, 101, Pen, 4, 6.00) and (Ben, 103, Pen, 8, 12.00). No product filter can restore the guest order already excluded at the customer link.

Keep each link visible

For a chain of inner joins, the written table order can change without changing the matching combinations, provided each condition refers only to tables already introduced. Starting with the subject of the report and following its keys is usually easiest to read.

A missing condition can multiply an intermediate result in SQLite or MySQL, which accept a plain JOIN without ON. PostgreSQL and SQL Server reject that syntax. A missing condition in a comma-style query can also create every combination at that step.

Sometimes one table serves two roles. A flight report can join airports as dep on dep.code = f.from_code and again as arr on arr.code = f.to_code. Distinct aliases give the departure and arrival facts separate places in the result.

Key Takeaways

  • Add one join condition for each link in the table path.
  • Each ON may use the new table and tables already introduced.
  • Identify what one output row represents before counting or adding values.
  • A table can appear under different aliases when the report needs different roles.

5.3 Keeping optional rows through a chain

A left join preserves rows at one step. Later joins and filters still act on that step's result. To keep an unmatched parent through a longer path, follow what happens to its NULL-filled columns at every stage.

See where a kept row disappears
SELECT c.name, o.order_id, oi.product_id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
JOIN order_items oi ON oi.order_id = o.order_id;

The first step makes five rows: Ana's two orders, Ben's order, and one NULL-filled row each for Chloe and Dev. At the next step, Chloe's and Dev's o.order_id values are NULL. They cannot satisfy the equality with an order-line key, so the inner join removes them. The query returns five order-line rows and loses both customers without orders.

Preserve the optional path
SELECT c.name, o.order_id, oi.product_id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
LEFT JOIN order_items oi ON oi.order_id = o.order_id;

Now the second join preserves those NULL-filled rows too. The result has (Ana, 101, 1), (Ana, 101, 2), (Ana, 102, 3), (Ben, 103, 1), (Ben, 103, 2), (Chloe, NULL, NULL), and (Dev, NULL, NULL): seven rows.

When later joins depend on an optional table's columns, they usually need to remain left joins to keep missing partners. A later inner join on a required table's columns has a different role, so examine the actual dependency in each condition.

Follow the same rule through a junction
SELECT s.name, c.title
FROM students s
LEFT JOIN enrollments e ON e.student_id = s.student_id
LEFT JOIN courses c ON c.course_id = e.course_id;

This returns (Mia, Algebra), (Mia, Biology), (Noah, Algebra), and (Omar, NULL). Making the last join an inner join removes Omar because his missing enrollment cannot match a course.

Parentheses offer another way to express the grouping: students s LEFT JOIN (enrollments e JOIN courses c ON c.course_id = e.course_id) ON e.student_id = s.student_id. The enrollment–course matches form the optional group; the outer join attaches that group to every student, preserving Omar.

Check later filters and join direction

Adding WHERE c.title = 'Algebra' to the all-left student query leaves only Mia and Noah in Algebra. The comparison is unknown for Omar's NULL course title. This answers “Who takes Algebra?” but no longer answers “Show all students and their courses.” An IS NULL test behaves differently because it deliberately accepts a missing value.

A later right join changes the kept side again: it preserves the newly added right table, and earlier unmatched rows can disappear. Put the must-keep table first and attach optional paths with left joins when that expresses the report clearly.

Build and inspect the query one join at a time. Note both the row count and which keys remain. A falling count can reveal lost unmatched rows; an unexpected jump can reveal a missing key part. Some counts stay unchanged when rows are both lost and multiplied, so inspect representative keys as well.

Key Takeaways

  • Each join acts on the result built so far.
  • An inner join through an optional table's NULL key removes unmatched rows.
  • Keep later dependent links optional when their missing rows must survive.
  • A later WHERE comparison can still remove rows preserved by left joins.
  • Inspect the count and the surviving keys after each step.
6

Reliable counts and results

Repeated values are often a correct consequence of a join, yet they can make a report's counts and sums wrong. Follow an order total as detail rows repeat it, choose the right identity to count, and distinguish useful DISTINCT from a hidden join mistake. Then check a report against predicted matches, complete keys, NULL behavior, and consistent key formats before trusting its plausible-looking output.

6.1 Counting and adding at the right level

A join can be correct while an aggregate over its result is wrong. The key question is what one row represents after the join. A value stored once per order can appear several times in a list of order lines.

Follow a repeated order total

Ana has order 101 for 22.00 and order 102 for 25.50. Adding the two orders gives 47.50. Order 101 has two lines, so joining to order_items repeats 22.00 twice; order 102 has one line. Adding the repeated values gives 69.50.

SELECT c.customer_id, c.name, SUM(o.total) AS spent
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.name;

GROUP BY collects rows for each customer, and SUM adds that group's values. This query returns (1, Ana, 47.50) and (2, Ben, 60.00). Adding the order-line join before grouping would inflate Ana to 69.50 and Ben to 120.00. The two customers' totals would become 189.50 instead of 107.50.

Across all four orders, including guest order 104, joining orders directly to their lines makes SUM(o.total) return 204.50 instead of 122.50. This fan-out comes from the one-to-many link, not from a syntax error.

Sum facts at their stored level

For order totals, sum orders without adding the detail table. For a line-value total, calculate each line's own value:

SELECT SUM(oi.quantity * p.price) AS sales
FROM order_items oi
JOIN products p ON p.product_id = oi.product_id;

The six line amounts are 16.00, 6.00, 25.50, 48.00, 12.00, and 15.00. They add to 122.50, the total of all four orders. Each quantity–price product belongs to one line, so each is added once.

Count real matches

After customers join to orders and lines, Ana occupies three rows. COUNT(*) counts those lines; COUNT(DISTINCT o.order_id) counts her two distinct orders.

After just a left join from customers to orders, COUNT(*) counts one NULL-filled row for Chloe and one for Dev. Count the optional order key instead:

SELECT c.customer_id, c.name, COUNT(o.order_id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.name;

This returns (1, Ana, 2), (2, Ben, 1), (3, Chloe, 0), and (4, Dev, 0). COUNT(column) ignores NULL values; COUNT(*) counts rows.

Two many sides multiply each other

If a customer has three orders and two tickets, joining both child tables independently to that customer makes six rows. Every order pairs with every ticket. Plain counts of either child key become six. Count distinct child keys to obtain three and two, or total the child tables separately.

DISTINCT c.customer_id, c.name is useful for a unique customer list. Keeping the key avoids merging different customers who share a name. It does not repair a missing join condition. SUM(DISTINCT o.total) is also unsafe: it merges two separate orders that happen to cost the same amount.

Key Takeaways

  • One-to-many joins repeat parent values; sum a fact at the level where it is stored.
  • COUNT(*) counts rows; COUNT(column) skips NULLs.
  • COUNT(DISTINCT key) counts distinct non-NULL identities after fan-out.
  • Two independent many-side joins can multiply each other.
  • SUM(DISTINCT amount) can merge different records with equal amounts.
  • Keep an identifying key in a unique entity list.

6.2 Checking matches before trusting a report

A query can run and still describe the wrong relationships. Before using a join result, predict what should survive, how often each key should appear, and what one result row should mean.

Make a row-count prediction

If an order's customer reference matches a unique customer key, each order has at most one customer partner. An inner join therefore returns at most one row per order. In the store data, three of four orders have a customer, so the customer–order inner join returns three rows.

From the customer side, a left join returns the three matched pairs plus one row for Chloe and one for Dev: five rows. A cross join of the four customers and four orders gives 16. These predictions provide different checks for different join types; the number of tables alone does not determine the count.

Investigate the shape of a wrong result
SymptomWhat to inspect
Every left row pairs with every right rowMissing condition or an always-true condition
No matches, or unrelated entities pairedWrong columns or incompatible key values
One logical record has several partnersAn incomplete key, duplicate lookup key, or expected one-to-many link
Parents without children disappearAn inner join or optional-side filter later in the query

Matching only store_id when records are identified by (store_id, month) mixes different months. Match every component with AND. A query that happens to work while a store has one month can fail as soon as a second month is loaded.

Separate missing values from bad keys

With ordinary equality, NULL keys do not match, including NULL against NULL. Joining orders to themselves on customer_id gives four pairs from Ana's two orders and one from Ben's order, for five rows. The guest order contributes none. An outer join is needed when a row with a missing reference must survive.

Duplicate lookup values multiply only the matches for those values. If a country code occurs twice in a lookup table, each customer with that code gets two partner rows. Codes that occur once still give one partner. Inspect a lookup expected to be unique with:

SELECT code, COUNT(*) AS copies
FROM countries
GROUP BY code
HAVING COUNT(*) > 1;

GROUP BY collects each code's rows; HAVING keeps groups whose count exceeds one. Loading the entire lookup twice would double every matching customer's rows.

Also inspect key types and formats. Text '0042' and the number 42 can produce different behavior across databases: no match, a type error, or an automatic conversion. SQLite can match a TEXT column containing '0042' to an INTEGER column containing 42. Use consistent key types and formats, or an explicit conversion chosen for the intended meaning.

Work through the report's requirements

Ask which table must keep all rows. Trace its full path of keys, and check each condition against the schema. Decide why each link is inner or outer, then check whether each filter belongs in matching or after matching. Compare row counts after each step and inspect a known matched row, an unmatched row, and a row with several partners. Finally, check that each count and sum measures the intended kind of record.

Key Takeaways

  • Predict counts from the relationship and the join type.
  • Check every component of a composite key.
  • Ordinary equality never treats NULL keys as a match.
  • Duplicate lookup keys multiply the affected matches.
  • Keep linked keys in consistent types and formats.
  • Check surviving identities as well as totals and row counts.

SQL Joins examples

6 Walkthroughs

1

From schema links to joined rows

10 steps
Read the store keys, predict matching pairs, and make each column’s source clear.
  1. 1
    IntroductionStart with the report’s question

    A report needs customer names beside order totals, but those facts live in different tables. A join brings the related rows together.

    First find the key link. Then picture the pairs it permits. This order helps you predict the result before writing SQL: a name may repeat because it has several matching orders, and a row may disappear because it has none.

  2. 2
    ExampleRead the customer and order keys

    Customers are 1 Ana, 2 Ben, 3 Chloe, and 4 Dev. Orders 101 and 102 refer to customer 1; order 103 refers to customer 2. Order 104 is a guest order with a NULL customer reference.

    The link is orders.customer_id → customers.customer_id. The order number identifies a purchase, while the customer reference identifies its owner. Comparing the two different kinds of ID would not express ownership.

  3. 3
    ExplanationPut the reference on the many side

    One customer can place several orders. Each order stores one customer reference, so orders is the many side of the link.

    For a one-to-one link, that reference is unique as well. For many-to-many links, such as students taking courses, a junction table records the pairs. These structures tell you which route a report must follow and where several partners are possible.

  4. 4
    ChallengeFind the missing route

    You need the products each customer bought. The schema links customers to orders, orders to order_items, and order_items to products. Can you skip order_items and compare order ID directly with product ID?

    ---reveal---

    No. Those IDs identify different things. Follow customers → orders → order_items → products. Each order-item row records a product in a particular order, supplying the link the direct comparison lacks.

  5. 5
    ExampleMake the first pairs

    Apply o.customer_id = c.customer_id. Order 101 matches Ana because both customer values are 1. Order 102 makes another pair with Ana, and order 103 matches Ben.

    The result pairs are (Ana, 101), (Ana, 102), and (Ben, 103). Chloe and Dev have no partners. Guest order 104 has a NULL customer key, so its equality condition is unknown and supplies no match.

  6. 6
    Key InsightOne line for each match

    Imagine a line between each customer row and each order row that matches it. Each line becomes a result row. Ana has two lines, so her name appears twice.

    This picture also works when keys repeat on both sides: two left rows and three right rows with one key create six lines. Overlapping circles cannot show those individual pairings or explain how a join becomes longer than either input.

  7. 7
    TipSay where a column comes from

    Give the table references aliases, such as customers c and orders o. Write c.name for the customer name and o.total for the order total.

    Both inputs have customer_id. With JOIN ... ON, selecting that name alone is ambiguous; qualify it as c.customer_id or o.customer_id. SELECT * includes all seven input columns, including both key copies. Selecting only needed columns makes the report narrower.

  8. 8
    ExplanationMatching comes before filtering

    Read the logical order as joins, then WHERE, then SELECT, then ORDER BY. A filter can use a joined column that the report does not display.

    For example, filtering the customer–order pairs by c.city = 'Denver' keeps Ana’s two orders. Chloe lives in Denver too, but an inner join produced no row for her. A later filter cannot restore a row excluded during matching.

  9. 9
    ChallengePredict a repeated-key result

    A table of registrations has two rows with location key 8. A location lookup accidentally has three rows with key 8. All other keys are absent. How many rows does their inner equality join return, and does selecting only the key change that count?

    ---reveal---

    Six rows return: each registration matches all three lookup rows. Selecting only the key narrows each row but keeps six rows unless a separate operation such as DISTINCT removes repeated selected values.

  10. 10
    RecapRead the relationship, then the rows

    Find the key route before choosing the output columns. One-to-many links can repeat parent values; a junction table carries a many-to-many link.

    For each pair, ask whether the condition is true. Then check how many partners each row has. Use aliases to distinguish the column sources, and keep matching separate from later filtering and sorting when explaining the result.

2

Build and read inner joins

11 steps
Write a complete matching query, then compare shorthand and range conditions.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionRequire a partner on both sides
  2. 2
    ExampleWrite the key condition
  3. 3
    ExampleRead all three output rows
  4. 4
    ChallengeAdd a customer filter
  5. 5
    TipMatch the complete identity
  6. 6
    ExplanationUse a shared-name shorthand
  7. 7
    ChallengeNotice what NATURAL changes
  8. 8
    ConnectionRead an older comma join
  9. 9
    ExampleMatch prices to bands
  10. 10
    ChallengePredict overlap and a gap
  11. 11
    RecapRead the condition behind the syntax
3

Keep missing matches without losing them to filters

14 steps
Choose the preserved side and trace ON and WHERE through outer-join results.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionDecide which records must remain
  2. 2
    ExampleKeep every customer
  3. 3
    ChallengeChange the must-keep side
  4. 4
    TipTest the missing partner’s key
  5. 5
    ExplanationRead a right join as a reversed left join
  6. 6
    ExampleKeep unmatched records from both sides
  7. 7
    ChallengeReconcile two fresh key lists
  8. 8
    TipCheck outer-join support
  9. 9
    ExampleRestrict matching orders in ON
  10. 10
    ExampleMove the date test to WHERE
  11. 11
    ChallengeFilter the left table deliberately
  12. 12
    ConnectionDecide what a displayed zero means
  13. 13
    ChallengeKeep everyone with recent activity
  14. 14
    RecapKeep matching and filtering separate
4

Follow managers and build combinations

11 steps
Use role aliases for self joins, then distinguish intended and accidental cross products.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionChoose roles or possibilities
  2. 2
    ExampleRead the reporting lines
  3. 3
    ChallengeKeep the person at the top
  4. 4
    ConnectionClimb one more level
  5. 5
    ExampleMake colleague pairs once
  6. 6
    ExampleBuild six shirt possibilities
  7. 7
    ChallengePredict the effect of another input
  8. 8
    Key InsightMake a complete reporting grid
  9. 9
    TipRepair the missing condition
  10. 10
    ChallengeConnect pair rules with product size
  11. 11
    RecapGive each pairing a clear purpose
5

Follow a junction and preserve a longer path

14 steps
Trace enrollments and purchase lines, then keep missing partners through dependent links.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionPlan the route to the facts
  2. 2
    ExampleRead the enrollment links
  3. 3
    ExampleCross the junction in two steps
  4. 4
    ChallengeFind a course with no students
  5. 5
    ExplanationExtend the store route
  6. 6
    ExampleExpand order 101 into lines
  7. 7
    ExampleRead the remaining purchase rows
  8. 8
    ChallengeChoose the product link
  9. 9
    ExampleSee a later join remove customers
  10. 10
    ExampleCarry the missing row forward
  11. 11
    ChallengeKeep the student without an enrollment
  12. 12
    ConnectionMake an optional group explicitly
  13. 13
    ChallengeCheck the finished report’s filter
  14. 14
    RecapInspect the whole path
6

Repair inflated totals and check the result

12 steps
Choose the right record to count or sum, then diagnose key and row-count problems.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionName the thing being measured
  2. 2
    ExampleWatch an order total repeat
  3. 3
    TipSum a fact at its own level
  4. 4
    ChallengeReject an amount-based repair
  5. 5
    ExampleCount an identity or a placeholder
  6. 6
    Key InsightTwo child lists multiply
  7. 7
    ExplanationPredict the scale before running
  8. 8
    ChallengeLocate an incomplete key
  9. 9
    TipCheck the lookup and the key format
  10. 10
    ExampleDo not match missing keys by equality
  11. 11
    ChallengeChoose a correct report measure
  12. 12
    RecapCheck meaning as well as counts

SQL Joins practice questions

50 Practice Questions in 5 sets

Set 1: Read relationships and choose matching rows10 Practice Questions

Question 1 of 10

Two rows in table a have key 7. Three rows in table b have key 7. These are the only rows. How many rows does a JOIN b ON a.k = b.k return?
  1. 2
  2. 3
  3. 5
  4. 6
Show answerHide answer
Answer: D
Each of the two left rows matches all three right rows, producing six distinct row pairs. An inner join keeps every matching pair, even when key values repeat. Counting only the three right rows would ignore that each has two left partners.

Question 2 of 10

rentals.customer_ref refers to customers.customer_id. Rentals also have their own rental_id. What ON condition links each rental r to its customer c?
  1. r.customer_ref = c.customer_id
  2. r.rental_id = c.customer_id
  3. r.customer_ref = r.rental_id
  4. c.customer_id = c.customer_id
Show answerHide answer
Answer: A
The foreign key customer_ref identifies the customer attached to the rental, so it must be compared with the customer key. The rental ID identifies a rental and expresses a different meaning. Similar-looking numeric IDs are not enough to establish a link.

Question 3 of 10

A library member can borrow many loans, and each loan belongs to one member. Where should the member foreign key be stored?
  1. In the members table
  2. In the loans table
  3. In both of the tables
  4. In a separate table
Show answerHide answer
Answer: B
Each loan needs to identify its one member, so the foreign key belongs in loans, the many side. A member can then be referenced by several loan rows. Putting one loan reference in members would not represent all the loans that member can have.

Question 4 of 10

Tables a and b each contain account_id and region. A natural join used to share only account_id; region was just added to b. Why can a formerly matching pair disappear?
  1. Its regions match but its account IDs differ.
  2. Adding a shared column makes all pairs match.
  3. Its account IDs match but its regions differ.
  4. The new column makes shared IDs display twice.
Show answerHide answer
Answer: C
NATURAL JOIN compares every shared column name, so region now joins account_id as a required equality. A pair with equal account IDs but unequal regions no longer matches. The join does not keep a pair merely because one shared column agrees.

Question 5 of 10

Teams are (1, North) and (2, South). Matches (match_id, team_id) are (81, 1) and (82, 1). What names appear in a team-side LEFT JOIN to matches, before filtering?
  1. North once; South once
  2. North twice; South once
  3. North twice; South never
  4. North twice; South twice
Show answerHide answer
Answer: B
North has two matching match rows, so it appears twice. South has no match but the left join preserves it once with NULL match columns. Counting one row per team would miss the extra row contributed by North’s second match.

Question 6 of 10

List A has unique keys 2 and 5. List B has unique keys 5 and 8. What pairs result from a FULL OUTER JOIN on key equality?
  1. (2, NULL), (5, 5)
  2. (5, 5), (NULL, 8)
  3. (2, NULL), (5, 5), (NULL, 8)
  4. (2, 2), (5, 5), (8, 8)
Show answerHide answer
Answer: C
Key 5 matches across the two inputs. Key 2 exists only in A, and key 8 exists only in B, so each remains with NULL for its missing partner. Filling both columns with each unmatched key would invent values from a row that was never present on that side.

Question 7 of 10

Employees are (id, manager_id): (10, NULL), (11, 10), (12, 10). A self join uses a.manager_id = b.manager_id AND a.id < b.id. Which ID pair returns?
  1. (11, 12)
  2. (10, 11)
  3. (11, 11)
  4. (12, 11)
Show answerHide answer
Answer: A
Employees 11 and 12 share manager 10, and 11 is smaller than 12. The less-than condition excludes self-pairs and the reversed pair. Employee 10 has a NULL manager ID, so it cannot equal the manager ID of employee 11 or 12.

Question 8 of 10

A studio lists 4 rooms and 3 session times. How many rows does a CROSS JOIN of the lists produce?
  1. 7
  2. 4
  3. 3
  4. 12
Show answerHide answer
Answer: D
Each of the four rooms pairs with each of the three times, producing twelve combinations. Adding four and three would count input entries, not room–time pairs. No matching condition limits the combinations in a cross join.

Question 9 of 10

authors(author_id) and books(book_id) are linked by book_authors(author_id, book_id). What path finds the books for an author?
  1. authors → books on equal IDs
  2. authors → books with CROSS JOIN
  3. book_authors → authors on book_id
  4. authors → book_authors → books
Show answerHide answer
Answer: D
The junction table records which author belongs to which book. Join authors to book_authors by author ID and book_authors to books by book ID. Directly equating the two endpoint IDs compares different kinds of identifiers and ignores the stored authorship relationships.

Question 10 of 10

sales(store_id, month) has (6, March) and (6, April). targets(store_id, month) has the same two pairs, each once. A query joins only on store ID and returns four rows. What repair gives one target per sale?
  1. Use DISTINCT on the store ID alone.
  2. Add equality of the two month columns to ON.
  3. Replace the inner join with a left join.
  4. Keep only the first two rows with LIMIT 2.
Show answerHide answer
Answer: B
Store ID alone lets each month’s sale match both target months, creating four pairs. Matching month as well leaves only March–March and April–April. A left join would still make all four matches because preserving rows does not complete an incomplete key.
Set 2: Keep missing matches and follow table paths10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

Each asset can have at most one active label, and each label belongs to at most one asset. Which relationship describes the two tables?
  1. One-to-many
  2. Many-to-many
  3. One-to-one
  4. All pairings
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

invoices contains (invoice_id, client_id): (301, 9), (302, NULL), (303, 12). clients has unique IDs 9 and 12. What invoice IDs survive an inner join on client ID?
  1. 301 and 303
  2. 301, 302, and 303
  3. 302 only
  4. 301 only
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A desk can have several reservations. Reservation IDs cannot be NULL, but note may be NULL. After desks LEFT JOIN reservations, which WHERE test finds only desks with no reservation?
  1. r.note IS NULL
  2. r.reservation_id = NULL
  3. r.note IS NOT NULL
  4. r.reservation_id IS NULL
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A report must show every department and attach only active employees. It begins departments d LEFT JOIN employees e ON e.department_id = d.department_id. Where should e.active = 1 go?
  1. As a condition in WHERE
  2. As an extra AND condition in ON
  3. As the ORDER BY expression
  4. As a replacement for the key comparison
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A CROSS JOIN combines a table with 8 rows and an empty table. How many rows return?
  1. 8
  2. 1
  3. 9
  4. 0
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

memberships(member_id, club_id) records a member joining a club. Where does joined_on, the date of that particular membership, belong?
  1. In memberships
  2. In members
  3. In clubs
  4. In a comma-separated club list
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A report follows clients → invoices → lines → services. Each arrow is a key link, and each service row is unique by service ID. What does one detail result row represent when every link matches?
  1. One client
  2. One invoice
  3. One invoice line
  4. One service in the catalog
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

Branches are 1 and 2. Branch 1 has one job with two task rows; branch 2 has no jobs. A chain uses branches LEFT JOIN jobs, then INNER JOIN tasks through the job key. How does making the task join LEFT change the row count?
  1. It stays at 2.
  2. It increases from 2 to 3.
  3. It increases from 2 to 4.
  4. It decreases from 3 to 2.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

One project has two invoices, each for 40.00. The first invoice has two lines and the second has three. After joining invoices to lines, what are SUM(total) and SUM(DISTINCT total)?
  1. 80.00 and 80.00
  2. 200.00 and 80.00
  3. 200.00 and 40.00
  4. 80.00 and 40.00
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A region lookup should have one row per code. Some joined customers appear twice. Which diagnostic query finds repeated lookup codes?
  1. SELECT code, COUNT(*) FROM regions GROUP BY code HAVING COUNT(*) > 1;
  2. SELECT DISTINCT code FROM regions;
  3. SELECT code, COUNT(*) FROM regions GROUP BY code HAVING COUNT(*) = 1;
  4. SELECT code FROM regions WHERE code IS NULL;
Show answerHide answer

Save this lesson to see this answer.

Set 3: Trace filters, pairings, and repeated values10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

Stores A and B are preserved in a left join to visits. A has one visit before a cutoff; B has none. ON keeps only visits on or after the cutoff date. If that restriction moves to WHERE, with no other changes, which stores remain?
  1. Neither A nor B
  2. A only
  3. B only
  4. Both A and B
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

Three employees have distinct IDs 21, 22, and 23, all with manager ID 5. A self join matches equal manager IDs and different employee IDs using <>. How many rows return, and how many would < return?
  1. 3 with <>; 3 with <
  2. 6 with <>; 3 with <
  3. 9 with <>; 6 with <
  4. 6 with <>; 6 with <
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A flight row has from_code and to_code, both referring to airports.code. How should a report obtain both airport names?
  1. Join airports once on from_code and reuse its name for arrival.
  2. Join airports once on both code equalities with AND.
  3. Cross-join airports and filter only by from_code.
  4. Join airports twice with departure and arrival aliases.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

An ON join combines a 4-column table with a 3-column table. What is the width of SELECT * before any columns are removed?
  1. 4 columns
  2. 3 columns
  3. 12 columns
  4. 7 columns
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A charge of 25.00 is joined to bands using BETWEEN low AND high. Bands are (0.00, 25.00) and (25.00, 50.00). How many matching band rows does the charge produce?
  1. 0
  2. 1
  3. 2
  4. 3
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A query uses suppliers s RIGHT JOIN deliveries d ON d.supplier_id = s.supplier_id and explicitly selects s.name, d.delivery_id. How can it preserve the same pairs with LEFT JOIN?
  1. suppliers s LEFT JOIN deliveries d ON d.supplier_id = s.supplier_id
  2. deliveries d LEFT JOIN suppliers s ON d.supplier_id = s.supplier_id
  3. deliveries d JOIN suppliers s ON d.supplier_id = s.supplier_id
  4. suppliers s FULL JOIN deliveries d ON d.supplier_id = s.supplier_id
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

All technicians must appear with their optional assignments and sites. The query left-joins assignments, then inner-joins sites using the assignment’s site ID. Why do unassigned technicians disappear?
  1. Their NULL assignment keys cannot match the inner site join.
  2. A left join can preserve only one unassigned technician.
  3. Selecting a site column removes every unmatched row.
  4. The site alias hides the technician’s primary key.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A team has no tasks. After teams LEFT JOIN tasks and grouping by team, what does COUNT(t.task_id) return if task IDs cannot be NULL?
  1. 1
  2. NULL
  3. 0
  4. The number of teams
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A writer has 2 articles and 3 awards, each linked independently to the writer. A query inner-joins both children by writer ID and groups by writer. What are COUNT(article_id) and COUNT(DISTINCT award_id)?
  1. 2 and 3
  2. 6 and 6
  3. 6 and 3
  4. 3 and 2
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A report starts from shipments s LEFT JOIN carriers c on a matching carrier key. One shipment has no carrier match, and one carrier has no shipments. Which unmatched record is preserved?
  1. The carrier only
  2. The shipment only
  3. Both unmatched records
  4. Neither unmatched record
Show answerHide answer

Save this lesson to see this answer.

Set 4: Diagnose incomplete links and missing rows10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

workers.supervisor_ref refers to workers.worker_id. Workers also have a name column. What should link the worker alias w to the supervisor alias s?
  1. w.supervisor_ref = s.worker_id
  2. w.worker_id = s.supervisor_ref
  3. w.name = s.name
  4. w.worker_id = s.worker_id
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A join condition is true for 4 candidate row pairs, false for 3, and unknown for 2. How many rows does the inner join return?
  1. 6
  2. 4
  3. 7
  4. 9
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A query inner-joins payments p to accounts a by account ID. It selects only a.name, p.amount. Must account ID also be selected for the join to work?
  1. Yes; both account-ID columns must be selected.
  2. Yes; at least one account-ID column must be selected.
  3. No; SELECT automatically adds both account IDs.
  4. No; ON can use columns absent from SELECT.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

Tables a(id, label) and b(id, label) have rows (1, red), (2, blue) and (1, red), (2, green) respectively. How many rows return with JOIN ... USING (id), and with NATURAL JOIN?
  1. 1 with USING; 1 with NATURAL
  2. 2 with USING; 2 with NATURAL
  3. 2 with USING; 1 with NATURAL
  4. 4 with USING; 2 with NATURAL
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

Regions East and West each have one office. An all-region LEFT JOIN includes AND r.name = 'East' in ON. Which rows appear when selecting region and office?
  1. East with its office; West with NULL
  2. East with its office; West is absent
  3. East with its office; West with its office
  4. East with NULL; West with NULL
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

Workers are (id, boss_id): (31, NULL), (32, 31), (33, 32), (34, 32). What ID triples return from inner joins on e.boss_id = m.id and m.boss_id = g.id?
  1. (32, 31, NULL) only
  2. (31, 32, 33) and (31, 32, 34)
  3. (32, 31, NULL), (33, 32, 31), (34, 32, 31)
  4. (33, 32, 31) and (34, 32, 31)
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

customers → orders → order_lines → products is a key path. A four-table inner join uses every link. Why can one order ID appear in several rows even if every primary key is unique?
  1. The product primary key must be duplicated.
  2. Every join returns all products for every order.
  3. The order has several matching order-line rows.
  4. Unique keys prevent one-to-many relationships.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

Zones A and B must both appear. A has one booking linked to a real service; B has no bookings. The query left-joins bookings and services, then adds WHERE service.category = 'Repair'. A’s service is Repair. What remains?
  1. A with its service; B with NULL
  2. A with its service; B disappears
  3. Both zones with NULL service fields
  4. B with NULL; A disappears
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

Two subscribers are both named Lee, with IDs 41 and 42. The first has two payments and the second has one. After an inner payment join, how many rows do SELECT DISTINCT name and SELECT DISTINCT subscriber_id, name return?
  1. 2 and 2
  2. 1 and 3
  3. 3 and 2
  4. 1 and 2
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A lookup has code X twice and code Y once. One event references X and two events reference Y. How many rows does their inner join produce?
  1. 4
  2. 3
  3. 5
  4. 6
Show answerHide answer

Save this lesson to see this answer.

Set 5: Check whole reports under changed conditions10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

Table A has key rows 1, 1, 2. Table B has key rows 1, 1, 1, 3. How many rows does an equality inner join return?
  1. 3
  2. 7
  3. 6
  4. 12
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A rate of 9.50 is inner-joined to bands [0.00, 9.00], [10.00, 20.00], and [8.00, 12.00] using inclusive BETWEEN tests. If the third band is removed, how does this rate’s output change?
  1. From two rows to one row
  2. From one row to one row
  3. From three rows to two rows
  4. From one row to zero rows
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

List A contains keys 4, 4, 9; list B contains keys 4, 4, 12. A full equality join selects both key columns. How many rows return, and how many remain after WHERE a.k IS NULL OR b.k IS NULL?
  1. 4 total; 2 mismatches
  2. 6 total; 2 mismatches
  3. 6 total; 4 mismatches
  4. 9 total; 2 mismatches
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

Customers P and Q each have one old order and no recent orders; R has no orders. A key-only LEFT JOIN is followed by WHERE o.date >= '2026-09-01' OR o.order_id IS NULL. Which customers survive?
  1. R only
  2. P, Q, and R
  3. P and Q only
  4. No customers
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A CROSS JOIN combines 2 modes, 5 regions, and 4 months. How many combinations does it produce?
  1. 11
  2. 20
  3. 40
  4. 10
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

Members are 7, 8, and 9; clubs are 50 and 60. Membership pairs are (7,50), (7,60), and (8,50). A query left-joins members to memberships and then clubs. What are the total row count and member 9’s club value?
  1. 3 rows; NULL
  2. 4 rows; NULL
  3. 4 rows; 60
  4. 6 rows; NULL
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

Groups G and H must both appear. G has one signup linked to a valid class; H has none. Compare groups LEFT JOIN signups JOIN classes with groups LEFT JOIN (signups JOIN classes), using the relevant keys. What happens to H?
  1. Only the grouped optional path keeps H.
  2. Both forms keep H.
  3. Only the ungrouped path keeps H.
  4. Neither form keeps H.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A client has invoices 501 for 18.00 and 502 for 27.00. Invoice 501 has 3 lines; invoice 502 has 2. The report needs total invoice value and number of invoices. What result should it report?
  1. 108.00 and 5
  2. 45.00 and 5
  3. 108.00 and 2
  4. 45.00 and 2
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

One table stores an identifier in a TEXT column as '0064'; another stores 64 in an INTEGER column. What should you check before relying on their equality join across databases?
  1. Only whether both tables have equal row counts
  2. Only whether both columns share the same name
  3. The key types, formats, and conversion behavior
  4. Whether DISTINCT can remove all unmatched rows
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A parent has 2 bills and 4 cases, independently linked by parent ID. An inner join to both children groups by parent. What are COUNT(*), COUNT(DISTINCT bill_id), and COUNT(DISTINCT case_id)?
  1. 6, 2, and 4
  2. 8, 2, and 4
  3. 8, 8, and 8
  4. 4, 2, and 4
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

58 Key Terms, each with its definition

A

Accidental cross join
An accidental cross join combines every row pair because a needed condition is missing or always true, producing unrelated matches.
A forgotten condition makes a customer appear beside other customers' orders as well as their own, increasing the result.
Aggregation level
An aggregation level is the kind of record a count or sum is intended to measure, such as customers, orders, or order lines.
Count distinct order IDs when the joined rows represent lines but the report asks how many orders exist.
Ambiguous column
An ambiguous column is an unqualified reference that could name a column in more than one input, leaving its origin unclear.
With JOIN ... ON, selecting bare customer_id fails when customers and orders both supply it.
AND in a join condition
AND in a join condition requires all its component tests to be true before the candidate pair counts as a match.
Matching both store and month prevents a store’s April row from joining to its March figures.
Anti-join
An anti-join returns rows with no matching partner, which can be found by a left join followed by a missing-right-key test.
Left-join products to order items and test the missing line key to find unsold products, such as a stapler.

C

Cartesian product
A Cartesian product is the complete collection of combinations formed by choosing one row from each input table.
Placing every customer beside every order forms all possible customer–order pairs, including unrelated pairings.
COALESCE
COALESCE returns its first non-NULL argument, allowing a result to display a fallback or a key available from either input.
COALESCE(o.total, 0) displays zero when the joined order total is NULL, so label the column carefully.
Comma join
A comma join names tables separated by commas in FROM and expresses their matching condition in the query’s WHERE clause.
Removing the customer-key test from a comma join of four customers and four orders makes every customer pair with every order.
Composite key
A composite key identifies a record using several columns together; matching a reference to it requires every key component.
The student–course pair prevents duplicate enrollments even though neither student ID nor course ID is unique alone.
COUNT(*)
COUNT(*) counts result rows, including an outer join’s NULL-filled placeholder for a parent without a matching child.
After a customer-side left join, a customer with no order contributes one row to COUNT(*).
COUNT(column)
COUNT(column) counts non-NULL values in a selected column, so it can count real optional partners after a left join.
A customer with no order has a NULL joined order key, which contributes zero to COUNT(o.order_id) in that customer’s group.
COUNT(DISTINCT key)
COUNT(DISTINCT key) counts each different non-NULL key once, ignoring repetitions introduced by matching several detail rows.
If a customer’s three line rows contain only two order IDs, counting distinct order IDs returns two.
Cross join
A cross join returns every pair of input rows without a matching condition, giving the product of the two input row counts.
Three shirt sizes crossed with two colors produce six possible size–color rows for the store.

D

Dependent link
A dependent link connects a later table through a column from an earlier joined table, so missing earlier partners affect it.
A course join depends on the enrollment’s course ID; a student with no enrollment has a NULL course ID that cannot satisfy that equality.
DISTINCT
DISTINCT removes rows identical across every selected value; it does not repair an incorrect relationship or deduplicate entities by itself.
Selecting distinct customer ID and name keeps different people separate even if they share a name.
Duplicate lookup key
A duplicate lookup key is a repeated value on a side expected to be unique, giving matching input rows extra partners.
If one country code appears twice, customers with that code each produce two joined lookup matches.

E

Enrollment
An enrollment links one student to one course and can be represented by one junction row with those two identifying keys.
Mia’s Algebra and Biology courses require separate enrollment rows; Omar has no enrollment row.
Equijoin
An equijoin matches rows using equality comparisons, often between a foreign key and the primary or unique key it references.
Customer ID 1 in an order equals customer ID 1 in the matching customer row, so that pair satisfies the equijoin.

F

Fan-out
Fan-out repeats a parent row for each matching child, which is useful for detail lists but can multiply parent-level totals.
If order 101, with a 22.00 total, has two lines, it appears twice after joining its lines, so that total would be added twice.
Foreign key
A foreign key is a column or group of columns referring to a primary or unique key in the same table or another table.
Several orders can hold customer ID 1, linking each of those orders to the single customer row with that ID.
Full outer join
A full outer join returns matching pairs and preserves unmatched rows from both inputs with NULLs for each missing partner.
A full customer–order join keeps customers without orders and guest orders without customers alongside the matched orders.

H

Hierarchy
A hierarchy is a structure of parent–child links, such as employee reporting lines, that a self join can follow one level at a time.
Jamal reports to Hassan, who reports to Grace; three table roles can show this two-link chain.

I

Inner join
An inner join returns every pair that satisfies its join condition and excludes rows from either input that find no match.
If Ana has two orders and Ben has one, an inner join of customers and orders returns those three orders and leaves out a guest checkout.
IS NULL test
An IS NULL test is true when a value is NULL and can select missing partners after an outer join if applied to a suitable key.
Testing o.order_id IS NULL keeps customers without orders; testing o.order_id = NULL keeps no rows.

J

Join
A join combines input rows into result rows according to a matching rule, optionally preserving unmatched rows with NULLs.
A customer’s name and an order’s total can appear together after their rows are joined by customer ID.
Join chain
A join chain attaches several tables in a query, with each new join acting on the rows built by the preceding joins.
When a customer–order join builds three pairs and those orders have five lines, joining order items expands them into five detail rows.
Join condition
A join condition is the expression that decides whether a pair matches; only a true result qualifies as a match.
o.customer_id = c.customer_id pairs order 101, which holds customer ID 1, with the customer row whose ID is also 1.
Join path
A join path is a sequence of meaningful links connecting the tables needed for a query, with a condition for each link.
The purchase report follows customers to orders, then order items, then products to reach product names.
Junction table
A junction table records one row per relationship pair and holds a foreign key to each endpoint of a many-to-many link.
Each enrollment row links one student with one course, allowing both students and courses to repeat.

K

Key-format mismatch
A key-format mismatch occurs when linked identifiers use different types or representations, making comparison behavior database-dependent.
Text '0042' and numeric 42 can match through conversion in SQLite but need consistent handling across systems.

L

Left outer join
A left outer join keeps every left row, returning each matching pair or one right-side NULL-filled row when no partner matches.
A customer who has never ordered still appears in a left-joined customer report, with NULL order columns.

M

Many-to-many relationship
A many-to-many relationship lets rows on either side have several partners and is represented with a table of linking pairs.
When one student takes two courses and one course includes two students, an enrollments table records each student–course pair.
Match restriction
A match restriction limits which candidate partners qualify in ON, while an outer join still preserves its required input rows.
An order-date cutoff in ON keeps every customer but attaches only orders meeting that cutoff.
Mixed join chain
A mixed join chain combines inner and outer joins, requiring each step to be checked for which earlier rows it can remove.
A left order join followed by an inner order-item join removes customers whose order key is NULL.

N

Natural join
A natural join automatically matches every column name shared by the inputs, so changes to shared names can change its result.
Adding a delivery-city column to orders makes a natural join compare cities as well as customer IDs.
Non-equijoin
A non-equijoin matches rows using a comparison other than equality, such as a price falling between a band’s lower and upper limits.
A mug priced at 8.00 falls in the price band named Standard and joins to that band’s label.
NULL-filled row
A NULL-filled row is an outer-join result that preserves an unmatched input row and supplies NULL for the missing partner’s columns.
A customer’s NULL order number in a left-join result marks a missing order partner; it is not a stored order.

O

ON clause
An ON clause gives the matching expression for a join and can combine several comparisons that must hold for a pair.
A condition matching shipments to order lines uses both order ID and product ID to identify the correct partner.
One-to-many relationship
A one-to-many relationship lets one parent row have several child rows, with the foreign key stored on the child side.
When a customer has two orders, both order rows refer to that customer’s ID without repeating the address.
One-to-one relationship
A one-to-one relationship allows at most one partner per row on either side, usually through a foreign key that is also unique.
If each employee can have at most one badge, a unique employee reference in badges enforces that link.
Optional side
The optional side of a left join is the right input, whose columns are filled with NULL when a preserved row has no match.
Orders are optional in an all-customer report, so a missing order must not remove the customer.
Order line
An order line is a record for a product within an order, commonly linking the order and product keys and storing its quantity.
Order 101 has separate Mug and Pen lines, so its customer and order values repeat in the detail report.

P

Parenthesized join
A parenthesized join explicitly groups joined inputs, allowing an inner-joined group to serve as one optional side of an outer join.
Enrollments joined to courses can form a group that is then left-joined to all students, including those with no enrollment.
Post-join filter
A post-join filter applies in WHERE to rows built by the join and can remove both matched rows and NULL-filled outer-join rows.
A right-side date comparison in WHERE removes a customer without orders, because that customer’s joined order date is NULL.
Preserved side
The preserved side of an outer join is the input whose unmatched rows must be returned before later joins or filters act.
Customers are the preserved side in customers c LEFT JOIN orders o, regardless of the number of orders.
Primary key
A primary key is a column or group of columns whose value uniquely identifies one row and cannot contain NULL.
In a customer table, customer_id distinguishes one customer’s record from every other customer record.

Q

Qualified column name
A qualified column name includes a table name or alias before the column, identifying which input supplies the value.
c.customer_id selects the customer-side key when both joined tables contain customer_id.

R

Reconciliation
Reconciliation compares records that should agree, aligning matching keys while exposing missing records or differing values.
A full join can place warehouse stock beside a physical count while keeping SKUs absent from either list.
Relationship attribute
A relationship attribute is a fact about a specific link between records, so it belongs on the row representing that link.
Quantity is stored on an order-item row because the same product can be bought in different quantities in different orders.
Reporting grid
A reporting grid is a complete list of required combinations that can be built with a cross join and filled by a left join.
Crossing stores with months makes missing sales months visible when actual sales are attached.
Right outer join
A right outer join preserves all right rows, adding left-side NULLs when a right row has no partner that meets the condition.
With orders on the right, a guest order 104 with a NULL customer reference stays even though no customer row can match it.
Row-count prediction
A row-count prediction estimates a join’s result from the matching relationships and the unmatched rows its join type preserves.
Three matched orders plus two unmatched customers predict five rows for the customer-side left join.

S

Self join
A self join joins a table to itself under separate aliases so its rows can play different roles or be compared with each other.
The employee alias supplies manager_id, and the manager alias supplies the matching emp_id.
Self-pair
A self-pair matches a row with itself when a self-join condition allows both aliases to refer to the same identifying key.
An employee row pairs with itself when a self-join condition checks only that both aliases' manager IDs are equal.

T

Table alias
A table alias is a short name assigned to a table reference so its role and column references stay clear within a query.
In FROM customers c, the alias c lets the query refer to the customer name as c.name.

U

Unknown comparison result
An unknown comparison result occurs in ordinary equality with NULL and does not count as true for matching or WHERE filtering.
Guest order 104 has a NULL customer reference, so it cannot match even another NULL through equality.
Unordered pair
An unordered pair treats two members as the same pairing in either order; comparing distinct IDs with < keeps it once.
The colleague pair Hassan–Iris appears once when the condition requires the first employee ID to be smaller.
USING clause
A USING clause lists same-named columns to compare by equality and displays each listed shared column once in SELECT *.
USING (customer_id) expresses the customer-key match without spelling out both column references.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started