sqlalchemy中的一对多关系定义与反射和声明性语法给出了连接条件错误

时间:2011-10-06 23:41:07

标签: python sqlalchemy

我正在尝试在现有数据库上设置一对多关系。

简化的DDL是:

create table accnt (
  code varchar(20) not null
  , def varchar(100)
  , constraint pk_accnt primary key (code)
);

commit;

create table slorder (
  code varchar(20) not null
  , def varchar(100)
  , dt date
  , c_accnt varchar(20) not null
  , constraint pk_slorder primary key (code)
  , constraint fk_slorder_accnt foreign key (c_accnt)
     references accnt (code)
     on update cascade on delete cascade
);

commit;

SqlAlchemy Code:

from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *

engine = create_engine('firebird://sysdba:masterkey@127.0.0.1/d:\\prj\\db2\\makki.fdb?charset=WIN1254', echo=False)
Base = declarative_base()
Base.metadata.bind = engine

class Accnt(Base):
    __tablename__ = 'accnt'
    __table_args__ = {'autoload': True}

    defi = Column('def', String(100))

class SlOrder(Base):
    __tablename__ = 'slorder'
    __table_args__ = {'autoload': True}

    defi = Column("def", String(100))
    accnt = relationship('Accnt', backref='slorders')

给出

sqlalchemy.exc.ArgumentError: Could not determine join condition between parent/child tables on relationship SlOrder.accnt.  Specify a 'primaryjoin' expression.  If 'secondary' is present, 'secondaryjoin' is needed as well.

错误。

我可能解决这个问题的方法是:

1

class SlOrder(Base):
    __tablename__ = 'slorder'
    __table_args__ = {'autoload': True}

    defi = Column("def", String(100))
    c_accnt = Column("c_accnt", String(20), ForeignKey('accnt.code'))

    accnt = relationship('Accnt', backref='slorders')

但是这种方法需要我必须手动添加每个外键约束列,这导致产生反射用法。 (因为我有很多列引用其他表。)

2

class SlOrder(Base):
    __table__ = Table('accnt', metadata, autoload = True, autoload_with=engine)
    accnt = relationship('Accnt', backref='slorders', primaryjoin=(__table__.c_accnt==Accnt.code))

这种方法有另一个后果(请参阅my previous question

那我错过了什么?使用反射和声明语法定义关系的最佳方法是什么?

编辑:

我认为如果子表只有一个对父表的引用,SqlAlchemy会找到并建立关系。

但是如果子表有多个引用:

create table slorder (
  code varchar(20) not null
  , def varchar(100)
  , dt date
  , c_accnt varchar(20) not null
  , c_accnt_ref  varchar(20) 
  , constraint pk_slorder primary key (code)
  , constraint fk_slorder_accnt foreign key (c_accnt)
     references accnt (code)
     on update cascade on delete cascade

  , constraint fk_slorder_accnt_ref foreign key (c_accnt_ref)
     references accnt (code)
     on update cascade on delete no action
);

发生上述错误。

如果两个表之间存在多个关系,那么SqlAlchemy的预期行为是否会产生错误?

2 个答案:

答案 0 :(得分:2)

我认为您必须在子表中添加ForeignKey

通过定义ForeignKey,您可以为c_accnt分配值,并将父对象分配给accnt

在sqlalchemy内部触发您在primaryjoin中编写的查询。如果没有外键,则模型无法理解它必须在哪个字段上运行查询。

您可以双向使用。但我个人更喜欢ForeignKeyrelationForeignKey。这样你就必须编写更多的代码,但是它可以灵活地直接赋值和对象。

答案 1 :(得分:1)

我认为您的代码应自动反映ForeignKey并使用该关系而不做任何更改 只是探讨这个问题的一些想法:

  1. 确保您没有多个ForeignKey到同一个父表,否则您必须使用primaryjoin参数指定连接条件,因为SA无法自动决定使用哪个。
  2. 确保您在ForeignKey表格中确实定义了slorder(如代码示例所示)
  3. 检查是否可能为某些表定义了非默认schema,您可能需要在表_table_args__ = {'schema': 'my_schema'}中定义一个(只是猜测,因为我不知道firebird所以真的不知道架构支持吗)
  4. 检查/调试反射步骤:sqlalchemy/dialects/firebird/base.pyget_foreign_keys。检查SQL语句fkqry并直接在数据库上执行它,看它是否反映了您的ForeignKey。如果没有,请尝试找出原因。