docs(website): rewrite Build the library + add Querying with joins guide
Build the library: polish and extend from a 2-table draft into the full guided build — all four tables, the authors→books 1:n and the books↔members m:n through the loans bridge (bridge-table concept taught in context), the isbn unique constraint, and captured show-relationships / show-data output. Remove the draft marker; it is now publication-quality. Uses the same sample rows as the Reference pages so output matches across the site. Querying with joins (new): joins built up from two tables → the three-table bridge join → a filtered join → a group-by aggregate, all in advanced mode with real captured output. Verified: pnpm build clean (25 pages); no forbidden terms; internal links resolve. Advanced-mode `mode advanced`/`mode simple` and the unique constraint checked against source.
This commit is contained in:
@@ -1,73 +1,200 @@
|
|||||||
---
|
---
|
||||||
title: Build the library
|
title: Build the library
|
||||||
description: Build the example library database step by step — tables, a relationship, and some rows.
|
description: Build the whole example library — four tables, a one-to-many and a many-to-many relationship, and some rows — step by step.
|
||||||
sidebar:
|
sidebar:
|
||||||
order: 1
|
order: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
:::note[Draft]
|
This guide builds the [example library](/getting-started/example-library/)
|
||||||
This guide is an early draft. The walkthrough is correct, but the wording
|
from scratch in **simple mode**. By the end you will have a complete little
|
||||||
and pacing will be refined for teaching before the docs are published.
|
database — `authors`, `books`, `members`, and `loans` — wired together with
|
||||||
|
both kinds of relationship, and you will have used the whole
|
||||||
|
**create → add columns → relate → insert → query** loop.
|
||||||
|
|
||||||
|
It picks up where [Your first project](/getting-started/first-project/) leaves
|
||||||
|
off. The only new idea is *relationships*, which we introduce as we go.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Type each command into the input field and press <kbd>Enter</kbd>. As you
|
||||||
|
type, the editor completes commands with <kbd>Tab</kbd> and flags mistakes
|
||||||
|
before you run them — see
|
||||||
|
[The assistive editor](/using-the-playground/the-assistive-editor/).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
This guide builds the [example library](/getting-started/example-library/)
|
|
||||||
from scratch in simple mode: two tables, a relationship between them, and a
|
|
||||||
few rows. By the end you will have used the create → add column → relate →
|
|
||||||
insert → query loop end to end.
|
|
||||||
|
|
||||||
We give each table a **named** primary key (like `author_id`) so the
|
|
||||||
relationship reads clearly — but `with pk` on its own gives you a default
|
|
||||||
`id` if you prefer, as in [Your first project](/getting-started/first-project/).
|
|
||||||
|
|
||||||
## 1. Create the authors table
|
## 1. Create the authors table
|
||||||
|
|
||||||
|
We give each table a **named** primary key (like `author_id`) so that
|
||||||
|
relationships read clearly later. `with pk author_id(serial)` makes the key a
|
||||||
|
`serial` — an auto-incrementing number the database fills in for you:
|
||||||
|
|
||||||
```rdbms
|
```rdbms
|
||||||
create table authors with pk author_id(serial)
|
create table authors with pk author_id(serial)
|
||||||
add column to authors: name (text)
|
add column to authors: name (text)
|
||||||
add column to authors: birth_year (int)
|
add column to authors: birth_year (int)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Create the books table
|
## 2. Create the books table and relate it to authors
|
||||||
|
|
||||||
|
Build `books` the same way. The `author_id` column will hold *which* author
|
||||||
|
wrote each book:
|
||||||
|
|
||||||
```rdbms
|
```rdbms
|
||||||
create table books with pk book_id(serial)
|
create table books with pk book_id(serial)
|
||||||
add column to books: title (text)
|
add column to books: title (text)
|
||||||
add column to books: author_id (int)
|
add column to books: author_id (int)
|
||||||
add column to books: published (int)
|
add column to books: published (int)
|
||||||
|
add column to books: isbn (text)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. Relate books to authors
|
No two books should share an ISBN, so mark that column unique — a
|
||||||
|
[constraint](/reference/constraints/) the database will enforce on every
|
||||||
An author has many books, so `books.author_id` should point at
|
insert:
|
||||||
`authors.author_id`. Declare that one-to-many relationship:
|
|
||||||
|
|
||||||
```rdbms
|
```rdbms
|
||||||
add 1:n relationship from authors.author_id to books.author_id
|
add constraint unique to books.isbn
|
||||||
```
|
```
|
||||||
|
|
||||||
The relationship reads parent-to-child: **from** the `authors` side **to**
|
Now the new idea. Every book is written by one author, but an author can write
|
||||||
the `books` side.
|
many books — a **one-to-many** relationship. We declare it so the database
|
||||||
|
keeps the link honest (you can never point a book at an author who does not
|
||||||
## 4. Add some rows
|
exist):
|
||||||
|
|
||||||
`author_id` and `book_id` are `serial`, so they fill themselves in:
|
|
||||||
|
|
||||||
```rdbms
|
```rdbms
|
||||||
insert into authors (name, birth_year) values ('Ada Lovelace', 1815)
|
add 1:n relationship as books_author from authors.author_id to books.author_id on delete cascade
|
||||||
insert into authors (name, birth_year) values ('Alan Turing', 1912)
|
|
||||||
insert into books (title, author_id, published) values ('Notes on the Analytical Engine', 1, 1843)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. Look at your data
|
Read it parent-to-child: **from** the `authors` side (the "one") **to** the
|
||||||
|
`books` side (the "many"). `on delete cascade` says that if an author is ever
|
||||||
|
deleted, their books go too — see [Relationships](/reference/relationships/)
|
||||||
|
for the other options.
|
||||||
|
|
||||||
|
## 3. Create the members table
|
||||||
|
|
||||||
|
Members are the people who borrow books. This table stands alone for now:
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
create table members with pk member_id(serial)
|
||||||
|
add column to members: name (text)
|
||||||
|
add column to members: joined (date)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Create the loans table — the many-to-many bridge
|
||||||
|
|
||||||
|
A book can be borrowed by many members over time, and a member can borrow many
|
||||||
|
books. That is a **many-to-many** relationship, and you do not model it with a
|
||||||
|
single link — you use a third table in the middle. Each row in `loans`
|
||||||
|
represents *one borrowing event*: one book, one member, and when it happened.
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
create table loans with pk loan_id(serial)
|
||||||
|
add column to loans: book_id (int)
|
||||||
|
add column to loans: member_id (int)
|
||||||
|
add column to loans: loaned_on (date)
|
||||||
|
add column to loans: returned_on (date)
|
||||||
|
```
|
||||||
|
|
||||||
|
`loans` is a **bridge table** (also called a junction table). It carries two
|
||||||
|
one-to-many relationships — one to `books`, one to `members` — and together
|
||||||
|
they express the many-to-many link:
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
add 1:n relationship as loans_book from books.book_id to loans.book_id on delete cascade
|
||||||
|
add 1:n relationship as loans_member from members.member_id to loans.member_id on delete cascade
|
||||||
|
```
|
||||||
|
|
||||||
|
You can confirm all three relationships at once:
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
show relationships
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Relationships (3):
|
||||||
|
books_author: authors.author_id → books.author_id on delete cascade
|
||||||
|
loans_book: books.book_id → loans.book_id on delete cascade
|
||||||
|
loans_member: members.member_id → loans.member_id on delete cascade
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Add some rows
|
||||||
|
|
||||||
|
The `serial` keys (`author_id`, `book_id`, …) fill themselves in, so you leave
|
||||||
|
them out of each `insert`. Start with the authors:
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
insert into authors (name, birth_year) values ('Ursula K. Le Guin', 1929)
|
||||||
|
insert into authors (name, birth_year) values ('Italo Calvino', 1923)
|
||||||
|
insert into authors (name, birth_year) values ('Octavia E. Butler', 1947)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then their books. The `author_id` values (`1`, `2`, `3`) are the keys the
|
||||||
|
database just assigned above — Le Guin is `1`, Calvino is `2`, Butler is `3`:
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
insert into books (title, author_id, published, isbn) values ('A Wizard of Earthsea', 1, 1968, '978-0553383041')
|
||||||
|
insert into books (title, author_id, published, isbn) values ('The Left Hand of Darkness', 1, 1969, '978-0441478125')
|
||||||
|
insert into books (title, author_id, published, isbn) values ('Invisible Cities', 2, 1972, '978-0156453806')
|
||||||
|
insert into books (title, author_id, published, isbn) values ('Kindred', 3, 1979, '978-0807083697')
|
||||||
|
```
|
||||||
|
|
||||||
|
A couple of members:
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
insert into members (name, joined) values ('Grace Hopper', '2023-01-15')
|
||||||
|
insert into members (name, joined) values ('Alan Turing', '2023-03-02')
|
||||||
|
```
|
||||||
|
|
||||||
|
And finally two loans, linking a book to a member. Grace Hopper has borrowed
|
||||||
|
book `1` and not yet returned it; Alan Turing borrowed book `3` and returned
|
||||||
|
it. Leave `returned_on` out when the book is still on loan:
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
insert into loans (book_id, member_id, loaned_on) values (1, 1, '2024-05-01')
|
||||||
|
insert into loans (book_id, member_id, loaned_on, returned_on) values (3, 2, '2024-05-03', '2024-05-20')
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Look at your data
|
||||||
|
|
||||||
|
Every table now has rows. `show data` prints them:
|
||||||
|
|
||||||
```rdbms
|
```rdbms
|
||||||
show data authors
|
show data authors
|
||||||
show data books
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
┌───────────┬───────────────────┬────────────┐
|
||||||
|
│ author_id │ name │ birth_year │
|
||||||
|
├───────────┼───────────────────┼────────────┤
|
||||||
|
│ 1 │ Ursula K. Le Guin │ 1929 │
|
||||||
|
│ 2 │ Italo Calvino │ 1923 │
|
||||||
|
│ 3 │ Octavia E. Butler │ 1947 │
|
||||||
|
└───────────┴───────────────────┴────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
```rdbms
|
||||||
|
show data loans
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┬─────────┬───────────┬────────────┬─────────────┐
|
||||||
|
│ loan_id │ book_id │ member_id │ loaned_on │ returned_on │
|
||||||
|
├─────────┼─────────┼───────────┼────────────┼─────────────┤
|
||||||
|
│ 1 │ 1 │ 1 │ 2024-05-01 │ (null) │
|
||||||
|
│ 2 │ 3 │ 2 │ 2024-05-03 │ 2024-05-20 │
|
||||||
|
└─────────┴─────────┴───────────┴────────────┴─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
The empty `returned_on` shows as `(null)` — the loan that is still out.
|
||||||
|
|
||||||
## Where to go next
|
## Where to go next
|
||||||
|
|
||||||
- Add the `members` and `loans` tables the same way to model borrowing — a
|
You now have the complete library. From here:
|
||||||
many-to-many relationship through `loans`.
|
|
||||||
- Try the same steps in advanced mode to see the SQL form of each command.
|
- **Ask questions across tables** — the `loans` bridge only really pays off
|
||||||
- Look up any command in detail in the Reference section.
|
when you query through it. See
|
||||||
|
[Querying with joins](/guides/querying-with-joins/).
|
||||||
|
- **See the relationships drawn out** — `show table books` and
|
||||||
|
`show relationship books_author` render the links as diagrams
|
||||||
|
([Relationships](/reference/relationships/)).
|
||||||
|
- **Try the same build in advanced mode** to see the SQL form of each command
|
||||||
|
([Querying & inspecting](/reference/querying-and-inspecting/),
|
||||||
|
[Tables](/reference/tables/)).
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
---
|
||||||
|
|
||||||
|
[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`.
|
||||||
Reference in New Issue
Block a user