Flask-SQLAlchemy查询连接关系表

20

我正在使用Flask和SQLAlchemy构建一个应用程序。 我基本上有3个表:用户,友谊和最好的朋友:

用户可以有很多朋友,但只有一个最好的朋友。 所以我希望我的模型是关联的。 “一对多”是“用户”和“友谊”之间的关系,“一对一”是“用户”和“最好的朋友”之间的关系。

这是我的模型:

from app import db
from sqlalchemy.orm import relationship, backref
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

class users(db.Model):

    __tablename__ = "Users"

    id = db.Column(db.Integer, primary_key=True)
    userName = db.Column(db.String, nullable=False)
    userEmail = db.Column(db.String, nullable=False)
    userPhone = db.Column(db.String, nullable=False)
    userPass = db.Column(db.String, nullable=False)

    friendsR = db.relationship('friendships', backref='friendships.friend_id', primaryjoin='users.id==friendships.user_id', lazy='joined')

    def __init__(self, userName, userEmail, userPhone, userPass):
        self.userName = userName
        self.userEmail = userEmail
        self.userPhone = userPhone
        self.userPass = userPass

    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.id, self.userName, self.userEmail, self.userPhone)

class friendships(db.Model):

    __tablename__ = "Friendships"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, ForeignKey('Users.id'), nullable=False)
    friend_id = db.Column(db.Integer, ForeignKey('Users.id'), nullable=False)

    userR = relationship('users', foreign_keys='friendships.user_id')
    friendR = relationship('users', foreign_keys='friendships.friend_id')

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


    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.user_id, self.friend_id)


class bestFriends(db.Model):

    __tablename__ = "BestFriends"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, ForeignKey('Users.id'), nullable=False)
    best_friend_id = db.Column(db.Integer, ForeignKey('Users.id'), nullable=False)

    user = relationship('users', foreign_keys='bestFriends.user_id')
    best_friend = relationship('users', foreign_keys='bestFriends.best_friend_id')


    def __init__(self, user_id, best_friend_id):

        self.user_id = user_id
        self.best_friend_id = best_friend_id


    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.user_id, self.best_friend_id)

我需要能够查询已登录用户的好友列表,以及如果存在的话,该用户最好的朋友。我还需要对结果进行分页:

这是我的app.py函数,用于显示用户的好友:

@app.route('/friendList<int:page>', methods=['GET', 'POST'])
@app.route('/friends')
def friendList(page=1):

if not session.get('logged_in'):
        return render_template('login.html')
    else:
        userID = session['user_id']

        userList = users.query.join(friendships).add_columns(users.id, users.userName, users.userEmail, friendships.user_id, friendships.friend_id).filter(users.id == friendships.friend_id).filter(friendships.user_id == userID).paginate(page, 1, False)

        return render_template(
            'friends.html', userList=userList)

以下是Jinja代码的部分:

{% extends "layout.html" %}
{% block body %}

<div id="pagination">
{% if userList.has_prev %}
        <a href="{{ url_for('friendList', page=userList.prev_num) }}">Back</a>
{% endif %} 

{% if userList.has_next %}
        <a href="{{ url_for('friendList', page=userList.next_num) }}">Next</a>
{% endif %}
</div>

<div style="clear:both;"></div>

<div id="innerContent">
{% if userList %}
    {% for friends in userList %}
                    <div class="contentUsers">
                        {{ friends.userName }}
                    </div>

                    <br><br><br><br>

    {% endfor %}{% else %}<div>No friends</div>
{% endif %} 

</div>
  {% endblock %}

如果我这样查询:

userList = db.session.query(users,friendships).filter(users.id == friendships.friend_id).filter(friendships.user_id == userID).paginate(page, 1, False)

我遇到了这个错误:

InvalidRequestError: Could not find a FROM clause to join from. Tried joining to <class 'models.friendships'>, but got: Can't determine join between 'Users' and 'Friendships'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.

如果我这样查询:
userList = users.query.join(friendships, users.id==friendships.user_id).add_columns(users.id, users.userName, users.userEmail, friendships.user_id, friendships.friend_id).filter(users.id == friendships.friend_id).filter(friendships.user_id == userID).paginate(page, 1, False)

我遇到了以下错误:

TypeError: 'Pagination' object is not iterable

我仍然认为这个后续查询是正确的方法,但我认为我的表格之间的关系/外键存在问题!!!

如果在Jinja侧将.items添加到循环中:

{% if userList.items %}
{% for friends in userList.items %}

                <div class="contentUsers">
                    {{ friends.userName }}
                </div>

                <br><br><br><br>

{% endfor %}{% else %}<div>No friends</div>

{% endif %}

它根本没有循环,只是显示“没有朋友”的else语句。

请参考以下问题,它们可能会指引您朝着正确的方向前进:https://dev59.com/F2Ml5IYBdhLWcg3wXF9f https://dev59.com/P3bZa4cB1Zd3GeqPBQsc - Matt Healy
你好Matthewh,感谢您的评论。现在我明白了为什么我能够将paginate()应用于我的某些查询而其他查询则不行。但是我仍然不知道如何使用Flask-SQLAlchemy执行我需要的联接查询。我有一个简单的查询与paginate一起工作,例如:"userList = users.query.paginate(page, 5, False)",但我不知道如何执行Flask-SQLAlchemy版本(带有paginate)的查询,例如:"SELECT users.userId,users.name,users.email,friends.userId,friendId FROM users INNER JOIN friends ON users.userId=friends.friendId WHERE friends.userId=1"。 - Al Ex Tsm
2个回答

71

错误信息告诉您,SQLAlchemy 无法确定如何连接两个表 usersfriendships,因为它们之间有不止一个外键链接。您需要明确定义连接条件。

尝试:

userList = users.query\
    .join(friendships, users.id==friendships.user_id)\
    .add_columns(users.userId, users.name, users.email, friends.userId, friendId)\
    .filter(users.id == friendships.friend_id)\
    .filter(friendships.user_id == userID)\
    .paginate(page, 1, False)

1
很抱歉,Matthewh。我太累了,没有意识到我使用的列名是来自我的MySql示例而不是我的SqlAlchemy模型:这将是您为我编写的查询的正确列名(仍会给我添加到原始问题中的错误):userList = users.query.join(friendships,users.id == friendships.user_id).add_columns(users.id, users.userName, users.userEmail, friendships.user_id, friendships.friend_id).filter(users.id == friendships.friend_id).filter(friendships.user_id == userID).paginate(page, 1, False) - Al Ex Tsm
你能解释一下 .add_columns() 是什么意思吗?在连接之后需要吗? - Rylan Schaeffer

1

我看完Matthewh的建议并休息后,似乎找到了最终解决方案:

我的模型:

from app import db
from sqlalchemy.orm import relationship, backref
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

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

    id = db.Column(db.Integer, primary_key=True)
    userName = db.Column(db.String, nullable=False)
    userEmail = db.Column(db.String, nullable=False)
    userPhone = db.Column(db.String, nullable=False)
    userPass = db.Column(db.String, nullable=False)

    def __init__(self, userName, userEmail, userPhone, userPass):
        self.userName = userName
        self.userEmail = userEmail
        self.userPhone = userPhone
        self.userPass = userPass

    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.id, self.userName, self.userEmail, self.userPhone)


 class friendships(db.Model):
    __tablename__ = "Friendships"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
    friend_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
    userR = db.relationship('users', foreign_keys='friendships.user_id')
    friendR = db.relationship('users', foreign_keys='friendships.friend_id')

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

    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.user_id, self.friend_id)


class bestFriends(db.Model):
    __tablename__ = "BestFriends"

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
    best_friend_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
    user = db.relationship('users', foreign_keys='bestFriends.user_id')
    best_friend = db.relationship('users', foreign_keys='bestFriends.best_friend_id')

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

    def __repr__(self):
        return '{}-{}-{}-{}'.format(self.user_id, self.best_friend_id)

我的app.py函数(显示已登录用户的朋友):

@app.route('/friendList<int:page>', methods=['GET', 'POST'])
@app.route('/friends')
  def friendList(page=1):
  if not session.get('logged_in'):
     return render_template('login.html')
  else:
     userID = session['user_id']
     userList = users.query.join(friendships, users.id==friendships.user_id).add_columns(users.id, users.userName, users.userEmail, friendships.id, friendships.user_id, friendships.friend_id).filter(friendships.friend_id == userID).paginate(page, 1, False)
     return render_template('friends.html', userList=userList)

而 'friends.html' 的 Jinja 部分:

{% extends "layout.html" %}

{% block body %}
    <div id="pagination">
        {% if userList.has_prev %}
            <a href="{{ url_for('friendList', page=userList.prev_num)}}">Back</a>
        {% endif %}

        {% if userList.has_next %}
            <a href="{{ url_for('friendList', page=userList.next_num)}}">Next</a>
        {% endif %}
    </div>

    <div style="clear:both;"></div>

    <div id="innerContent">
    {% if userList.items %}
        {% for friends in userList.items %}
            <div class="contentUsers">
                {{ friends.userName }}
            </div>
            <br><br><br><br>
        {% endfor %}
        {% else %}
            <div>No friends</div>
        {% endif %}
    </div>
{% endblock %}

这会给我一个类似这样的对象(在userList.items中的朋友)。
(2-Carlos-carlos@carlos.com-900102030, 2, u'Carlos', u'carlos@carlos.com', 2, 2, 1)

我希望看到的是这样的结果: |用户ID|用户名|用户邮箱|用户电话|好友关系ID|好友关系中的用户ID|好友关系中的登录用户ID|
所以我有以下疑问/问题:
我没有完全理解查询结果对象的结构: -重复的用户ID、姓名和电子邮件 -第二个名称和电子邮件前面的'u'是什么意思
我没有完全理解关系模型结构:
  • why is the following NOT required in the 'users' class of the database model:

    #friendsR = db.relationship('friendships', backref='friendships.friend_id', primaryjoin='users.id==friendships.user_id', lazy='joined')
    

我的数据库模型是否符合此答案中发布的标准化关系模型,或者我应该进行改进?


2
为什么不只定义两个模型(用户和友谊),并添加一个布尔条目来表示“最好的朋友”友谊?这将大大简化您的查询。如果您想找到用户的最佳朋友,您只需要添加一个“是最好的朋友等于真”的过滤器即可。 - Patrick Da Silva

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接