SQL炼金术中的外键和继承

时间:2019-02-27 19:35:27

标签: python inheritance sqlalchemy

我很难用外键引用在SQLAlchemy中进行继承。

我有一个drives表,它以id作为主键:

   Column     |         Type          | Collation | Nullable |              Default
---------------+-----------------------+-----------+----------+------------------------------------
 id            | integer               |           | not null | nextval('drives_id_seq'::regclass)
 model         | integer               |           | not null |

我还有另一个名为smart的表,它看起来像这样,以<ts, drive>作为主键,而drive是引用drives.id的外键:

   Column     |         Type          | Collation | Nullable |              Default
---------------+-----------------------+-----------+----------+------------------------------------
 drive         | integer                    |           | not null | nextval('drives_id_seq'::regclass)
 ts            | timestamp without timezone |           | not null |
 value         | integer                    |           |          |

我具有以下表示上述表格的类定义。

class Drives(Base):
    __tablename__ = 'drives'
    id = Column('id', Integer, primary_key=True)
    model = Column('model', String)

class Smart(Base):
   ___tablename__ = 'smart'
   drive = Column('drive', Integer, ForeignKey=Drives.id)
   ts = Column('ts', TIMESTAMP)
   value = Column('value', Integer)
   drives = relationship('Drives')
   # I would like something like the following to work, but I get an AttributeError for `model`
   __mapper_args__ = {'primary_key': [ts, drive], 'polymorphic_on': drives.model} 

我想创建两个派生类ModelASmartModelBSmart,其中smart.value根据与drive对应的模型进行不同的解释。

class ModelASmart(Smart):
    __mapper_args__ = {'polymorphic_identity': 'ModelA', 'primary_key': [Smart.ts, Smart.drive]}
    @hybrid_property
    def actual_value(self):
        return self.value * 2

class ModelBSmart(Smart):
    __mapper_args__ = {'polymorphic_identity': 'ModelB', 'primary_key': [Smart.ts, Smart.drive]}
    @hybrid_property
    def actual_value(self):
        return self.value * 3

我的问题:如何将另一个表(model)中的列(drives)用作主表smart中的鉴别符?

1 个答案:

答案 0 :(得分:1)

您可以使用column_property属性将model设为Smart的“本地”,但要花费一个相关的子查询:

class Drives(Base):
    __tablename__ = 'drives'
    id = Column(Integer, primary_key=True)
    model = Column(String)

class Smart(Base):
   __tablename__ = 'smart'
   drive = Column(Integer, ForeignKey(Drives.id), primary_key=True)
   ts = Column(DateTime, primary_key=True)
   value = Column(Integer)
   drives = relationship(Drives)
   model = column_property(select([Drives.model]).where(Drives.id == drive))
   __mapper_args__ = {'polymorphic_on': model}

class ModelASmart(Smart):
    __mapper_args__ = {'polymorphic_identity': 'ModelA'}
    @hybrid_property
    def actual_value(self):
        return self.value * 2

class ModelBSmart(Smart):
    __mapper_args__ = {'polymorphic_identity': 'ModelB'}
    @hybrid_property
    def actual_value(self):
        return self.value * 3

column属性将始终包含在查询中,这意味着相关的子查询将可能确定查询性能。

SQLAlchemy还具有其他通过关系引入属性的方法,例如association proxies和混合属性,但是不能用作polymorphic_on鉴别符。还有另一种更具异国情调的可能性是在smartdrives表之间的map Smart over a join


另一种选择是放弃使用继承,而在Smart上使用普通的混合属性:

class Drives(Base):
    __tablename__ = 'drives'
    id = Column(Integer, primary_key=True)
    model = Column(String)

class Smart(Base):
   __tablename__ = 'smart'
   drive = Column(Integer, ForeignKey(Drives.id), primary_key=True)
   ts = Column(DateTime, primary_key=True)
   value = Column(Integer)
   drives = relationship(Drives)
   _model_coeff = {
       'ModelA': 2,
       'ModelB': 3,
   }
   @hybrid_property
   def actual_value(self):
       return self.value * self._model_coeff[self.drives.model]
   @actual_value.expression
   def actual_value(cls):
       return cls.value * case(
           cls._model_coeff,
           value=select([Drives.model]).
                 where(Drives.id == cls.drive).
                 as_scalar())

这使用case()中的"shorthand" format将查询dict映射到SQL CASE表达式。查询,例如:

session.query(Smart, Smart.actual_value)

将使用相关的子查询在系数之间进行选择,但还有一个使用紧急加载的选项:

session.query(Smart).options(joinedload(Smart.drives, innerjoin=True))

通过这种方式,相关的Drives实例被加载到同一查询中,因此,在访问实例时,hybrid属性不需要执行获取操作:

# `s` is an instance from the previous eager loading query. This access
# will not fire additional queries.
s.actual_value