--- title: Querying with joins description: Combine rows from related tables — author with book, member with loan — using SQL joins in advanced mode. sidebar: order: 2 --- import Demo from '../../../components/Demo.astro'; [Build the library](/guides/build-the-library/) left you with four related tables. So far each `show data` looks at one table at a time — but the interesting questions span tables: *who wrote this book? who has borrowed it?* Answering those means a **join**: matching rows from one table against related rows in another. Joins are part of SQL, so this guide is in **advanced mode**. Switch to it with the `mode` command: ```rdbms mode advanced ``` If you only need one SQL command without leaving simple mode, prefix it with a colon instead — see [Simple and advanced modes](/getting-started/modes/). ## Join two tables `books` stores an `author_id`, not the author's name. To show the name, join `books` to `authors`, matching each book's `author_id` to the author it points at: ```sql select authors.name, books.title from books join authors on books.author_id = authors.author_id order by authors.name ``` ``` ┌───────────────────┬───────────────────────────┐ │ name │ title │ ├───────────────────┼───────────────────────────┤ │ Italo Calvino │ Invisible Cities │ │ Octavia E. Butler │ Kindred │ │ Ursula K. Le Guin │ A Wizard of Earthsea │ │ Ursula K. Le Guin │ The Left Hand of Darkness │ └───────────────────┴───────────────────────────┘ ``` The `on books.author_id = authors.author_id` clause is the heart of the join: it says *which* rows belong together. Because Le Guin wrote two books, her name appears on two rows — exactly the one-to-many relationship you declared, expanded into result rows. ## Join through the bridge table The real payoff is the many-to-many link. *Who has borrowed which book?* lives in three tables: the borrower in `members`, the book in `books`, and the connection in `loans`. Join all three, hopping through the `loans` bridge: ```sql select members.name, books.title, loans.loaned_on, loans.returned_on from loans join books on loans.book_id = books.book_id join members on loans.member_id = members.member_id order by loans.loaned_on ``` ``` ┌──────────────┬──────────────────────┬────────────┬─────────────┐ │ name │ title │ loaned_on │ returned_on │ ├──────────────┼──────────────────────┼────────────┼─────────────┤ │ Grace Hopper │ A Wizard of Earthsea │ 2024-05-01 │ (null) │ │ Alan Turing │ Invisible Cities │ 2024-05-03 │ 2024-05-20 │ └──────────────┴──────────────────────┴────────────┴─────────────┘ ``` Each loan row pulls in the matching book *and* the matching member, turning three tables of IDs into a readable sentence: who borrowed what, and when. ## Filter a join A `where` clause narrows a join just like it narrows a single table. A loan that has not been returned has an empty `returned_on`, so *which books are currently out?* is: ```sql select members.name, books.title from loans join books on loans.book_id = books.book_id join members on loans.member_id = members.member_id where loans.returned_on is null ``` ``` ┌──────────────┬──────────────────────┐ │ name │ title │ ├──────────────┼──────────────────────┤ │ Grace Hopper │ A Wizard of Earthsea │ └──────────────┴──────────────────────┘ ``` ## Summarise with group by Joins combine with `group by` to count and total. *How many books has each author written?* groups the joined rows by author and counts each group: ```sql select authors.name, count(*) as book_count from books join authors on books.author_id = authors.author_id group by authors.name order by book_count desc ``` ``` ┌───────────────────┬────────────┐ │ name │ book_count │ ├───────────────────┼────────────┤ │ Ursula K. Le Guin │ 2 │ │ Octavia E. Butler │ 1 │ │ Italo Calvino │ 1 │ └───────────────────┴────────────┘ ``` `count(*)` counts the rows in each group; `as book_count` names the result column. Le Guin's two books collapse into a single row with a count of `2`. ## Where to go next - See **how** a query runs — prefix any of these with `explain` to view the plan, and add an [index](/reference/indexes/) to speed up a join. - The full `select` surface — projections, more join forms, set operations, and CTEs — is in [Querying & inspecting](/reference/querying-and-inspecting/). - Switch back to simple mode any time by running `mode simple`.