一对多的关系SQLAlchemy相互依赖

时间:2012-04-23 22:33:09

标签: python database database-design sqlalchemy

我正在努力让以下模型协同工作。首先,情景如下:

  1. 用户可以拥有多个电子邮件地址,但每个电子邮件地址只能与一个用户关联;
  2. 每位用户只能拥有一个主电子邮件地址(将其视为当前的电子邮件地址)。
  3. 电子邮件地址是用户的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的新手,所以我不确定我需要做些什么来正确设置模型。

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

    谢谢!

3 个答案:

答案 0 :(得分:5)

您已经有了一个非常好的解决方案,一个小修补程序将使您的代码正常工作。请在下面找到有关您的代码的快速反馈:

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

版本2的示例代码:

[users.id, users.email_id] to [user_emails.user_id, user_emails.email]

答案 1 :(得分:3)

我不熟悉Python / SQLAlchemy,但这是表示数据库中所需内容的一种方法:

enter image description here

您要么使用延迟约束(如果您的DBMS支持它们),请将USER.PRIMARY_EMAIL留空(如上面的模型所示)以打破数据修改周期。


或者,您可以这样做:

enter image description here

订购属于同一用户的电子邮件(请注意:{USER_ID,ORDER}上的备用密钥),并且在该订购之上的任何电子邮件都可以被视为“主要”。这种方法的好处在于它完全避免了循环引用。

答案 2 :(得分:1)

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

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