【问题标题】:SqlAlchemy Model association based on record data基于记录数据的SqlAlchemy模型关联
【发布时间】:2012-02-03 23:56:02
【问题描述】:

有一个info 表,它与car 表或suvtable 有关系。

info.type 字段中指定。

那么如何根据记录的类型数据动态创建关联呢?

class Info(Base):
    item_id = Column(ForeignKey('cars-or-suvs-table.id'))
    type = Column(String())

class Car(Base):
    - data - 

    info = relationship('Info', backref="car")

class Suv(Base):
    - data - 

    info = relationship('Info', backref="suv")

编辑:我的表已经填满了数据,所以我无法更改数据库架构。

【问题讨论】:

  • 每辆 Car 应该与 Item 是一对一的关系,还是一对多的关系?
  • car-or-suv 和 info 是一对一的

标签: python sqlalchemy flask flask-sqlalchemy


【解决方案1】:

由于您正在寻找不需要将外键移动到其他表的解决方案,您可以尝试这种方法:

import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import sql
Base = declarative_base()
engine = sqlalchemy.create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
session = Session()

class Info(Base):
    __tablename__ = 'info'
    id = Column(Integer(), primary_key=True)
    type = Column(String())
    item_id = Column(Integer())

    @property
    def item(self):
        if self.type == 'car':
            return self._car
        elif self.type == 'suv':
            return self._suv
        return None

    @item.setter
    def item(self, value):
        if value is not None:
            self.item_id = value.id
            self.type = value.__tablename__
        else:
            self.item_id = None

class Car(Base):
    __tablename__ = 'car'
    id = Column(Integer(), primary_key=True)
    info = relationship(Info, primaryjoin=sql.and_(id == Info.item_id, Info.type == 'car'), foreign_keys=Info.item_id, uselist=False, backref='_car')

class Suv(Base):
    __tablename__ = 'suv'
    id = Column(Integer(), primary_key=True)
    info = relationship(Info, primaryjoin=sql.and_(id == Info.item_id, Info.type == 'suv'), foreign_keys=Info.item_id, uselist=False, backref='_suv')

我将 Info.car 重命名为 Info._car,因为即使 .type 是“suv”,._car 也不可避免地会成为假汽车对象。

为了简单起见,我将事件侦听器的东西留了下来,但你绝对可以从我的其他答案中调整你需要的部分,以避免事情进入不一致的状态。

【讨论】:

    【解决方案2】:

    在 SQL 中,外键必须映射到一个特定的表,因此您需要将外键放在指向 'info.id' 的 'car' 或 'suv' 表中。

    这对于你需要的东西来说可能有点过头了,但这是解决它的一种方法(假设你确实希望每辆车只有一个信息):

    import sqlalchemy
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy import Column, String, Integer, ForeignKey
    from sqlalchemy.orm import relationship
    from sqlalchemy import event
    Base = declarative_base()
    
    class Info(Base):
        __tablename__ = 'info'
        id = Column(Integer(), primary_key=True)
        type = Column(String())
        # NOTE: can't use backref='info' because we need the attributes defined
        #   directly on both classes so we can attach event listeners
        car = relationship('Car', back_populates='info', uselist=False)
        suv = relationship('Suv', back_populates='info', uselist=False)
    
        @property
        def item(self):
            # could check self.type here if you wanted
            return self.car or self.suv
    
        @item.setter
        def item(self, value):
            if isinstance(value, Car):
                self.car = value
            elif isinstance(value, Suv):
                self.suv = value
            elif value is None:
                self.car = None
                self.suv = None
            else:
                raise ValueError("item must be Car or Suv")
    
    @event.listens_for(Info.car, 'set')
    def _car_set_event(target, value, oldvalue, initiator):
        if value is not None:
            target.type = 'car'
            if target.suv:
                target.suv = None
        elif target.type == 'car':
            target.type = None
    
    @event.listens_for(Info.suv, 'set')
    def _suv_set_event(target, value, oldvalue, initiator):
        if value is not None:
            target.type = 'suv'
            if target.car:
                target.car = None
        elif target.type == 'suv':
            target.type = None
    
    class Car(Base):
        __tablename__ = 'car'
        id = Column(Integer(), primary_key=True)
        info_id = Column(Integer(), ForeignKey('info.id'))
        info = relationship(Info, back_populates='car')
    
    @event.listens_for(Car.info, 'set')
    def _car_info_set_event(target, value, oldvalue, initiator):
        if value is not None:
            value.type = 'car'
    
    class Suv(Base):
        __tablename__ = 'suv'
        id = Column(Integer(), primary_key=True)
        info_id = Column(Integer(), ForeignKey('info.id'))
        info = relationship(Info, back_populates='suv')
    
    @event.listens_for(Suv.info, 'set')
    def _suv_info_set_event(target, value, oldvalue, initiator):
        if value is not None:
            value.type = 'suv'
    

    事件侦听器的复杂性让您知道,当您执行以下操作时会自动管理类型:

    car1.info = Info()
    assert (car1.info.type == 'car')
    

    info1 = car1.info
    info1.suv = suv1
    assert (car1.info is None)
    assert (info1.type == 'suv')
    

    如果你想自己保持 Info.type、Info.car 和 Info.suv 一致,你可以省略所有的事件监听函数。

    为 CarInfo 和 SuvInfo 设置单独的对象和表也是一个非常合理的选择,并完全避免所有这些复杂性。

    【讨论】:

    • 感谢您提供详细信息,但我无法更改当前数据库架构,我会尝试从您的回答中找出答案。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 2012-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多