【问题标题】:Avoid Circular Dependency Between Three Tables避免三个表之间的循环依赖
【发布时间】:2012-06-07 19:44:57
【问题描述】:

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

在我的测试数据中,每个Observation 都有一个State(位置、速度、加速度),而State 有一个关联的Time(状态适用的时间)。到现在为止还挺好。我为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 中做错了吗?我对这一切都很陌生,所以它可能是上述任何一种。非常感谢您提供的任何帮助。

附:我尝试了这里推荐的操作:Trying to avoid a circular reference,但要么我做错了,要么它没有解决我的特定问题。

【问题讨论】:

    标签: database orm sqlalchemy


    【解决方案1】:

    此处有关重新考虑您的用例的其他答案很有价值,您应该考虑这些。然而,就 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()
    

    【讨论】:

    • 这太棒了。您还能去哪里从软件的创建者那里获得有关软件包问题的详细、个性化的答案?谢谢迈克!
    【解决方案2】:

    观察和状态之间存在多对一的关系。所以一个 State 可以有很多 Observation,每个 Observation 都有一个 State。

    State 和 Times 之间也存在多对一关系。所以一个 Time 可以有多个 State,每个 State 都有一个 Time。

    您是正确的,因为问题是对《泰晤士报》观察结果的引用。您强制每个 Time 有一个 Observation,而后者又必须有一个 State,而 State 又必须有一个 Time(然后循环永远重复)。

    要打破这一点,您需要弄清楚您在这些关系中实际试图描绘的内容。如果一个观察有一个状态,它有一个时间,那么观察有一个时间(你可以从状态中得到时间)。

    所以你需要回答的真正问题是:说一个时间有一个观察是什么意思?您将如何在您的应用程序中使用它?

    【讨论】:

    • 谢谢。我想要的是观察和状态之间以及状态和时间之间的一对一。如果我编码错误,请帮助我。我认为uselist=True 是我一对一的方式。我想要 Observations 和 Times 之间的一对多,这意味着 Observation 有许多不同类型的时间。
    • @IanVS - 这是 sqlalchemy docs.sqlalchemy.org/en/rel_0_7/orm/relationships.html 中关系的链接我自己没有使用过,所以我不确定它到底是如何工作的,但这是一个很好的指南建立不同类型的关系。
    • @IanVS - 话虽如此,我认为您仍然会遇到以您描述的方式建立关系的问题。您仍然通过以两种不同的方式将 Observations 连接到 Times 来创建一个圆圈。你要么不需要让States依赖于Times,要么不需要让Observations依赖于Times,或者不需要让Observations依赖于States。我不知道这些对象在您的应用程序中的实际含义,所以我不知道它们之间的关系。但我不明白一个对象如何与一个“时间”相关,但也有很多自己的“时间”。
    • 再次感谢。也许解决这个问题的一种方法是将 Times 表分成两个?一个用于 ObservationTimes,一个用于 StateTimes。我会试试的。现在我将把这个问题留作未回答,以防有人有办法让我把桌子放在一起。
    【解决方案3】:

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

    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

    【讨论】:

    • 感谢您的帮助。我想我没有看到(也许我错过了),是我如何拥有与时间相关的 ObservationInstance(称为观察时间)及其与不同时间(状态时间)相关的状态。我已经简化了我的示例,但实际上我可能有许多“预测的”状态,未来会有时间。所以一个观察将有一个观察时间,多个状态都有自己的状态时间。你上面的设计支持这个吗?
    • @IanVS:我同意 van 的观点,您的模型中的问题似乎在命名中显示出来。时间不是状态,也没有状态;一个状态是什么情况,而不是什么情况,所以它没有时间。为什么没有实体 Time 和 State 与链接到两者的 Occurrence 相关?
    • @reinierpost:一个状态没有相关的有效时间是毫无价值的,那么你为什么说一个状态没有时间呢?也许从我的描述中不清楚。在我的例子中,state 是特定时间的位置、速度和加速度。以这种方式考虑,也许将“valid_time”列添加到state 并让time 表仅与observation 相关更有意义。
    • @IanVS:我相信您错误地使用了“状态”一词。状态是 what 是这种情况(在您的情况下:位置、速度和加速度),而不是 when 它是这种情况。如果模型以尽可能接近其日常含义的方式使用单词,则模型变得更容易理解。我相信您的“循环”引用的出现与您在描述什么是状态时包含一个时间点的原因相同。
    • @reinierpost:我相信我们正在从不同的角度来探讨这个术语。我使用这个词作为“状态向量”的快捷方式,在我的领域中,它被定义为特定有效时间的三维 pos、vel、acc。很抱歉这个词引起了混乱,再次感谢您的帮助。
    猜你喜欢
    • 2022-01-11
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    • 1970-01-01
    • 2012-02-15
    • 2011-03-12
    • 2012-08-10
    • 1970-01-01
    相关资源
    最近更新 更多