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

如何使工单查询返回父/子工单ID列表而非关联字典列表?

如何让SQLAlchemy关联的工单查询返回ID列表而非完整关联对象?

问题描述

我开发了一个工单管理应用,工单之间支持交叉引用,用专用表存储关联关系。简化后的SQLAlchemy表结构如下:

class Ticket(Base):
    """Ticket."""
    __tablename__ = "tickets"
    id = Column(Integer, primary_key=True, autoincrement="auto")
    title = Column(String(50), nullable=False)
    children_tickets = relationship(
        "TicketReferences",
        primaryjoin="Ticket.id == TicketReferences.parent_id",
        cascade="all, delete-orphan"
    )
    parents_tickets = relationship(
        "TicketReferences",
        primaryjoin="Ticket.id == TicketReferences.child_id",
        cascade="all, delete-orphan"
    )


class TicketReferences(Base):
    """Tickets can be cross-referenced"""
    __tablename__ = "tickets_references"
    id = Column(Integer, primary_key=True, autoincrement="auto")
    parent_id = Column(Integer, ForeignKey("tickets.id"), nullable=False)
    child_id = Column(Integer, ForeignKey("tickets.id"), nullable=False)

对应的Pydantic Schema:

class TicketsReferences(BaseModel):
    parent_id: int
    child_id: int

    class Config:
        orm_mode = True

class TicketCreate(TicketBase):
    title: str
    parents_tickets: Optional[List[int]] = []
    children_tickets: Optional[List[int]] = []

class Ticket(TicketBase):
    id: int
    title: str
    parents_tickets: List[TicketsReferences]
    children_tickets: List[TicketsReferences]
    class Config:
        orm_mode = True

创建工单的请求体示例:

# 创建第一个工单(ID会是1)
{
  "title": "Foo",
  "parents_tickets": [],
  "children_tickets": []
}

# 创建第二个工单,关联工单1为父工单
{
  "title": "Bar",
  "parents_tickets": [1],
  "children_tickets": []
}

现在查询工单时,返回的关联字段结构过于复杂:

# 查询工单#2的结果
{
  "id": 2,
  "title": "bar",
  "parents_tickets": [
    {
      "parent_id": 1,
      "child_id": 2
    }
  ],
  "children_tickets": []
}

我期望返回的是ID列表,比如"parents_tickets": [1],请问怎么实现?


解决方案

方法1:修改Pydantic模型,转换关联数据

如果不想改动SQLAlchemy的关联结构,可以在Pydantic模型中添加转换逻辑,把TicketReferences对象列表转换成纯ID列表。

适配Pydantic v2版本

使用field_validator处理数据转换,同时保留原字段名:

from pydantic import BaseModel, field_validator
from typing import List, Optional

class Ticket(TicketBase):
    id: int
    title: str
    parents_tickets: List[int]
    children_tickets: List[int]

    @field_validator('parents_tickets', mode='before')
    def parse_parents(cls, v):
        # 从SQLAlchemy的关联对象中提取parent_id
        if isinstance(v, list) and all(isinstance(item, TicketReferences) for item in v):
            return [item.parent_id for item in v]
        return v
    
    @field_validator('children_tickets', mode='before')
    def parse_children(cls, v):
        if isinstance(v, list) and all(isinstance(item, TicketReferences) for item in v):
            return [item.child_id for item in v]
        return v

    class Config:
        orm_mode = True

适配Pydantic v1版本

使用@validator装饰器实现相同逻辑:

from pydantic import BaseModel, validator
from typing import List, Optional

class Ticket(TicketBase):
    id: int
    title: str
    parents_tickets: List[int]
    children_tickets: List[int]

    @validator('parents_tickets', pre=True)
    def parse_parents(cls, v):
        if isinstance(v, list) and all(isinstance(item, TicketReferences) for item in v):
            return [item.parent_id for item in v]
        return v
    
    @validator('children_tickets', pre=True)
    def parse_children(cls, v):
        if isinstance(v, list) and all(isinstance(item, TicketReferences) for item in v):
            return [item.child_id for item in v]
        return v

    class Config:
        orm_mode = True

方法2:将SQLAlchemy关联改为多对多自关联

更优雅的方式是重构关联关系,使用SQLAlchemy的多对多自关联,让框架直接维护关联表,简化数据转换逻辑。

首先修改SQLAlchemy模型:

class Ticket(Base):
    """Ticket."""
    __tablename__ = "tickets"
    id = Column(Integer, primary_key=True, autoincrement="auto")
    title = Column(String(50), nullable=False)
    # 多对多自关联:父工单关联子工单
    children_tickets = relationship(
        "Ticket",
        secondary="tickets_references",
        primaryjoin="Ticket.id == TicketReferences.parent_id",
        secondaryjoin="Ticket.id == TicketReferences.child_id",
        back_populates="parents_tickets",
        cascade="all, delete-orphan"
    )
    # 反向关联:子工单关联父工单
    parents_tickets = relationship(
        "Ticket",
        secondary="tickets_references",
        primaryjoin="Ticket.id == TicketReferences.child_id",
        secondaryjoin="Ticket.id == TicketReferences.parent_id",
        back_populates="children_tickets",
        cascade="all, delete-orphan"
    )

class TicketReferences(Base):
    """Tickets can be cross-referenced"""
    __tablename__ = "tickets_references"
    # 用复合主键避免重复关联
    parent_id = Column(Integer, ForeignKey("tickets.id"), nullable=False, primary_key=True)
    child_id = Column(Integer, ForeignKey("tickets.id"), nullable=False, primary_key=True)

然后修改Pydantic模型,通过from_orm方法直接提取ID:

class TicketCreate(TicketBase):
    title: str
    parents_tickets: Optional[List[int]] = []
    children_tickets: Optional[List[int]] = []

class Ticket(TicketBase):
    id: int
    title: str
    parents_tickets: List[int]
    children_tickets: List[int]

    class Config:
        orm_mode = True
        
        @classmethod
        def from_orm(cls, obj):
            return cls(
                id=obj.id,
                title=obj.title,
                parents_tickets=[ticket.id for ticket in obj.parents_tickets],
                children_tickets=[ticket.id for ticket in obj.children_tickets]
            )

这样查询工单时就能直接返回纯ID列表,同时创建工单时只需将传入的ID列表转换为对应的Ticket对象添加到关联中即可。


内容的提问来源于stack exchange,提问作者Shan-x

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 12:55:20