【问题标题】:One-to-many relationships SQLAlchemy that depend on each other相互依赖的一对多关系 SQLAlchemy
【发布时间】:2012-05-04 13:52:22
【问题描述】:

我正在尝试让以下模型一起工作。首先场景如下:

  1. 一个用户可以有多个邮箱,但每个邮箱只能关联一个用户;
  2. 每个用户只能有一个主电子邮件地址(把它想象成他们当前的电子邮件地址)。

电子邮件地址是用户的 ID,因此他们必须始终拥有一个,但当他们更改它时,我想跟踪他们过去使用过的其他地址。到目前为止,设置是有一个帮助表user_emails,它在电子邮件和用户之间建立联系,我听说在使用声明性 SQLAlchemy 方法时不应该将其设置为一个类(尽管我不知道为什么)。另外,我是否认为我需要使用use_alter=True,因为users 表在插入之前不知道外键email_id?

models.py 看起来像这样:

"""models.py"""
user_emails = Table('user_emails', Base.metadata,
                Column('user_id', Integer, ForeignKey('users.id'),
                       primary_key=True),
                Column('email', String(50), ForeignKey('emails.address'),
                       primary_key=True))

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, Sequence('usr_id_seq', start=100, increment=1), 
                primary_key=True)
    email_id = Column(String(50),
                      ForeignKey('emails.address', use_alter=True, name='fk_email_id'),              
                      unique=True, nullable=False)
    first = Column(String(25), unique=True, nullable=False)
    last = Column(String(25), unique=True, nullable=False)

    def __init__(self, first, last):
        self.first = first
        self.last = last

class Email(Base):
    __tablename__ = 'emails'
    address = Column(String(50), unique=True, primary_key=True)
    user = relationship(User, secondary=user_emails, backref='emails')
    added = Column(DateTime, nullable=False)
    verified = Column(Boolean, nullable=False)

    def __init__(self, address, added, verified=False):
        self.address = address
        self.added = added
        self.verified = verified

在我尝试提交数据库之前,一切似乎都很好:

>>> user = models.User("first", "last")
>>> addy = models.Email("example@example.com", datetime.datetime.utcnow())
>>> addy
<Email 'example@example.com' (verified: False)>
>>> user
>>> <User None (active: True)>
>>>
>>> user.email_id = addy
>>> user
>>> <User <Email 'example@example.com' (verified: False)> (active: True)>
>>> Session.add_all([user, addy])
>>> Session.commit()
>>> ...
>>> sqlalchemy.exc.ProgrammingError: (ProgrammingError) can't adapt type 'Email' "INSERT INTO users (id, email_id, first, last, active) VALUES (nextval('usr_id_seq'), %(email_id)s, %(first)s, %(last)s, %(active)s) RETURNING users.id" {'last': 'last', 'email_id': <Email 'example@example.com' (verified: False)>, 'active': True, 'first': 'first'}

所以,我认为我做错了/愚蠢,但我是 SQLAlchemy 的新手,所以我不确定我需要做什么才能正确设置模型。

最后,假设我设置了正确的模型,是否可以添加关系,以便通过加载任意电子邮件对象,我将能够从电子邮件对象中的属性访问拥有它的用户?

谢谢!

【问题讨论】:

    标签: python database database-design sqlalchemy


    【解决方案1】:

    我不熟悉 Python/SQLAlchemy,但这里有一种方法可以在数据库中表示您想要的内容:

    您可以使用延迟约束(如果您的 DBMS 支持它们),或者让 USER.PRIMARY_EMAIL 为空(如上面的模型所示)来中断数据修改周期。


    或者,您可以这样做:

    对属于同一用户的电子邮件进行排序(注意备用键:{USER_ID, ORDER}),并且任何位于该排序之上的电子邮件都可以被视为“主要”。这种方法的好处是它完全避免了循环引用。

    【讨论】:

    • 虽然我认为我的问题已经在上面解决了,但第二种方法很有趣 - 谢谢。
    【解决方案2】:

    您已经有了一个很好的解决方案,一个小的修复将使您的代码正常工作。在下方找到对您的代码的快速反馈:

    • 您需要use_alter=True 吗?不,你实际上不需要那个。如果Email 表的primary_key 是在数据库级别计算的(与基于autoincrement 的主键一样),那么当您有两个相互具有foreign_keys 的表时可能需要它。在您的情况下,您甚至没有,因为您有第三个表,因此对于任何关系组合,SA (sqlalchemy) 将通过插入新电子邮件、用户和关系来解决。
    • 您的代码有什么问题?:嗯,您将Email 的实例分配给User.email_id,它应该只获得email 的值。有两种方法可以解决它:
      1. 直接分配电子邮件。所以将行 user.email_id = addy 更改为 user.email_id = addy.address
      2. 创建一个relationship,然后进行分配(参见下面的代码)。 就个人而言,我更喜欢选项 2。
    • 其他事项:您当前的模型不检查 User.email_id 实际上是 User.emails 之一。这可能是设计使然,否则只需从 [users.id, users.email_id] to [user_emails.user_id, user_emails.email] 添加一个 ForeignKey

    版本 2 的示例代码:

    """ models.py """
    class User(Base):
        __tablename__ = 'users'
        # ...
        email_id = Column(String(50),
                          ForeignKey('emails.address', use_alter=True,
                                     name='fk_email_id'), unique=True,
                          nullable=False)
        default_email = relationship("Email", backref="default_for_user")
    
    """ script """
    # ... (all that you have below until next line)
    # user.email_id = addy.address
    user.default_email = addy
    

    【讨论】:

    • 这是一个很好的答案,谢谢!将Email 分配给User 中的email_id 是我的拼写错误——我想这解释了编程错误异常?我将使用您的解决方案的第二版。 PS - use_alter 是否需要在他们的给定中,正如您所说的那样,关键是地址(而不是自动增量?)。
    • 正如我所提到的,您的代码不需要use_alter=True。
    【解决方案3】:

    http://docs.sqlalchemy.org/en/latest/orm/tutorial.html 中的示例与您想要的很接近。除非我需要多对多关系,否则我从不使用像您的 user_emails 这样的中间连接表。用户到电子邮件应该是一对多的。

    您需要跟踪旧的电子邮件地址吗?将“过时”布尔属性添加到您的 Email 类并对其进行过滤以显示当前或旧的电子邮件地址。

    【讨论】:

    • +1:这实际上是一个很好的观点——没有辅助表的简单 1-N 关系应该足够了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-04
    • 2011-06-26
    • 2012-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多