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

SQLAlchemy中select语句order_by+distinct报错,session.query正常

SQLAlchemy 1.4.28 + MySQL 8.0.19 多表关联查询问题

问题背景

用SQLAlchemy 1.4.28搭配MySQL 8.0.19做开发,在多表关联后执行order_by和distinct操作时遇到两个问题:

  • 用session.query接口执行完全正常
  • 用原生select配合session.execute执行时报错,手动补全字段后SQL在MySQL端返回行数一致,但Python端拿到的结果数量不一样

报错信息

3065, "Expression #1 of ORDER BY clause is not in SELECT list, references column 'my_db.fault.jira_ticket' which is not in SELECT list; this is incompatible with DISTINCT")

代码对比

1. session.query 正常执行代码

sess.query(CM, CM.cm_id).where(CM.status == "created").join(Fault).join(NewFaultMap).order_by(Fault.jira_ticket).distinct().limit(1).all()

现象:生成的SQL会自动把order_by里的Fault.jira_ticket加到SELECT列表里,执行没有问题。

2. 原生select 报错代码

sess.execute(select(CM, CM.cm_id).where(CM.status == "created").join(Fault).join(NewFaultMap).order_by(Fault.jira_ticket).distinct().limit(1)).all()

现象:因为order_by引用的Fault.jira_ticket不在SELECT列表里触发报错。手动把这个字段加进去后,生成的SQL和session.query的一致,MySQL端执行返回的行数相同,但Python端结果数量有差异:

  • session.query结果长度:164
  • 添加字段后的select结果长度:167

模型定义

class CustomBase(Timestamp):
    """
    Custom base class which will add
    created and updated fields automatically to the newly created table
    __repr__ method
    """

    def as_dict(self):
        return {k: v for k, v in vars(self).items() if not k.startswith("_sa")}


CustomModelBase: Any = declarative_base(cls=CustomBase)


class NewFaultMap(CustomModelBase):
    __tablename__ = "faultmap"

    id = Column("id", Integer, primary_key=True)
    check_id = Column(String(96), unique=True, nullable=False)

    # One to many relationship with Fault class
    faults = relationship("Fault", back_populates="faultmap", lazy="selectin")


class CM(CustomModelBase):
    __tablename__ = "cm"

    cm_id = Column("id", Integer(), primary_key=True)
    status = Column("status", String(32), nullable=False)
    created_by = Column("created_by", String(64), default="")
    updated_by = Column("updated_by", String(64), default="")


    # One to many relationship with Fault class
    faults = relationship(
        "Fault",
        back_populates="cm",
        lazy="selectin",
        cascade="all, delete-orphan",
    )


class Fault(CustomModelBase):
    __tablename__ = "fault"

    id = Column("id", Integer(), primary_key=True)
    cm_id = Column(
        Integer,
        ForeignKey("cm.id", ondelete="CASCADE"),
        index=True,
    )
    check_id = Column(
        String(96),
        ForeignKey("faultmap.check_id", onupdate="CASCADE"),
        nullable=False,
    )
    jira_ticket = Column("jira_ticket", String(64), default="")
    cm = relationship("CM", back_populates="faults", lazy="selectin")
    faultmap = relationship("NewFaultMap", back_populates="faults", lazy="selectin")

问题原因及解决方案

1. 原生select报错原因

SQLAlchemy 1.4里,session.query和新的2.0风格select接口处理distinct+order_by的逻辑不一样:

  • session.query会自动把order_by里的字段隐式加到SELECT列表,满足MySQL对DISTINCT的约束(ORDER BY的列必须出现在SELECT列表中)
  • 新的select接口更贴近原生SQL,不会自动补全字段,必须显式指定所有需要的字段

2. Python端结果数量差异原因

手动添加Fault.jira_ticket到select后,返回的结果元组包含CM对象、CM.cm_id、Fault.jira_ticket三个元素;而session.query(CM, CM.cm_id)返回的是(CM对象, cm_id)的元组。更关键的是,SQLAlchemy的ORM层会对返回的CM对象自动去重(因为distinct),但原生select返回的是原始行数据,同一CM对象可能对应不同的jira_ticket,导致计数变多。

正确解决方案

方案一:用ORM层面的去重逻辑

如果只需要去重后的CM对象,直接查询CM并配合distinct(CM.cm_id),SQLAlchemy会自动处理字段并保证ORM去重:

result = sess.execute(
    select(CM)
    .where(CM.status == "created")
    .join(Fault)
    .join(NewFaultMap)
    .order_by(Fault.jira_ticket)
    .distinct(CM.cm_id)
    .limit(1)
).scalars().all()
print(len(result))  # 和session.query结果一致

方案二:显式指定字段后手动去重

如果要保留CM+cm_id的返回结构,补全字段后在Python端按CM对象的主键去重:

result = sess.execute(
    select(CM, CM.cm_id, Fault.jira_ticket)
    .where(CM.status == "created")
    .join(Fault)
    .join(NewFaultMap)
    .order_by(Fault.jira_ticket)
    .distinct()
    .limit(1)
).all()
# 按CM的cm_id去重
unique_result = list({item[0].cm_id: item for item in result}.values())
print(len(unique_result))  # 输出164

方案三:继续使用session.query兼容写法

如果暂时不想切换到新的select接口,直接沿用session.query的写法即可:

result = sess.query(CM, CM.cm_id).where(CM.status == "created").join(Fault).join(NewFaultMap).order_by(Fault.jira_ticket).distinct().limit(1).all()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 23:22:51