sqlmodel/docs/tutorial/connect/create-connected-tables.md
Evan Grim 2013c69c4d
✏ Fix multiple typos and some rewording (#22)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2021-12-14 18:17:10 +01:00

261 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Create Connected Tables
Now we will deal with **connected** data put in different tables.
So, the first step is to create more than one table and connect them, so that each row in one table can reference another row in the other table.
We have been working with heroes in a single table `hero`. Let's now add a table `team`.
The team table will look like this:
<table>
<tr>
<th>id</th><th>name</th><th>headquarters</th>
</tr>
<tr>
<td>1</td><td>Preventers</td><td>Sharp Tower</td>
</tr>
<tr>
<td>2</td><td>Z-Force</td><td>Sister Margarets Bar</td>
</tr>
</table>
To connect them, we will add another column to the hero table to point to each team by the ID with the `team_id`:
<table>
<tr>
<th>id</th><th>name</th><th>secret_name</th><th>age</th><th>team_id ✨</th>
</tr>
<tr>
<td>1</td><td>Deadpond</td><td>Dive Wilson</td><td>null</td><td>2 ✨</td>
</tr>
<tr>
<td>2</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>1 ✨</td>
</tr>
<tr>
<td>3</td><td>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>1 ✨</td>
</tr>
</table>
This way each row in the table `hero` can point to a row in the table `team`:
<img alt="table relationships" src="/img/databases/relationships.svg">
## One-to-Many and Many-to-One
Here we are creating connected data in a relationship where **one** team could have **many** heroes. So it is commonly called a **one-to-many** or **many-to-one** relationship.
The **many-to-one** part can be seen if we start from the heroes, **many** heroes could be part of **one** team.
This is probably the most popular type of relationship, so we'll start with that. But there's also **many-to-many** and **one-to-one** relationships.
## Create Tables in Code
### Create the `team` Table
Let's start by creating the tables in code.
Import the things we need from `sqlmodel` and create a new `Team` model:
```Python hl_lines="6-9"
{!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:1-9]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
```
</details>
This is very similar to what we have been doing with the `Hero` model.
The `Team` model will be in a table automatically named `"team"`, and it will have the columns:
* `id`, the primary key, automatically generated by the database
* `name`, the name of the team
* `headquarters`, the headquarters of the team
And finally we mark it as a table in the config.
### Create the New `hero` Table
Now let's create the `hero` table.
This is the same model we have been using up to now, we are just adding the new column `team_id`:
```Python hl_lines="18"
{!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:1-18]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
```
</details>
Most of that should look familiar:
The column will be named `team_id`. It will be an integer, and it could be `NULL` in the database (or `None` in Python), becase there could be some heroes that don't belong to any team.
We add a default of `None` to the `Field()` so we don't have to explicitly pass `team_id=None` when creating a hero.
Now, here's the new part:
In `Field()` we pass the argument `foreign_key="team.id"`. This tells the database that this column `team_id` is a foreign key to the table `team`. A "**foreign key**" just means that this column will have the **key** to identify a row in a **foreign** table.
The value in this column `team_id` will be the same integer that is in some row in the `id` column on the `team` table. That is what connects the two tables.
#### The Value of `foreign_key`
Notice that the `foreign_key` is a string.
Inside it has the name of the **table**, then a dot, and then the name of the **column**.
This is the name of the **table** in the database, so it is `"team"`, not the name of the **model** class `Team` (with a capital `T`).
If you had a custom table name, you would use that custom table name.
!!! info
You can learn about setting a custom table name for a model in the Advanced User Guide.
### Create the Tables
Now we can add the same code as before to create the engine and the function to create the tables:
```Python hl_lines="3-4 6 9-10"
# Code above omitted 👆
{!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:21-28]!}
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
```
</details>
And as before, we'll call this function from another function `main()`, and we'll add that function `main()` to the main block of the file:
```Python hl_lines="3-4 7-8"
# Code above omitted 👆
{!./docs_src/tutorial/connect/create_tables/tutorial001.py[ln:31-36]!}
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
```
</details>
## Run the Code
!!! tip
Before running the code, make sure you delete the file `database.db` to make sure you start from scratch.
If we run the code we have up to now, it will go and create the database file `database.db` and the tables in it we just defined, `team` and `hero`:
<div class="termy">
```console
$ python app.py
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Check if the tables exist already
INFO Engine PRAGMA main.table_info("team")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("team")
INFO Engine [raw sql] ()
INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
// Create the tables
INFO Engine
CREATE TABLE team (
id INTEGER,
name VARCHAR NOT NULL,
headquarters VARCHAR NOT NULL,
PRIMARY KEY (id)
)
INFO Engine [no key 0.00010s] ()
INFO Engine
CREATE TABLE hero (
id INTEGER,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(team_id) REFERENCES team (id)
)
INFO Engine [no key 0.00026s] ()
INFO Engine COMMIT
```
</div>
## Create Tables in SQL
Let's see that same generated SQL code.
As we saw before, those `VARCHAR` columns are converted to `TEXT` in SQLite, which is the database we are using for these experiments.
So, the first SQL could also be written as:
```SQL
CREATE TABLE team (
id INTEGER,
name TEXT NOT NULL,
headquarters TEXT NOT NULL,
PRIMARY KEY (id)
)
```
And the second table could be written as:
```SQL hl_lines="8"
CREATE TABLE hero (
id INTEGER,
name TEXT NOT NULL,
secret_name TEXT NOT NULL,
age INTEGER,
team_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(team_id) REFERENCES team (id)
)
```
The only new is the `FOREIGN KEY` line, and as you can see, it tells the database what column in this table is a foreign key (`team_id`), which other (foreign) table it references (`team`) and which column in that table is the key to define which row to connect (`id`).
Feel free to experiment with it in **DB Browser for SQLite**.
## Recap
Using **SQLModel**, in most of the cases you only need a field (column) with a `foreign_key` in the `Field()` with a string pointing to another table and column to connect two tables.
Now that we have the tables created and connected, let's create some rows in the next chapter. 🚀