You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于FastAPI实现递归家族树的问题求助

解决FastAPI递归家族树的实现问题

一、修复SQLAlchemy模型的自关联关系

你的children字段无法同时对应father和mother两个关系,SQLAlchemy不允许一个关系同时反向关联多个字段,需要拆分或重新定义关系:

from sqlalchemy import Column, Integer, String, ForeignKey, text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

class DBPerson(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    name = Column(String, index=True)
    # 其他字段(如性别、出生日期等)...

    father_id = Column(Integer, ForeignKey("person.id"), nullable=True)
    # 指定foreign_keys区分自关联字段,反向关联作为父亲的孩子
    father = relationship("DBPerson", foreign_keys=[father_id], back_populates="father_children")
    
    mother_id = Column(Integer, ForeignKey("person.id"), nullable=True)
    # 指定foreign_keys区分自关联字段,反向关联作为母亲的孩子
    mother = relationship("DBPerson", foreign_keys=[mother_id], back_populates="mother_children")

    # 分别存储作为父亲/母亲的孩子(可根据需求选择是否保留)
    father_children = relationship("DBPerson", foreign_keys=[father_id], back_populates="father")
    mother_children = relationship("DBPerson", foreign_keys=[mother_id], back_populates="mother")

    # 可选:统一的children字段(仅用于查询,不支持写入)
    children = relationship(
        "DBPerson",
        primaryjoin="or_(DBPerson.father_id == foreign(DBPerson.id), DBPerson.mother_id == foreign(DBPerson.id))",
        viewonly=True
    )

二、解决Pydantic模型的递归引用问题

直接在类定义中引用自身会导致未定义错误,可通过字符串类型注解解决,这是Pydantic v1/v2通用的方案:

from pydantic import BaseModel

class PersonBase(BaseModel):
    id: int
    name: str
    father: "PersonBase | None" = None
    mother: "PersonBase | None" = None

    class Config:
        orm_mode = True  # Pydantic v1;v2改用from_attributes=True

# Pydantic v1需要手动更新引用(v2可省略,但加上更保险)
PersonBase.update_forward_refs()

三、限制递归世代数

直接递归序列化可能导致无限循环或返回过多数据,可通过两种方式控制深度:

方式1:自定义序列化函数(灵活直观)

编写递归函数,传入深度参数,达到指定深度后停止返回父母信息:

def serialize_person(person: DBPerson, max_depth: int = 3) -> dict:
    """
    将DBPerson对象序列化为家族树结构,限制最大递归深度
    :param max_depth: 最大返回世代数(0表示仅返回当前节点,1表示包含父母,以此类推)
    """
    base_data = {"id": person.id, "name": person.name}
    
    if max_depth <= 0:
        base_data["father"] = None
        base_data["mother"] = None
        return base_data
    
    # 递归序列化父亲(如果存在)
    base_data["father"] = serialize_person(person.father, max_depth - 1) if person.father else None
    # 递归序列化母亲(如果存在)
    base_data["mother"] = serialize_person(person.mother, max_depth - 1) if person.mother else None
    
    return base_data

在FastAPI路由中使用:

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from your_models import DBPerson, get_db  # 替换为你的数据库依赖

app = FastAPI()

@app.get("/person/{person_id}")
def get_family_tree(person_id: int, depth: int = 2, db: Session = Depends(get_db)):
    person = db.query(DBPerson).filter(DBPerson.id == person_id).first()
    if not person:
        return {"error": "Person not found"}
    return serialize_person(person, max_depth=depth)

方式2:SQL递归CTE查询(性能更优)

通过SQL递归公共表表达式(CTE)预查询指定深度内的所有家族成员,再构建树结构,避免N+1查询问题:

def get_family_tree_with_cte(db: Session, person_id: int, max_depth: int = 3):
    # 初始化递归CTE,从指定节点开始,深度为0
    cte = db.query(
        DBPerson.id,
        DBPerson.name,
        DBPerson.father_id,
        DBPerson.mother_id,
        text("0 as depth")
    ).filter(DBPerson.id == person_id).cte(recursive=True)

    # 递归查询父母节点,深度递增,直到达到max_depth
    cte_recursive = db.query(
        DBPerson.id,
        DBPerson.name,
        DBPerson.father_id,
        DBPerson.mother_id,
        text(f"{cte.c.depth} + 1 as depth")
    ).join(cte, DBPerson.id == cte.c.father_id).union_all(
        db.query(
            DBPerson.id,
            DBPerson.name,
            DBPerson.father_id,
            DBPerson.mother_id,
            text(f"{cte.c.depth} + 1 as depth")
        ).join(cte, DBPerson.id == cte.c.mother_id)
    ).filter(cte.c.depth < max_depth)

    # 将所有查询结果存入字典,便于快速查找
    node_map = {}
    for row in db.query(cte.union_all(cte_recursive)).all():
        node_map[row.id] = {
            "id": row.id,
            "name": row.name,
            "father_id": row.father_id,
            "mother_id": row.mother_id,
            "depth": row.depth
        }

    # 递归构建家族树
    def build_tree(node_id):
        node = node_map.get(node_id)
        if not node:
            return None
        # 达到最大深度时,不再递归返回父母
        if node["depth"] >= max_depth:
            return {"id": node["id"], "name": node["name"], "father": None, "mother": None}
        return {
            "id": node["id"],
            "name": node["name"],
            "father": build_tree(node["father_id"]),
            "mother": build_tree(node["mother_id"])
        }

    return build_tree(person_id)

内容的提问来源于stack exchange,提问作者olofristen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:14:58