Database relationship design: one-to-many, many-to-many practice
📂 Stage: Stage 2 - Interaction and Data (Core) 🔗 Related chapters: SQLAlchemy ORM · 评论系统与交互
1. Sorting out relationship types
When designing a business data model, clarifying "who" and "who" are related is the basis for making the system scalable and easy to maintain. Entity relationships in the database are mainly divided into three categories:
You can think of them as real-world relationships: a user can write multiple articles (one-to-many), an article can be tagged with multiple tags, and a tag can be used by multiple articles (many-to-many), and a user will only have one private profile (one-to-one).
Core business correspondence shorthand
This article focuses on dismantling the most frequently used one-to-many and many-to-many, and easily solves the N+1 query performance pit that is most easily avoided.
2. One-to-many relationship practice: Classification → Article
One-to-many is the most common relationship: the "one" side holds a collection attribute (reverse relationship), and the "many" side holds foreign keys and object attributes that point directly to the "one" side (forward relationship).
Take Category and Post as an example: there can be multiple articles under a category, and each article only belongs to one category.
2.1 Complete model definition
Design Points:
- foreign keys
category_idPut it in the "many" side table, it is recommended to add it explicitlyindex=True。 back_populatesSynchronize the relationship between the two parties: set the articlecategorytime, classifiedpostsIt will also be updated automatically.lazy="dynamic"Especially useful on reverse collections: to avoid loading too many articles at once, chain filtering, sorting, and paging are possible.
3. One-to-many core problem: query and N+1 optimization
3.1 Basic related query
3.2 lazyHow to choose parameters?
lazyControlling the loading timing and return type of associated data directly affects performance:
Tips: many-to-one positive correlation (such as
post.category) Usually the amount of data is small, you can use the defaultselect, or useselectinBatch loading in one IN query; reverse one-to-many collection (such ascategory.posts) It is recommended to use it directlydynamic。
3.3 Fatal performance pit: N+1 query
❌ Wrong way of writing
This is the classic N+1 problem - a large number of additional database queries are triggered in the loop, and the interface will become slower and slower.
✅ Correct optimization: preload related data
Recommendedselectinload: It is better thanjoinedloadMore stable, no result set expansion caused by complex JOIN.
4. Many-to-many relationship practice: article → tag
An article can have multiple tags, and a tag can also be used by multiple articles, which requires an intermediate table to record the relationship. If the intermediate table only stores two foreign keys (no additional data), you can directly usedb.Tabledefinition without creating a model class.
4.1 Model definition of pure relational table
Don't forget toPostAdd it to the modeltagsrelation:
4.2 Common operations
The essence of this kind of "multiple tags at the same time" query is to firstJOINIntermediate table, then group by articles, and finally useHAVINGFilter out records with the correct number of matches.
5. Self-referencing one-to-many: comment tree
The "building within a building" of comments is a classic self-referencing one-to-many: a comment can have multiple replies, and each reply is itself a comment. The way to achieve this is to add a foreign key pointing to your own primary key in the same table.
remote_side=[id]Tells SQLAlchemy about the current modelidcolumn is the primary key of the "one" side, soparent_idIt is the foreign key of the "many" end.cascade="all, delete-orphan"All child replies will be automatically deleted when the parent comment is deleted to avoid leaving orphan data.
passcomment.repliesAll sub-replies can be obtained,comment.parentYou can get the parent comment and easily build a tree comment structure.
6. Summary of best practices
- Explicit indexing: Foreign keys and common query fields (category name, title, time) must be added
index=True。 - Prevention N+1: Use decisively when querying related data in batches
selectinloadorjoinedload。 - For large gatherings
dynamic: For reverse collections like "all articles under a category" that may become very large, be sure tolazyset to"dynamic"。 - Cascade deletion should be enabled with caution:
cascade="delete-orphan"Only use it when you need to "delete the parent as well as the child" to avoid accidentally deleting data. - Build a pure intermediate table model: If the intermediate table only needs to store two foreign keys, use
db.TableMore lightweight; only upgrade to model when additional fields are needed (e.g. correlation time, weight).
🔗 Extended reading

