diff --git a/website/src/content/docs/guides/build-the-library.md b/website/src/content/docs/guides/build-the-library.md index f79ea1b..094dec3 100644 --- a/website/src/content/docs/guides/build-the-library.md +++ b/website/src/content/docs/guides/build-the-library.md @@ -1,73 +1,200 @@ --- 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: order: 1 --- -:::note[Draft] -This guide is an early draft. The walkthrough is correct, but the wording -and pacing will be refined for teaching before the docs are published. +This guide builds the [example library](/getting-started/example-library/) +from scratch in **simple mode**. By the end you will have a complete little +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 Enter. As you +type, the editor completes commands with Tab 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 +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 create table authors with pk author_id(serial) add column to authors: name (text) 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 create table books with pk book_id(serial) add column to books: title (text) add column to books: author_id (int) add column to books: published (int) +add column to books: isbn (text) ``` -## 3. Relate books to authors - -An author has many books, so `books.author_id` should point at -`authors.author_id`. Declare that one-to-many relationship: +No two books should share an ISBN, so mark that column unique — a +[constraint](/reference/constraints/) the database will enforce on every +insert: ```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** -the `books` side. - -## 4. Add some rows - -`author_id` and `book_id` are `serial`, so they fill themselves in: +Now the new idea. Every book is written by one author, but an author can write +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 +exist): ```rdbms -insert into authors (name, birth_year) values ('Ada Lovelace', 1815) -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) +add 1:n relationship as books_author from authors.author_id to books.author_id on delete cascade ``` -## 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 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 -- Add the `members` and `loans` tables the same way to model borrowing — a - many-to-many relationship through `loans`. -- Try the same steps in advanced mode to see the SQL form of each command. -- Look up any command in detail in the Reference section. +You now have the complete library. From here: + +- **Ask questions across tables** — the `loans` bridge only really pays off + 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/)). diff --git a/website/src/content/docs/guides/querying-with-joins.md b/website/src/content/docs/guides/querying-with-joins.md new file mode 100644 index 0000000..e99a137 --- /dev/null +++ b/website/src/content/docs/guides/querying-with-joins.md @@ -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`.