避免三个表之间的循环依赖

时间:2012-06-07 19:44:57

标签: database orm sqlalchemy

我正在使用sqlalchemy设计一个数据库来存储科学测试数据。我遇到了一个我似乎无法弄清楚的问题。

在我的测试数据中,每个Observation都有一个State(位置,速度,加速度),StateTime(州的时间)适用)。到现在为止还挺好。我为Times创建了一个单独的表,因为我处理了不同的次数,我想使用参考表来指示每次的时间类型(状态时间,观察时间等)。我处理的时间类型可能会发生变化,因此我认为通过这种方式进行规范化会让我在未来添加新的时间,因为它们只是参考表中的行。

到目前为止,这部分工作(使用声明式):

class Observation(Base):
    __tablename__ = 'tbl_observations'
    id = Column(Integer, primary_key=True)
    state_id = Column(Integer, ForeignKey('tbl_states.id'))
    state = relationship('State', uselist=False)

class State(Base):
    __tablename__ = 'tbl_states'
    id = Column(Integer, primary_key=True)
    time_id = Column(Integer, ForeignKey('tbl_times.id'))
    time = relationship('Time', uselist=False)

class Time(Base):
    __tablename__ = 'tbl_times'
    id = Column(Integer, primary_key=True)
    time_type_id = Column(Integer, ForeignKey('ref_tbl_time_types.id'))
    time_type = relationship('TimeType', uselist=False)
    time_value = Column(Float)

class TimeType(Base):
    __tablename__ = 'ref_tbl_time_types'
    id = Column(Integer, primary_key=True)
    desc = Column(String)

皱纹是观察本身可以有不同的次数。当我尝试在ObservationTime之间创建一对多关系时,我得到循环依赖性错误:

class Observation(Base):
    __tablename__ = 'tbl_observations'
    id = Column(Integer, primary_key=True)
    state_id = Column(Integer, ForeignKey('tbl_states.id'))
    state = relationship('State', uselist=False)

    # Added this line:
    times = relationship('Time')

class Time(Base):
    __tablename__ = 'tbl_times'
    id = Column(Integer, primary_key=True)
    time_type_id = Column(Integer, ForeignKey('ref_tbl_time_types.id'))
    time_type = relationship('TimeType', uselist=False)
    time_value = Column(Float)

    # Added this line:
    observation_id = Column(Integer, ForeignKey('tbl_observations.id'))

我猜这是因为原来的Observation - > State - > Time链的引用权限为Observation

有什么方法可以解决这个问题吗?我的设计搞砸了吗?我在sqlalchemy做错了吗?我是所有这一切的新手所以它可能是上述任何一个。非常感谢您提供的任何帮助。

P.S。我尝试过这里推荐的内容:Trying to avoid a circular reference但是我做错了或者没有解决我的特定问题。

3 个答案:

答案 0 :(得分:4)

此处有关重新考虑您的用例的其他答案很有价值,您应该考虑这些。但是,就SQLAlchemy而言,由多个FK引起的循环依赖问题由use_alter / post_update组合解决,记录在http://docs.sqlalchemy.org/en/rel_0_7/orm/relationships.html#rows-that-point-to-themselves-mutually-dependent-rows。这是使用它的模型:

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

Base= declarative_base()

class Observation(Base):
    __tablename__ = 'tbl_observations'
    id = Column(Integer, primary_key=True)
    state_id = Column(Integer, ForeignKey('tbl_states.id'))
    state = relationship('State', uselist=False)

    times = relationship('Time')

class State(Base):
    __tablename__ = 'tbl_states'
    id = Column(Integer, primary_key=True)
    time_id = Column(Integer, ForeignKey('tbl_times.id'))

    # post_update is preferable on the many-to-one
    # only to reduce the number of UPDATE statements
    # versus it being on a one-to-many.
    # It can be on Observation.times just as easily.
    time = relationship('Time', post_update=True)

class Time(Base):
    __tablename__ = 'tbl_times'
    id = Column(Integer, primary_key=True)
    time_type_id = Column(Integer, ForeignKey('ref_tbl_time_types.id'))
    time_type = relationship('TimeType', uselist=False)
    time_value = Column(Float)

    observation_id = Column(Integer, ForeignKey('tbl_observations.id', 
                                    use_alter=True, name="fk_time_obs_id"))

class TimeType(Base):
    __tablename__ = 'ref_tbl_time_types'
    id = Column(Integer, primary_key=True)
    desc = Column(String)


e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
Base.metadata.drop_all(e)
Base.metadata.create_all(e)

s = Session(e)

tt1 = TimeType(desc="some time type")
t1, t2, t3, t4, t5 = Time(time_type=tt1, time_value=40), \
                Time(time_type=tt1, time_value=50), \
                Time(time_type=tt1, time_value=60),\
                Time(time_type=tt1, time_value=70),\
                Time(time_type=tt1, time_value=80)

s.add_all([
    Observation(state=State(time=t1), times=[t1, t2]),
    Observation(state=State(time=t2), times=[t1, t3, t4]),
    Observation(state=State(time=t2), times=[t2, t3, t4, t5]),
])

s.commit()

答案 1 :(得分:2)

观察与国家之间存在多对一的关系。因此,一个国家可以有许多观察,每个观察都有一个国家。

你们国家和时代之间也有多对一的关系。因此,一次可以有许多国家,每个国家都有一次。

你是对的,因为这个问题是对“时代”观察的回顾。你强迫每个时间都有一个观察,而观察又必须有一个状态,而状态又必须有一个时间(然后循环会永远重复)。

要解决这个问题,你需要弄清楚你在这些关系中想要描绘的内容。如果一个观察有一个有时间的状态,则观察有一个时间(你可以从州获得时间)。

所以你需要回答的真正问题是:说时间观察是什么意思?你将如何在你的申请中使用它?

答案 2 :(得分:0)

我想我并没有完全获得对象模型中的模型名称以及它们如何与现实世界相对应。但我会尝试猜测。首先,我怀疑模型Time(看起来相当基本且几乎没有逻辑)应该对某些更高级别的模型类ForeignKeyObservation。鉴于此,我认为您的模型不是n-1关系的链,而是ternary relationship的一种。{{3}}。所以我可以看到你的模特如下:

class Base(object):
    id = Column(Integer, primary_key=True)

class Observation(Base):
    __tablename__ = 'tbl_observations'

class ObservationInstance(Base):
    __tablename__ = 'tbl_observation_instances'
    observation_id = Column(Integer, ForeignKey('tbl_observations.id'))
    state_id = Column(Integer, ForeignKey('tbl_states.id'))
    time_id = Column(Integer, ForeignKey('tbl_times.id'))

    # relationships
    observation = relationship('Observation', backref="instances")
    state = relationship('State')
    time = relationship('Time')

class State(Base):
    __tablename__ = 'tbl_states'

class Time(Base):
    __tablename__ = 'tbl_times'
    time_type_id = Column(Integer, ForeignKey('ref_tbl_time_types.id'))
    time_type = relationship('TimeType', uselist=False)
    time_value = Column(Float)

class TimeType(Base):
    __tablename__ = 'ref_tbl_time_types'
    desc = Column(String)

希望这有任何意义,并且适合您正在尝试建模的现实世界。我假设你的模型代表某种(科学)实验。在这种情况下,我会重命名Observation -> ExperiementObservationInstance -> Observation