Python - Sqlalchemy:如何从多对多关系中获取记录列表

时间:2017-09-10 16:13:04

标签: mysql orm sqlalchemy flask-sqlalchemy python-3.6

我的数据库如下:

+-----+     +------+     +-------+      +-------------+
|Users|-----|Emails|-----|Assoc_T|------|Other_T      |
|     |1   m|      |1   m|       |m    1|             |
|other|     |other |     |other  |      |types        |
|data |     |data  |     |data   |      |other data   |
+-----+     +------+     +-------+      +-------------+

简短描述:我有用户,可能有很多电子邮件,而且这封电子邮件与Other_T有多对多的关系

如果我在SQL炼金术中有User对象:

user = UserModel.query.join(UserModel.Emails).filter_by(Email=id).first()

如何获取当前用户Other_T的唯一列表?

我使用下面的方法,但它看起来并不正确,即使它确实有效。 (太多嵌套循环和对DB的查询)*除非你告诉我必须这样做...

class User(object):
    __DBModel = UserModel()

    @property
    def email(self):
        return self.__DBModel.Emails if self.__DBModel else None

    def __init__(self, id=None):
        if Helpers.is_email(str(id)):
            self.__DBModel = UserModel.query.join(UserModel.Emails).filter_by(Email=id).first() if id else UserModel()
        elif Helpers.is_number(str(id)):
            pass

    # THE QUESTION IS HERE: how to get list of OtherT record based on UserModel defined in __init__? 
    def get_OtherT(self, email=None, other_types=None):
        # get list of email that the user have
        emails = []
        if not email:
            emails = self.email.all()
        else:
            if Helpers.is_email(str(email)):
                emails.append(user.email.filter_by(Email=email).first())
            else:
                return False

        # get list of Other_T_ID in Assoc Table
        O_T_ID = []
        for e in emails:
            assoc_other_t = e.EmailAssociations
            for assoc in assoc_other_t:
                if assoc.Other_T_ID not in O_T_ID:
                    O_T_ID .append(assoc.Other_T_ID)

        # now, after i have the list of the Other_T ID, get the actual Other_T
        ret = []
        for o in O_T_ID :
            ret.append(Other_TModel.query.filter_by(Other_T_ID=o, types=other_types).first()

        return ret

这是我的sql炼金术模型:

的usermodel

class UserModel(db.Model):
    __tablename__ = "Users"

    UserID = db.Column(db.Integer, primary_key=True, autoincrement=True)
    FirstName = db.Column(db.String(255), nullable=False)
    LastName = db.Column(db.String(255), nullable=False)

    # relationships
    Emails = db.relationship('EmailModel', backref='user', lazy='dynamic')

    @orm.reconstructor
    def init_on_load(self):
        pass

    def __init__(self):
        pass

EmailModel

class EmailModel(db.Model):
    __tablename__ = "Emails"

    Email = db.Column(db.String(255), unique=True, primary_key=True, nullable=False)
    UserID = db.Column(db.Integer, db.ForeignKey('users.UserID'), nullable=False)

    # relationships
    EmailAssociations = db.relationship("Assoc_TModel", back_populates="Emails")

    @orm.reconstructor
    def init_on_load(self):
        pass

    def __init__(self):
        pass

Assoc_TModel

class Assoc_TModel(db.Model):
    __tablename__ = 'Assoc_T'

    Other_T_ID = db.Column(
        db.Integer, db.ForeignKey('Other_T.Other_T_ID'),
        primary_key=True, nullable=False
    )
    Email = db.Column(
        db.String(255), db.ForeignKey('Emails.Email'),
        primary_key=True, nullable=False
    )
    EmailVerified = db.Column(db.Boolean, nullable=False, server_default='0')

    # relationships
    Emails = db.relationship("EmailModel", back_populates="EmailAssociations")
    Other_Ts = db.relationship("Other_TModel", back_populates="Other_TAssociations")

    @orm.reconstructor
    def init_on_load(self):
        pass

    def __init__(self):
        pass

Other_TModel

class Other_TModel(db.Model):
    __tablename__ = "Other_T"

    Other_T_ID = db.Column(db.Integer, nullable=False, unique=True, primary_key=True, autoincrement=True)
    Other_T_Type = db.Column(db.Enum('one', 'two', 'three'), nullable=False, server_default='one')
    Other_Data= db.Column(db.String(255), nullable=False)

    # relationships
    Other_TAssociations= db.relationship("Assoc_TModel", back_populates="Other_Ts")
    @orm.reconstructor
    def init_on_load(self):
        pass

    def __init__(self):
        pass

谢谢!

1 个答案:

答案 0 :(得分:1)

您的实施会导致过度获取您不需要的数据。

emails = self.email.all()会将所有电子邮件实体加载到内存中。

assoc_other_t = e.EmailAssociations会为每个EmailAssociations触发额外的SQL查询,这将极大地影响您的效果。

您可以使用subquery来避免中间提取。

class User(object):

    def get_OtherT(self, email=None, other_types=None):
        if email and not Helpers.is_email(email):
            # I'd recommend an exception here
            return False
        # Assoc_TModel subquery
        # SELECT Other_T_ID FROM Other_T_ID
        assoc_sq = session.query(Assoc_TModel.Other_T_ID)

        # Handle email predicate
        if email:
            # Specific email
            assoc_sq = assoc_sq.filter(Assoc_TModel.Email == email)
        else:
            # All emails associated with current user
            email_sq = session.query(EmailModel.Email).\
                filter(EmailModel.UserID == self.__DBModel.UserID)
            assoc_sq = assoc_sq.filter(Assoc_TModel.Email.in_(email_sq))

        # Fetch Other_TModel

        q = session.query(Other_TModel).\
            filter(Other_TModel.Other_T_ID.in_(assoc_sq))

        if other_types:
            # unclear `other_types` is a list?
            q = q.filter(Other_TModel.other_types.in_(other_types))
            # or is a scalar value?
            q = q.filter(Other_TModel.other_types == other_types)

        return q.all()