文章

SQLAlcyemy relationship

最佳实践方案:

  1. 使用字符串引用(推荐): ```python from sqlalchemy import Column, Integer, String from sqlalchemy.orm import relationship from app.db.base_class import Base

class User(Base): tablename = “users” id = Column(Integer, primary_key=True) name = Column(String(100)) # 使用字符串引用,不需要导入 Post 模型 posts = relationship(“Post”, back_populates=”author”)

1
2
3
4
5
6
7
8
9
10
11
12
13
```python
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from app.db.base_class import Base

class Post(Base):
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True)
    title = Column(String(255))
    author_id = Column(Integer, ForeignKey("users.id"))
    # 同样使用字符串引用
    author = relationship("User", back_populates="posts")
  1. 推荐使用第一种方法(字符串引用),因为:
    • 简单直观
    • 不需要额外的初始化步骤
    • SQLAlchemy 官方推荐的方式
  2. 目录结构建议
    1
    2
    3
    4
    5
    6
    7
    
    app/
      models/
     __init__.py      # 导出所有模型
     base.py          # 基础模型类
     user.py          # 用户模型
     post.py          # 文章模型
     comment.py       # 评论模型
    
  3. __init__.py 中统一导出: ```python:app/models/init.py from .user import User from .post import Post from .comment import Comment

all = [“User”, “Post”, “Comment”]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

4. **关系定义的一般原则**:
   - 在"多"的一方定义外键
   - 在更重要的实体上定义 relationship
   - 使用 back_populates 而不是 backref(更明确)
   - 使用字符串引用避免循环导入

5. **示例使用**:
```python
# 在其他地方使用模型
from app.models import User, Post

# 创建用户和文章
user = User(name="Alice")
post = Post(title="Hello")
user.posts.append(post)

这样的组织方式可以:

  • 避免循环引用问题
  • 保持代码清晰和模块化
  • 便于维护和扩展
  • 提供良好的类型提示支持

对于多对多关系,我来展示几种常见的实现方式:

  1. 最简单的多对多关系: ```python from sqlalchemy import Table, Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from app.db.base_class import Base

关联表定义

post_tags = Table( “post_tags”, Base.metadata, Column(“post_id”, Integer, ForeignKey(“posts.id”), primary_key=True), Column(“tag_id”, Integer, ForeignKey(“tags.id”), primary_key=True) )

class Tag(Base): tablename = “tags” id = Column(Integer, primary_key=True) name = Column(String(50), unique=True) # 通过 secondary 指定关联表 posts = relationship(“Post”, secondary=post_tags, back_populates=”tags”)

1
2
3
4
5
6
7
8
9
10
11
12
```python
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from app.db.base_class import Base

class Post(Base):
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True)
    title = Column(String(255))
    # 反向关联
    tags = relationship("Tag", secondary="post_tags", back_populates="posts")
  1. 带额外字段的多对多关系: ```python from datetime import datetime from sqlalchemy import Column, Integer, ForeignKey, DateTime from sqlalchemy.orm import relationship from app.db.base_class import Base

class PostTag(Base): tablename = “post_tags” post_id = Column(Integer, ForeignKey(“posts.id”), primary_key=True) tag_id = Column(Integer, ForeignKey(“tags.id”), primary_key=True) # 额外字段 created_at = Column(DateTime, default=datetime.utcnow) created_by = Column(Integer, ForeignKey(“users.id”))

1
2
3
# 关联关系
post = relationship("Post", back_populates="tag_associations")
tag = relationship("Tag", back_populates="post_associations") ```
1
2
3
4
5
6
7
8
9
class Post(Base):
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True)
    title = Column(String(255))
    
    # 通过关联表访问标签
    tag_associations = relationship("PostTag", back_populates="post")
    # 便捷属性直接访问标签
    tags = relationship("Tag", secondary="post_tags", viewonly=True)
1
2
3
4
5
6
7
8
9
class Tag(Base):
    __tablename__ = "tags"
    id = Column(Integer, primary_key=True)
    name = Column(String(50), unique=True)
    
    # 通过关联表访问文章
    post_associations = relationship("PostTag", back_populates="tag")
    # 便捷属性直接访问文章
    posts = relationship("Post", secondary="post_tags", viewonly=True)
  1. 使用示例: ```python

    简单多对多关系使用

    post = Post(title=”SQLAlchemy Guide”) tag1 = Tag(name=”Python”) tag2 = Tag(name=”Database”)

添加标签

post.tags.append(tag1) post.tags.append(tag2)

或者

post.tags.extend([tag1, tag2])

带额外字段的多对多关系使用

post_tag = PostTag( post=post, tag=tag1, created_by=current_user.id ) db.session.add(post_tag)

查询示例

获取文章的所有标签

tags = post.tags

获取标签的所有文章

posts = tag1.posts

获取带创建信息的关联

for assoc in post.tag_associations: print(f”Tag: {assoc.tag.name}, Created at: {assoc.created_at}”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4. **查询优化**:
```python
class Post(Base):
    __tablename__ = "posts"
    
    # 预加载标签的查询
    @classmethod
    def get_with_tags(cls, db: Session, post_id: int):
        return db.query(cls).options(
            joinedload(cls.tags)
        ).filter(cls.id == post_id).first()
    
    # 按标签筛选文章
    @classmethod
    def filter_by_tag(cls, db: Session, tag_name: str):
        return db.query(cls).join(cls.tags).filter(
            Tag.name == tag_name
        ).all()
  1. 性能考虑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    class Post(Base):
     # 使用 lazy='selectin' 优化加载性能
     tags = relationship(
         "Tag", 
         secondary="post_tags",
         back_populates="posts",
         lazy="selectin"  # 自动预加载标签
     )
        
     # 使用 lazy='dynamic' 延迟加载,支持进一步查询
     tag_query = relationship(
         "Tag",
         secondary="post_tags",
         lazy="dynamic"
     )
    
  2. 常用查询模式: ```python

    获取特定标签的文章数量

    tag_counts = ( db.query(Tag.name, func.count(Post.id)) .join(post_tags) .join(Post) .group_by(Tag.name) .all() )

获取至少有一个标签的文章

posts_with_tags = ( db.query(Post) .join(Post.tags) .distinct() .all() )

获取同时包含多个标签的文章

posts_with_all_tags = ( db.query(Post) .join(Post.tags) .filter(Tag.name.in_([‘Python’, ‘SQLAlchemy’])) .group_by(Post.id) .having(func.count(distinct(Tag.id)) == 2) .all() ) ```

这些实现方式各有优势:

  • 简单多对多关系:代码简洁,适合简单场景
  • 带额外字段的关联:更灵活,可以存储关联的元数据
  • 查询优化:根据实际需求选择合适的加载策略

选择哪种方式主要取决于:

  1. 是否需要在关联中存储额外信息
  2. 查询模式和性能要求
  3. 代码的复杂度和可维护性
本文由作者按照 CC BY 4.0 进行授权