mirror of
				https://github.com/fastapi/sqlmodel.git
				synced 2025-11-01 02:43:22 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			252 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Update and Remove Many-to-Many Relationships
 | ||
| 
 | ||
| Now we'll see how to update and remove these **many-to-many** relationships.
 | ||
| 
 | ||
| We'll continue from where we left off with the previous code.
 | ||
| 
 | ||
| <details>
 | ||
| <summary>👀 Full file preview</summary>
 | ||
| 
 | ||
| ```Python
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial001.py!}
 | ||
| ```
 | ||
| 
 | ||
| </details>
 | ||
| 
 | ||
| ## Get Data to Update
 | ||
| 
 | ||
| Let's now create a function `update_heroes()`.
 | ||
| 
 | ||
| We'll get **Spider-Boy** and the **Z-Force** team.
 | ||
| 
 | ||
| As you already know how these goes, I'll use the **short version** and get the data in a single Python statement.
 | ||
| 
 | ||
| And because we are now using `select()`, we also have to import it.
 | ||
| 
 | ||
| ```Python hl_lines="3  7-12"
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py[ln:1-3]!}
 | ||
| 
 | ||
| # Some code here omitted 👈
 | ||
| 
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-83]!}
 | ||
| 
 | ||
| # Code below omitted 👇
 | ||
| ```
 | ||
| 
 | ||
| <details>
 | ||
| <summary>👀 Full file preview</summary>
 | ||
| 
 | ||
| ```Python
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py!}
 | ||
| ```
 | ||
| 
 | ||
| </details>
 | ||
| 
 | ||
| And of course, we have to add `update_heroes()` to our `main()` function:
 | ||
| 
 | ||
| ```Python hl_lines="6"
 | ||
| # Code above omitted 👆
 | ||
| 
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py[ln:100-107]!}
 | ||
| ```
 | ||
| 
 | ||
| <details>
 | ||
| <summary>👀 Full file preview</summary>
 | ||
| 
 | ||
| ```Python
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py!}
 | ||
| ```
 | ||
| 
 | ||
| </details>
 | ||
| 
 | ||
| ## Add Many-to-Many Relationships
 | ||
| 
 | ||
| Now let's imagine that **Spider-Boy** thinks that the **Z-Force** team is super cool and decides to go there and join them.
 | ||
| 
 | ||
| We can use the same **relationship attributes** to include `hero_spider_boy` in the `team_z_force.heroes`.
 | ||
| 
 | ||
| ```Python hl_lines="10-12  14-15"
 | ||
| # Code above omitted 👆
 | ||
| 
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-90]!}
 | ||
| 
 | ||
| # Code below omitted 👇
 | ||
| ```
 | ||
| 
 | ||
| <details>
 | ||
| <summary>👀 Full file preview</summary>
 | ||
| 
 | ||
| ```Python
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py!}
 | ||
| ```
 | ||
| 
 | ||
| </details>
 | ||
| 
 | ||
| !!! tip
 | ||
|     Because we are accessing an attribute in the models right after we commit, with `hero_spider_boy.teams` and `team_z_force.heroes`, the data is refreshed automatically.
 | ||
| 
 | ||
|     So we don't have to call `session.refresh()`.
 | ||
| 
 | ||
| We then commit the change, refresh, and print the updated **Spider-Boy**'s heroes to confirm.
 | ||
| 
 | ||
| Notice that we only `add` **Z-Force** to the session, then we commit.
 | ||
| 
 | ||
| We never add **Spider-Boy** to the session, and we never even refresh it. But we still print his teams.
 | ||
| 
 | ||
| This still works correctly because we are using `back_populates` in the `Relationship()` in the models. That way, **SQLModel** (actually SQLAlchemy) can keep track of the changes and updates, and make sure they also happen on the relationships in the other related models. 🎉
 | ||
| 
 | ||
| ## Run the Program
 | ||
| 
 | ||
| You can confirm it's all working by running the program in the command line:
 | ||
| 
 | ||
| <div class="termy">
 | ||
| 
 | ||
| ```console
 | ||
| $ python app.py
 | ||
| 
 | ||
| // Previous output omitted 🙈
 | ||
| 
 | ||
| // Create the new many-to-many relationship
 | ||
| INFO Engine INSERT INTO heroteamlink (team_id, hero_id) VALUES (?, ?)
 | ||
| INFO Engine [generated in 0.00020s] (1, 3)
 | ||
| INFO Engine COMMIT
 | ||
| 
 | ||
| // Start a new automatic transaction
 | ||
| INFO Engine BEGIN (implicit)
 | ||
| 
 | ||
| // Automatically refresh the data while accessing the attribute .teams
 | ||
| INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age 
 | ||
| FROM hero 
 | ||
| WHERE hero.id = ?
 | ||
| INFO Engine [generated in 0.00044s] (3,)
 | ||
| INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters 
 | ||
| FROM team, heroteamlink 
 | ||
| WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
 | ||
| INFO Engine [cached since 0.1648s ago] (3,)
 | ||
| 
 | ||
| // Print Spider-Boy teams, including Z-Force 🎉
 | ||
| Updated Spider-Boy's Teams: [
 | ||
|     Team(id=2, name='Preventers', headquarters='Sharp Tower'),
 | ||
|     Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Bar')
 | ||
| ]
 | ||
| 
 | ||
| // Automatically refresh the data while accessing the attribute .heores
 | ||
| INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age 
 | ||
| FROM hero, heroteamlink 
 | ||
| WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id
 | ||
| INFO Engine [cached since 0.1499s ago] (1,)
 | ||
| 
 | ||
| // Print Z-Force heroes, including Spider-Boy 🎉
 | ||
| Z-Force heroes: [
 | ||
|     Hero(name='Deadpond', age=None, id=1, secret_name='Dive Wilson'),
 | ||
|     Hero(name='Spider-Boy', age=None, id=3, secret_name='Pedro Parqueador', teams=[
 | ||
|         Team(id=2, name='Preventers', headquarters='Sharp Tower'),
 | ||
|         Team(id=1, name='Z-Force', headquarters='Sister Margaret’s Bar', heroes=[...])
 | ||
|     ])
 | ||
| ]
 | ||
| ```
 | ||
| 
 | ||
| </div>
 | ||
| 
 | ||
| ## Remove Many-to-Many Relationships
 | ||
| 
 | ||
| Now let's say that right after joining the team, **Spider-Boy** realized that their "life preserving policies" are much more relaxed than what he's used to. 💀
 | ||
| 
 | ||
| And their *occupational safety and health* is also not as great... 💥
 | ||
| 
 | ||
| So, **Spider-Boy** decides to leave **Z-Force**.
 | ||
| 
 | ||
| Let's update the relationships to remove `team_z_force` from `hero_spider_boy.teams`.
 | ||
| 
 | ||
| Because `hero_spider_boy.teams` is just a list (a special list managed by SQLAlchemy, but a list), we can use the standard list methods.
 | ||
| 
 | ||
| In this case, we use the method `.remove()`, that takes an item and removes it from the list.
 | ||
| 
 | ||
| ```Python hl_lines="17-19  21-22"
 | ||
| # Code above omitted 👆
 | ||
| 
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py[ln:78-97]!}
 | ||
| 
 | ||
| # Code below omitted 👇
 | ||
| ```
 | ||
| 
 | ||
| <details>
 | ||
| <summary>👀 Full file preview</summary>
 | ||
| 
 | ||
| ```Python
 | ||
| {!./docs_src/tutorial/many_to_many/tutorial002.py!}
 | ||
| ```
 | ||
| 
 | ||
| </details>
 | ||
| 
 | ||
| And this time, just to show again that by using `back_populates` **SQLModel** (actually SQLAlchemy) takes care of connecting the models by their relationships, even though we performed the operation from the `hero_spider_boy` object (modifying `hero_spider_boy.teams`), we are adding `team_z_force` to the **session**. And we commit that, without even add `hero_spider_boy`.
 | ||
| 
 | ||
| This still works because by updating the teams in `hero_spider_boy`, because they are synchronized with `back_populates`, the changes are also reflected in `team_z_force`, so it also has changes to be saved in the DB (that **Spider-Boy** was removed).
 | ||
| 
 | ||
| And then we add the team, and commit the changes, which updates the `team_z_force` object, and because it changed the table that also had a connection with the `hero_spider_boy`, it is also marked internally as updated, so it all works.
 | ||
| 
 | ||
| And then we just print them again to confirm that everything worked correctly.
 | ||
| 
 | ||
| ## Run the Program Again
 | ||
| 
 | ||
| To confirm that this last part worked, you can run the program again, it will output something like:
 | ||
| 
 | ||
| <div style="font-size: 1rem;" class="termy">
 | ||
| 
 | ||
| ```console
 | ||
| $ python app.py
 | ||
| 
 | ||
| // Previous output omitted 🙈
 | ||
| 
 | ||
| // Delete the row in the link table
 | ||
| INFO Engine DELETE FROM heroteamlink WHERE heroteamlink.team_id = ? AND heroteamlink.hero_id = ?
 | ||
| INFO Engine [generated in 0.00043s] (1, 3)
 | ||
| // Save the changes
 | ||
| INFO Engine COMMIT
 | ||
| 
 | ||
| // Automatically start a new transaction
 | ||
| INFO Engine BEGIN (implicit)
 | ||
| 
 | ||
| // Automatically refresh the data while accessing the attribute .heroes
 | ||
| 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.00029s] (1,)
 | ||
| INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age 
 | ||
| FROM hero, heroteamlink 
 | ||
| WHERE ? = heroteamlink.team_id AND hero.id = heroteamlink.hero_id
 | ||
| INFO Engine [cached since 0.5625s ago] (1,)
 | ||
| 
 | ||
| // Print the Z-Force heroes after reverting the changes
 | ||
| Reverted Z-Force's heroes: [
 | ||
|     Hero(name='Deadpond', age=None, id=1, secret_name='Dive Wilson')
 | ||
| ]
 | ||
| 
 | ||
| // Automatically refresh the data while accessing the attribute .teams
 | ||
| INFO Engine SELECT hero.id AS hero_id, hero.name AS hero_name, hero.secret_name AS hero_secret_name, hero.age AS hero_age 
 | ||
| FROM hero 
 | ||
| WHERE hero.id = ?
 | ||
| INFO Engine [cached since 0.4209s ago] (3,)
 | ||
| INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters 
 | ||
| FROM team, heroteamlink 
 | ||
| WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
 | ||
| INFO Engine [cached since 0.5842s ago] (3,)
 | ||
| 
 | ||
| // Print Spider-Boy's teams after reverting the changes
 | ||
| Reverted Spider-Boy's teams: [
 | ||
|     Team(id=2, name='Preventers', headquarters='Sharp Tower')
 | ||
| ]
 | ||
| 
 | ||
| // Automatically roll back any possible previously unsaved transaction
 | ||
| INFO Engine ROLLBACK
 | ||
| 
 | ||
| ```
 | ||
| 
 | ||
| </div>
 | ||
| 
 | ||
| ## Recap
 | ||
| 
 | ||
| Updating and removing many-to-many relationships is quite straightforward after setting up the **link model** and the relationship attributes.
 | ||
| 
 | ||
| You can just use common list operation. 🚀
 | 
