mirror of
				https://github.com/fastapi/sqlmodel.git
				synced 2025-10-31 10:06:57 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			261 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 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. 🚀
 | 
