📝 Add docs
This commit is contained in:
290
docs/tutorial/connect/create-connected-rows.md
Normal file
290
docs/tutorial/connect/create-connected-rows.md
Normal 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 Margaret’s 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 Margaret’s 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. ✨
|
||||
260
docs/tutorial/connect/create-connected-tables.md
Normal file
260
docs/tutorial/connect/create-connected-tables.md
Normal 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 Margaret’s 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. 🚀
|
||||
12
docs/tutorial/connect/index.md
Normal file
12
docs/tutorial/connect/index.md
Normal 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. 🤓
|
||||
586
docs/tutorial/connect/read-connected-data.md
Normal file
586
docs/tutorial/connect/read-connected-data.md
Normal 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 Margaret’s 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 Margaret’s 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 Margaret’s 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 Margaret’s 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. ✨
|
||||
108
docs/tutorial/connect/remove-data-connections.md
Normal file
108
docs/tutorial/connect/remove-data-connections.md
Normal 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 Margaret’s 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. 💥
|
||||
110
docs/tutorial/connect/update-data-connections.md
Normal file
110
docs/tutorial/connect/update-data-connections.md
Normal 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 Margaret’s 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. 💥
|
||||
Reference in New Issue
Block a user