首页 > 解决方案 > 不同属性上的双向外键,一对一和一对多

问题描述

我有两个模型:UserReferralKey,有这些要求:

根据这个问题的答案,最好的解决方案似乎是ReferralKeyUser. 其他两个的解决方案需要外键,而且看起来真的很乱——把表纠缠在一起,我还不如把它们放在同一个表中。

第一个的解决方案如下所示:

def User(model):
    id = Column(BigInteger(), autoincrement=True, primary_key=True)
    referral_key = relationship('ReferralKey', uselist=False)
    ...
    def __init__(self):
        self.referral_key = ReferralKey()

def ReferralKey(model):
    id = Column(BigInteger(), autoincrement=True, primary_key=True)
    user_id = Column(BigInteger(), ForeignKey('user.id', ondelete='SET NULL'), nullable=True)

这按预期工作,并解决了第一点和第三点。尝试解决第二个问题时会出现问题。这(出于某种原因)需要一个新的外键 in User,这需要relationship在两者中声明 aUser并且ReferralKey(我猜)消除外键的歧义:

def User(model):
    id = Column(BigInteger(), autoincrement=True, primary_key=True)
    referral_key = relationship('ReferralKey', uselist=False)
    referrer_id = Column(BigInteger(), ForeignKey('referral_key.id', ondelete='SET NULL'))
    referrer = relationship('ReferralKey', foreign_keys=['referrer_id'], backref='used_by')
    ...
    def __init__(self):
        self.referral_key = ReferralKey()

def ReferralKey(model):
    __tablename__='referral_key'
    id = Column(BigInteger(), autoincrement=True, primary_key=True)
    user_id = Column(BigInteger(), ForeignKey('user.id', ondelete='SET NULL'), nullable=True)
    user = relationship('User', foreign_keys=['user_id'])

relationship我已经尝试了and的所有不同排列ForeignKey,并且总是得到相同的错误:

sqlalchemy.exc.CircularDependencyError: Can't sort tables for DROP; an unresolvable foreign key dependency exists between tables: referral_key, users.  Please ensure that the ForeignKey and ForeignKeyConstraint objects involved in the cycle have names so that they can be dropped using DROP CONSTRAINT.

最终,我的问题是我只是不明白我在做什么。为什么我需要改变User桌子才能跟踪桌子上的东西ReferralKey?声明的目的是什么relationship——为什么没有这个声明会模棱两可?如果User有一个外键引用ReferralKey并且ReferralKey有一个外键引用——并且在删除的情况下User应该设置其中任何一个NULL,为什么 SQL 需要比这更多的信息?

为什么我不能拥有:

def User(model):
    id = Column(BigInteger(), autoincrement=True, primary_key=True)
    def __init__(self):
        ReferralKey(user_id=self.id)

def ReferralKey(model):
    __tablename__='referral_key'
    id = Column(BigInteger(), autoincrement=True, primary_key=True)
    user_id = Column(BigInteger(), ForeignKey('user.id', ondelete='SET NULL'), nullable=True)
    used_by = [list of user IDs]

    def __init__(self, user_id):
        if user_id:
            self.user_id == user_id

这对我来说感觉更干净、更直观。如果我想添加(或删除!)推荐密钥,我几乎不必担心添加东西,User因为它主要独立于推荐密钥的功能。为什么我需要在user表中添加一列来跟踪我想要ReferralKey跟踪的内容?

基本上,我完全不知道这一点。有人介意帮助我吗?

标签: pythonpostgresqlsqlalchemy

解决方案


试试下面的。

它依赖于以下信息:

  • 用户在注册时创建自己的主密钥。
  • 用户在注册时可以注册从属密钥,但可能不会。

这样外键保留在一个表中,但您需要区分它们..

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

Base= declarative_base()

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    own_referral_id = Column(Integer, ForeignKey('referral_key.id'))
    own_referral_key = relationship('ReferralKey', foreign_keys=[own_referral_id], back_populates='owner')
    signup_referral_id = Column(Integer, ForeignKey('referral_key.id'))
    signup_referral_key = relationship('ReferralKey', foreign_keys=[signup_referral_id], back_populates='signups')
    def __init__(self, **kwargs):
        self.own_referral_key = ReferralKey()
        super().__init__(**kwargs)

class ReferralKey(Base):
    __tablename__ = "referral_key"
    id = Column(Integer, primary_key=True)

    owner = relationship('User', foreign_keys=[User.own_referral_id], back_populates='own_referral_key', uselist=False)
    signups = relationship('User', foreign_keys=[User.signup_referral_id], back_populates='signup_referral_key', uselist=True)


e = create_engine("sqlite://")
Base.metadata.create_all(e)

s = Session(e)

u1 = User()
s.add(u1)
s.commit()

u2 = User(signup_referral_id = u1.own_referral_id)
u3 = User(signup_referral_id = u1.own_referral_id)

s.add(u2)
s.add(u3)
s.commit()

print(u1.own_referral_key.signups)

推荐阅读