📝 Add docs

This commit is contained in:
Sebastián Ramírez
2021-08-24 15:02:48 +02:00
parent 29017c60a7
commit 6d1d86ab85
51 changed files with 12260 additions and 0 deletions

View File

@@ -0,0 +1,290 @@
# Create and Connect Rows
We will now **create rows** for each table. ✨
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>
And after we finish working with the data in this chapter, the `hero` table will look like this:
<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>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>1</td>
</tr>
<tr>
<td>3</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>null</td>
</tr>
</table>
Each row in the table `hero` will point to a row in the table `team`:
<img alt="table relationships" src="/img/tutorial/relationships/select/relationships2.svg">
!!! info
We will later update **Spider-Boy** to add him to the **Preventers** team too, but not yet.
We will continue with the code in the previous example and we will add more things to it.
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/create_tables/tutorial001.py!}
```
</details>
Make sure you remove the `database.db` file before running the examples to get the same results.
## Create Rows for Teams with **SQLModel**
Let's do the same we did before and define a `create_heroes()` function where we create our heroes.
And now we will also create the teams there. 🎉
Let's start by creating two teams:
```Python hl_lines="3-9"
# Code above omitted 👆
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-37]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
This would hopefully look already familiar.
We start a **session** in a `with` block using the same **engine** we created above.
Then we create two instances of the model class (in this case `Team`).
Next we add those objects to the **session**.
And finally we **commit** the session to save the changes to the database.
## Add It to Main
Let's not forget to add this function `create_heroes()` to the `main()` function so that we run it when calling the program from the command line:
```Python hl_lines="5"
# Code above omitted 👆
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:63-65]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
## Run it
If we run that code we have up to now, it will output:
<div class="termy">
```console
$ python app.py
// Previous output omitted 😉
// Automatically start a transaction
INFO Engine BEGIN (implicit)
// Add the teams to the database
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
INFO Engine [generated in 0.00050s] ('Preventers', 'Sharp Tower')
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
INFO Engine [cached since 0.002324s ago] ('Z-Force', 'Sister Margarets Bar')
INFO Engine COMMIT
```
</div>
You can see in the output that it uses common SQL `INSERT` statements to create the rows.
## Create Rows for Heroes in Code
Now let's create one hero object to start.
As the `Hero` class model now has a field (column, attribute) `team_id`, we can set it by using the ID field from the `Team` objects we just created before:
```Python hl_lines="12"
# Code above omitted 👆
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-41]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
We haven't committed this hero to the database yet, but there are already a couple of things to pay **attention** to.
If the database already had some teams, we wouldn't even know **what is the ID** that is going to be automatically assigned to each team by the database, for example, we couldn't just guess `1` or `2`.
But once the team is created and committed to the database, we can access the object's `id` field to get that ID.
Accessing an attribute in a model that was just committed, for example with `team_z_force.id`, automatically **triggers a refresh** of the data from the DB in the object, and then exposes the value for that field.
So, even though we are not committing this hero yet, just because we are using `team_z_force.id`, that will trigger some SQL sent to the database to fetch the data for this team.
That line alone would generate an output of:
```
INFO Engine BEGIN (implicit)
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [generated in 0.00025s] (2,)
```
Let's now create two more heroes:
```Python hl_lines="14-20"
# Code above omitted 👆
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-52]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
When creating `hero_rusty_man`, we are accessing `team_preventers.id`, so that will also trigger a refresh of its data, generating an output of:
```
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team
WHERE team.id = ?
INFO Engine [cached since 0.001795s ago] (1,)
```
There's something else to note. We marked `team_id` as `Optional[int]`, meaning that this could be `NULL` on the database (and `None` in Python).
That means that a hero doesn't have to have a team. And in this case, **Spider-Boy** doesn't have one.
Next we just commit the changes to save them to the database, and that will generate the output:
```
INFO Engine INSERT INTO hero (name, secret_name, age, team_id) VALUES (?, ?, ?, ?)
INFO Engine [generated in 0.00022s] ('Deadpond', 'Dive Wilson', None, 2)
INFO Engine INSERT INTO hero (name, secret_name, age, team_id) VALUES (?, ?, ?, ?)
INFO Engine [cached since 0.0007987s ago] ('Rusty-Man', 'Tommy Sharp', 48, 1)
INFO Engine INSERT INTO hero (name, secret_name, age, team_id) VALUES (?, ?, ?, ?)
INFO Engine [cached since 0.001095s ago] ('Spider-Boy', 'Pedro Parqueador', None, None)
INFO Engine COMMIT
```
## Refresh and Print Heroes
Now let's refresh and print those new heroes to see their new ID pointing to their teams:
```Python hl_lines="26-28 30-32"
# Code above omitted 👆
{!./docs_src/tutorial/connect/insert/tutorial001.py[ln:31-60]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
If we execute that in the command line, it will output:
<div class="termy">
```console
$ python app.py
// Previous output omitted 😉
// Automatically start a transaction
INFO Engine BEGIN (implicit)
// Refresh the first hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00021s] (1,)
// Refresh the second hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001575s ago] (2,)
// Refresh the third hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.002518s ago] (3,)
// Print the heroes
Created hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None
Created hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48
Created hero: id=3 secret_name='Pedro Parqueador' team_id=None name='Spider-Boy' age=None
```
</div>
They now have their `team_id`s, nice!
## Relationships
Relationships in SQL databases are just made by having **columns in one table** referencing the values in **columns on other tables**.
And here we have treated them just like that, more **column fields**, which is what they actually are behind the scenes in the SQL database.
But later in this tutorial, in the next group of chapters, you will learn about **Relationship Attributes** to make it all a lot easier to work with in code. ✨

View File

@@ -0,0 +1,260 @@
# 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.
As we don't have to explicitly pass `team_id=None` when creating a hero, we add a default of `None` to the `Field()`.
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. 🚀

View File

@@ -0,0 +1,12 @@
# Connect Tables - JOIN - Intro
By this point, you already know how to perform the main <abbr title="Create, read, update, delete.">CRUD</abbr> operations with **SQLModel** using a single table. 🎉
But the main advantage and feature of SQL databases is being able to handle related data, to **connect** or **"join"** different tables together. Connecting rows in one table to rows in another.
Let's see how to use **SQLModel** to manage connected data in the next chapters. 🤝
!!! tip
We will extend this further in the next group of chapters making it even more convenient to work with in Python code, using **relationship attributes**.
But you should start in this group of chapters first. 🤓

View File

@@ -0,0 +1,586 @@
# Read Connected Data
Now that we have some data in both tables, let's select the data that is connected together.
The `team` table has this data:
<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>
And the `hero` table has this data:
<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>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>1</td>
</tr>
<tr>
<td>3</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>null</td>
</tr>
</table>
We will continue with the code in the previous example and we will add more things to it.
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
## `SELECT` Connected Data with SQL
Let's start seeing how SQL works when selecting connected data. This is where SQL databases actually shine.
If you don't have a `database.db` file, run that previous program we had written (or copy it from the preview above) to create it.
Now open **DB Browser for SQLite** and open the `database.db` file.
To `SELECT` connected data we use the same keywords we have used before, but now we combine the two tables.
Let's get each hero with the `id`, `name`, and the team `name`:
```SQL
SELECT hero.id, hero.name, team.name
FROM hero, team
WHERE hero.team_id = team.id
```
!!! info
Because we have two columns called `name`, one for `hero` and one for `team`, we can specify them with the prefix of the table name and the dot to make it explicit what we refer to.
Notice that now in the `WHERE` part we are not comparing one column with a literal value (like `hero.name = "Deadpond"`), but we are comparing two columns.
It means, more or less:
> Hey SQL database 👋, please go and `SELECT` some data for me.
>
> I'll first tell you the columns I want:
>
> * `id` of the `hero` table
> * `name` of the `hero` table
> * `name` of the `team` table
>
> I want you to get that data `FROM` the tables `hero` and `team`.
>
> And I don't want you to combine each hero with each possible team. Instead, for each hero, go and check each possible team, but give me only the ones `WHERE` the `hero.team_id` is the same as the `team.id`.
If we execute that SQL, it will return the table:
<table>
<tr>
<th>id</th><th>name</th><th>name</th>
</tr>
<tr>
<td>1</td><td>Deadpond</td><td>Z-Force</td>
</tr>
<tr>
<td>2</td><td>Rusty-Man</td><td>Preventers</td>
</tr>
</table>
You can go ahead and try it in **DB Browser for SQLite**:
<img class="shadow" src="/img/tutorial/relationships/select/image01.png">
!!! note
Wait, what about Spider-Boy? 😱
He doesn't have a team, so his `team_id` is `NULL` in the database. And this SQL is comparing that `NULL` from the `team_id` with all the `id` fields in the rows in the `team` table.
As there's no team with an ID of `NULL`, it doesn't find a match.
But we'll see how to fix that later with a `LEFT JOIN`.
## Select Related Data with **SQLModel**
Now let's use SQLModel to do the same select.
We'll create a function `select_heroes()` just as we did before, but now we'll work with two tables.
Remember SQLModel's `select()` function? It can take more than one argument.
So, we can pass the `Hero` and `Team` model classes. And we can also use both their columns in the `.where()` part:
```Python hl_lines="5"
# Code above omitted 👆
{!./docs_src/tutorial/connect/select/tutorial001.py[ln:63-65]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/select/tutorial001.py!}
```
</details>
Notice that in the comparison with `==` we are using the class attributes for both `Hero.team_id` and `Team.id`.
That will generate the appropriate **expression** object that will be converted to the right SQL, equivalent to the SQL example we saw above.
Now we can execute it and get the `results` object.
And as we used `select` with two models, we will receive tuples of instances of those two models, so we can iterate over them naturally in a `for` loop:
```Python hl_lines="7"
# Code above omitted 👆
{!./docs_src/tutorial/connect/select/tutorial001.py[ln:63-68]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/select/tutorial001.py!}
```
</details>
For each iteration in the `for` loop we get a a tuple with an instance of the class `Hero` and an instance of the class `Team`.
And in this `for` loop we assign them to the variable `hero` and the variable `team`.
!!! info
There was a lot of research, design, and work behind **SQLModel** to make this provide the best possible developer experience.
And you should get autocompletion and inline errors in your editor for both `hero` and `team`. 🎉
## Add It to Main
As always, we must remember to add this new `select_heroes()` function to the `main()` function to make sure it is executed when we call this program from the command line.
```Python hl_lines="6"
# Code above omitted 👆
{!./docs_src/tutorial/connect/select/tutorial001.py[ln:71-74]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/select/tutorial001.py!}
```
</details>
## Run the Program
Now we can run the program and see how it shows us each hero with their corresponding team:
<div class="termy">
```console
$ python app.py
// Previous output omitted 😉
// Get the heroes with their teams
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero, team
WHERE hero.team_id = team.id
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine [no key 0.00015s] ()
// Print the first hero and team
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margarets Bar' id=2 name='Z-Force'
// Print the second hero and team
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
2021-08-09 08:55:50,682 INFO sqlalchemy.engine.Engine ROLLBACK
```
</div>
## `JOIN` Tables with SQL
There's an alternative syntax for that SQL query from above using the keyword `JOIN` instead of `WHERE`.
This is the same version from above, using `WHERE`:
```SQL
SELECT hero.id, hero.name, team.name
FROM hero, team
WHERE hero.team_id = team.id
```
And this is the alternative version using `JOIN`:
```SQL
SELECT hero.id, hero.name, team.name
FROM hero
JOIN team
ON hero.team_id = team.id
```
Both are equivalent. The differences in the SQL code are that instead of passing the `team` to the `FROM` part (also called `FROM` clause) we add a `JOIN` and put the `team` table there.
And then, instead of putting a `WHERE` with a condition, we put an `ON` keyword with the condition, because `ON` is the one that comes with `JOIN`. 🤷
So, this second version means, more or less:
> Hey SQL database 👋, please go and `SELECT` some data for me.
>
> I'll first tell you the columns I want:
>
> * `id` of the `hero` table
> * `name` of the `hero` table
> * `name` of the `team` table
>
> ...up to here it's the same as before, LOL.
>
> Now, I want you to get that data starting `FROM` the table `hero`.
>
> And to get the rest of the data, I want you to `JOIN` it with the table `team`.
>
> And I want you to join those two tables `ON` the combinations of rows that have the `hero.team_id` with the same value as the `team.id`.
>
> Did I say all this before already? I feel like I'm just repeating myself. 🤔
That will return the same table as before:
<table>
<tr>
<th>id</th><th>name</th><th>name</th>
</tr>
<tr>
<td>1</td><td>Deadpond</td><td>Z-Force</td>
</tr>
<tr>
<td>2</td><td>Rusty-Man</td><td>Preventers</td>
</tr>
</table>
Also in **DB Browser for SQLite**:
<img class="shadow" src="/img/tutorial/relationships/select/image02.png">
!!! tip
Why bother with all this if the result is the same?
This `JOIN` will be useful in a bit to be able to also get Spider-Boy, even if he doesn't have a team.
## Join Tables in **SQLModel**
The same way there's a `.where()` available when using `select()`, there's also a `.join()`.
And in SQLModel (actually SQLAlchemy), when using the `.join()`, because we already declared what is the `foreign_key` when creating the models, we don't have to pass an `ON` part, it is inferred automatically:
```Python hl_lines="5"
# Code above omitted 👆
{!./docs_src/tutorial/connect/select/tutorial002.py[ln:63-68]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/select/tutorial002.py!}
```
</details>
Also notice that we are still including `Team` in the `select(Hero, Team)`, because we still want to access that data.
This is equivalent to the previous example.
And if we run it in the command line, it will output:
<div class="termy">
```console
$ python app.py
// Previous output omitted 😉
// Select using a JOIN with automatic ON
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero JOIN team ON team.id = hero.team_id
INFO Engine [no key 0.00032s] ()
// Print the first hero and team
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margarets Bar' id=2 name='Z-Force'
// Print the second hero and team
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
```
</div>
## `JOIN` Tables with SQL and `LEFT OUTER` (Maybe `JOIN`)
When working with a `JOIN`, you can imagine that you start with a table on the `FROM` part and put that table in an imaginary space on the **left** side.
And then you want another table to `JOIN` the result.
And you put that second table in the **right** side on that imaginary space.
And then you tell the database `ON` which condition it should join those two tables and give you the results back.
But by default, only the rows from both left and right that match the condition will be returned.
<img alt="table relationships" src="/img/databases/relationships.svg">
In this example of tables above 👆, it would return all the heroes, because every hero has a `team_id`, so every hero can be joined with the `team` table:
<table>
<tr>
<th>id</th><th>name</th><th>name</th>
</tr>
<tr>
<td>1</td><td>Deadpond</td><td>Z-Force</td>
</tr>
<tr>
<td>2</td><td>Rusty-Man</td><td>Preventers</td>
</tr>
<tr>
<td>3</td><td>Spider-Boy</td><td>Preventers</td>
</tr>
</table>
### Foreign Keys with `NULL`
But in the database that we are working with in the code above, **Spider-Boy** doesn't have any team, the value of `team_id` is `NULL` in the database.
So there's no way to join the **Spider-Boy** row with some row in the `team` table:
<img alt="table relationships" src="/img/tutorial/relationships/select/relationships2.svg">
Running the same SQL we used above, the resulting table would not include **Spider-Boy** 😱:
<table>
<tr>
<th>id</th><th>name</th><th>name</th>
</tr>
<tr>
<td>1</td><td>Deadpond</td><td>Z-Force</td>
</tr>
<tr>
<td>2</td><td>Rusty-Man</td><td>Preventers</td>
</tr>
</table>
### Include Everything on the `LEFT OUTER`
In this case, that we want to include all heroes in the result even if they don't have a team, we can extend that same SQL using a `JOIN` from above and add a `LEFT OUTER` right before `JOIN`:
```SQL hl_lines="3"
SELECT hero.id, hero.name, team.name
FROM hero
LEFT OUTER JOIN team
ON hero.team_id = team.id
```
This `LEFT OUTER` part tells the database that we want to keep everything on the first table, the one on the `LEFT` in the imaginary space, even if those rows would be left **out**, so we want it to include the `OUTER` rows too. In this case, every hero with or without a team.
And that would return the following result, including **Spider-Boy** 🎉:
<table>
<tr>
<th>id</th><th>name</th><th>name</th>
</tr>
<tr>
<td>1</td><td>Deadpond</td><td>Z-Force</td>
</tr>
<tr>
<td>2</td><td>Rusty-Man</td><td>Preventers</td>
</tr>
<tr>
<td>3</td><td>Spider-Boy</td><td>null</td>
</tr>
</table>
!!! tip
The only difference between this query and the previous is that extra `LEFT OUTER`.
And here's another of the SQL variations, you could write `LEFT OUTER JOIN` or just `LEFT JOIN`, it means the same.
## Join Tables in **SQLModel** with `LEFT OUTER`
Now let's replicate the same query in **SQLModel**.
`.join()` has a parameter we can use `isouter=True` to make the `JOIN` be a `LEFT OUTER JOIN`:
```Python hl_lines="5"
# Code above omitted 👆
{!./docs_src/tutorial/connect/select/tutorial003.py[ln:63-68]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/select/tutorial003.py!}
```
</details>
And if we run it, it will output:
<div class="termy">
```console
$ python app.py
// Previous output omitted 😉
// SELECT using LEFT OUTER JOIN
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
FROM hero LEFT OUTER JOIN team ON team.id = hero.team_id
INFO Engine [no key 0.00051s] ()
// Print the first hero and team
Hero: id=1 secret_name='Dive Wilson' team_id=2 name='Deadpond' age=None Team: headquarters='Sister Margarets Bar' id=2 name='Z-Force'
// Print the second hero and team
Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
// Print the third hero and team, we included Spider-Boy 🎉
Hero: id=3 secret_name='Pedro Parqueador' team_id=None name='Spider-Boy' age=None Team: None
```
</div>
## What Goes in `select()`
You might be wondering why we put the `Team` in the `select()` and not just in the `.join()`.
And then why we didn't include `Hero` in the `.join()`. 🤔
In SQLModel (actually in SQLAlchemy), all these functions and tools try to **replicate** how it would be to work with the **SQL** language.
Remember that [`SELECT` defines the columns to get and `WHERE` how to filter them?](../where.md#select-and-where){.internal-link target=_blank}.
This also applies here, but with `JOIN` and `ON`.
### Select Only Heroes But Join with Teams
If we only put the `Team` in the `.join()` and not in the `select()` function, we would not get the `team` data.
But we would still be able to **filter** the rows with it. 🤓
We could even add some additional `.where()` after `.join()` to filter the data more, for example to return only the heroes from one team:
```Python hl_lines="5"
# Code above omitted 👆
{!./docs_src/tutorial/connect/select/tutorial004.py[ln:63-68]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/select/tutorial004.py!}
```
</details>
Here we are **filtering** with `.where()` to get only the heroes that belong to the **Preventers** team.
But we are still only requesting the data from the heroes, not their teams.
If we run that, it would output:
<div class="termy">
```console
$ python app.py
// Select only the hero data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
// But still join with the team table
FROM hero JOIN team ON team.id = hero.team_id
// And filter with WHERE to get only the Preventers
WHERE team.name = ?
INFO Engine [no key 0.00066s] ('Preventers',)
// We filter with the team, but only get the hero
Preventer Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48
```
</div>
### Include the `Team`
By putting the `Team` in `select()` we tell **SQLModel** and the database that we want the team data too.
```Python hl_lines="5"
# Code above omitted 👆
{!./docs_src/tutorial/connect/select/tutorial005.py[ln:63-68]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/select/tutorial005.py!}
```
</details>
And if we run that, it will output:
<div class="termy">
```console
$ python app.py
// Select the hero and the team data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id, team.id AS id_1, team.name AS name_1, team.headquarters
// Join the hero with the team table
FROM hero JOIN team ON team.id = hero.team_id
// Filter with WHERE to get only Preventers
WHERE team.name = ?
INFO Engine [no key 0.00018s] ('Preventers',)
// Print the hero and the team
Preventer Hero: id=2 secret_name='Tommy Sharp' team_id=1 name='Rusty-Man' age=48 Team: headquarters='Sharp Tower' id=1 name='Preventers'
```
</div>
We still have to `.join()` because otherwise it would just compute all the possible combinations of heroes and teams, for example including **Rusty-Man** with **Preventers** and also **Rusty-Man** with **Z-Force**, which would be a mistake.
## Relationship Attributes
Here we have been using the pure class models directly, but in a future chapter we will also see how to use **Relationship Attributes** that let us interact with the database in a way much more close to the code with Python objects.
And we will also see how to load their data in a different, simpler way, achieving the same we achieved here. ✨

View File

@@ -0,0 +1,108 @@
# Remove Data Connections
We currently have a `team` table:
<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>
And a `hero` table:
<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>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>1</td>
</tr>
<tr>
<td>3</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>1</td>
</tr>
</table>
Let's see how to **remove** connections between rows in tables.
We will continue with the code from the previous chapter.
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/update/tutorial001.py!}
```
</details>
## Break a Connection
We don't really have to delete anyting to break a connection. We can just assign `None` to the foreign key, in this case, to the `team_id`.
Let's say **Spider-Boy** is tired of the lack of friendly neighbors and wants to get out of the **Preventers**.
We can simply set the `team_id` to `None`, and now it doesn't have a connection with the team:
```Python hl_lines="8"
# Code above omitted 👆
{!./docs_src/tutorial/connect/delete/tutorial001.py[ln:31-32]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/connect/delete/tutorial001.py[ln:68-72]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/delete/tutorial001.py!}
```
</details>
Again, we just **assign** a value to that field attribute `team_id`, now the value is `None`, which means `NULL` in the database. Then we `add()` the hero to the session, and then `commit()`.
Next we `refresh()` it to get the recent data, and we print it.
Running that in the command line will output:
<div class="termy">
```console
$ python app.py
// Previous output omitted 😉
// Update the hero
INFO Engine UPDATE hero SET team_id=? WHERE hero.id = ?
INFO Engine [cached since 0.07753s ago] (None, 3)
// Commit the session
INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Refresh the hero
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.1661s ago] (3,)
// Print the hero without a team
No longer Preventer: id=3 secret_name='Pedro Parqueador' team_id=None name='Spider-Boy' age=None
```
</div>
That's it, we now removed a connection between rows in different tables by unsetting the foreign key column. 💥

View File

@@ -0,0 +1,110 @@
# Update Data Connections
At this point we have a `team` table:
<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>
And a `hero` table:
<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>Rusty-Man</td><td>Tommy Sharp</td><td>48</td><td>1</td>
</tr>
<tr>
<td>3</td><td>Spider-Boy</td><td>Pedro Parqueador</td><td>null</td><td>null</td>
</tr>
</table>
Some of these heroes are part of a team.
Now we'll see how to **update** those connections between rows tables.
We will continue with the code we used to create some heroes, and we'll update them.
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/insert/tutorial001.py!}
```
</details>
## Assign a Team to a Hero
Let's say that **Tommy Sharp** uses his "rich uncle" charms to recruit **Spider-Boy** to join the team of the **Preventers**, now we need to update our Spider-Boy hero object to connect it to the Preventers team.
Doing it is just like updating any other field:
```Python hl_lines="8"
# Code above omitted 👆
{!./docs_src/tutorial/connect/update/tutorial001.py[ln:31-32]!}
# Previous code here omitted 👈
{!./docs_src/tutorial/connect/update/tutorial001.py[ln:62-66]!}
# Code below omitted 👇
```
<details>
<summary>👀 Full file preview</summary>
```Python
{!./docs_src/tutorial/connect/update/tutorial001.py!}
```
</details>
We can simply **assign** a value to that field attribute `team_id`, then `add()` the hero to the session, and then `commit()`.
Next we `refresh()` it to get the recent data, and we print it.
Running that in the command line will output:
<div class="termy">
```console
$ python app.py
// Previous output omitted 😉
// Update the hero
INFO Engine UPDATE hero SET team_id=? WHERE hero.id = ?
INFO Engine [generated in 0.00014s] (1, 3)
// Commit the session saving the changes
INFO Engine COMMIT
// Automatically start a new transaction
INFO Engine BEGIN (implicit)
// Refresh the hero data
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age, hero.team_id
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.08837s ago] (3,)
// Print the updated hero
Updated hero: id=3 secret_name='Pedro Parqueador' team_id=1 name='Spider-Boy' age=None
```
</div>
And now **Spider-Boy** has the `team_id=1`, which is the ID of the Preventers. 🎉
Let's now see how to remove connections in the next chapter. 💥