flask的orm框架(SQLAlchemy)-一對多查詢以及多對多查詢

- 一對多,多對多是什么?

一對多。例如,班級與學生,一個班級對應多個學生,或者多個學生對應一個班級。
多對多。例如,學生與課程,可以有多個學生修同一門課,同時,一門課也有很多學生。

- 一對多查詢

如果一個項目,有兩張表。分別是班級表,學生表。

在設計數(shù)據(jù)表時,我們給學生表設置一個外鍵,指向班級表的 id 。

sqlalchemy 模板創(chuàng)建表的代碼:

from flask import Flask, render_template, request, flash, redirect
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__,static_folder="static",template_folder="templates")

# 設置數(shù)據(jù)庫連接屬性
app.config['SQLALCHEMY_DATABASE_URI'] = '×××'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 實例化 ORM 操作對象
db = SQLAlchemy(app)

# 班級表
class Classes(db.Model):
    __tablename__ = "classes"
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(20),nullable=False,unique=True)

# 學生表
class Students(db.Model):
    __tablename__ = "students"
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(40),nullable=False)
    cls_id = db.Column(db.Integer,db.ForeignKey("classes.id"))    # 注意要寫成(表名.字段名)
注意:代碼中的‘xxx’需要改成你的數(shù)據(jù)庫URI

創(chuàng)建完表,插入完數(shù)據(jù)后。

如果我們知道學生的學號,要查學生班級的名稱,應該怎么操作呢?

現(xiàn)在可以用一種比較麻煩的方達查詢:

cls_id = Students.query.filter(Student.id == 'xxx').first()
cls = Classes.query.filter(Classes.id == cls.id).first()
print(cls.name)

這樣的方法太麻煩了,有沒有簡單的辦法?

上面創(chuàng)建表的代碼,在18行可以插入一條語句。

relate_student = db.relationship("Students",backref='relate_class',lazy='dynamic')

其中realtionship描述了StudentsClasses的關系。在此文中,第一個參數(shù)為對應參照的類"Students"

  • 第二個參數(shù)backref為類Students申明新屬性的方法
  • 第三個參數(shù)lazy決定了什么時候SQLALchemy從數(shù)據(jù)庫中加載數(shù)據(jù)
  • 如果設置為子查詢方式(subquery),則會在加載完Classes對象后,就立即加載與其關聯(lián)的對象,這樣會讓總查詢數(shù)量減少,但如果返回的條目數(shù)量很多,就會比較慢
  • 另外,也可以設置為動態(tài)方式(dynamic),這樣關聯(lián)對象會在被使用的時候再進行加載,并且在返回前進行過濾,如果返回的對象數(shù)很多,或者未來會變得很多,那最好采用這種方式

如果一大堆理論看不明白,那么知道怎么用就可以了。

如果知道學生的姓名,想知道班級的名稱,可以這樣查:

stu = Students.query.filter(Students.name == 'xxx').first()
stu.relate_class.name    # stu.relate_class 會跳到 classes 表

如果知道班級的名稱,想返回全部學生的名字的列表,可以這樣查:

cls = Classes.query.filter(Classes.name == 'xxx').first()
cls.relate_student.first().name    # cls.relate_stu 會跳到 students 表
注意:在設置db.relationship的一方查詢時需要用first()或者all()才能獲取到對象,所以一對多的關系中盡可能把外鍵設置在多的一方,db.relationship設置在‘一’的一方。

- 多對多查詢

假設一堆學生選了不同的課程,這就是多對多關系。

tb_student_course = db.Table('tb_student_course',
                             db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
                             db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
                             )


class Student(db.Model):
    __tablename__ = "students"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)

   # 關聯(lián)屬性,多對多的情況,可以寫在任意一個模型類中
    relate_courses = db.relationship('Course', secondary=tb_student_course,
                              backref='relate_student',
                              lazy='dynamic')


class Course(db.Model):
    __tablename__ = "courses"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)

添加測試數(shù)據(jù):

# 添加測試數(shù)據(jù)

    stu1 = Student(name='張三')
    stu2 = Student(name='李四')
    stu3 = Student(name='王五')

    cou1 = Course(name='物理')
    cou2 = Course(name='化學')
    cou3 = Course(name='生物')

    stu1.courses = [cou2, cou3]    # 記得要添加關系
    stu2.courses = [cou2]
    stu3.courses = [cou1, cou2, cou3]

    db.session.add_all([stu1, stu2, stu2])
    db.session.add_all([cou1, cou2, cou3])

    db.session.commit()

要查某個學生修的全部課程,修了某個課程的全部學生:

for course in stu1.relate_courses.all():
    print(course.name)

for student in cou2.relate_student.all():
    print(student)

特殊(自關聯(lián)/自引用)

在 web 開發(fā)中還有一種情況,就是同一張表通過關系列表實現(xiàn)自關聯(lián)。常見例子入微博用戶的 “關注者/粉絲”(followed/followers)。


數(shù)據(jù)庫示意圖

實現(xiàn)代碼:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate


app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///demo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
migrate = Migrate(app, db)


followers = db.Table('followers',
    db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False)
    followed = db.relationship(
        # User 表示關系當中的右側實體(將左側實體看成是上級類)
        # 由于這是自引用關系,所以兩側都是同一個實體。
        'User', 

        # secondary 指定了用于該關系的關聯(lián)表
        # 就是使用我在上面定義的 followers
        secondary=followers,

        # primaryjoin 指明了右側對象關聯(lián)到左側實體(關注者)的條件 
        # 也就是根據(jù)左側實體查找出對應的右側對象
        # 執(zhí)行 user.followed 時候就是這樣的查找
        primaryjoin=(followers.c.follower_id == id),

        # secondaryjoin 指明了左側對象關聯(lián)到右側實體(被關注者)的條件 
        # 也就是根據(jù)右側實體找出左側對象
        # 執(zhí)行 user.followers 時候就是這樣的查找
        secondaryjoin=(followers.c.followed_id == id),

        # backref 定義了右側實體如何訪問該關系
        # 也就是根據(jù)右側實體查找對應的左側對象
        # 在左側,關系被命名為 followed
        # 在右側使用 followers 來表示所有左側用戶的列表,即粉絲列表
        backref=db.backref('followers', lazy='dynamic'), 
        lazy='dynamic'
        )      

@app.shell_context_processor
def make_shell_context():
    return {'db': db, 'User': User}

關聯(lián)關系也可以分開寫

class Follow(db.Model):
    __tablename__ = 'follows'
    followed_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    follower_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)


class Follow(db.Model):
    __tablename__ = 'follows'
    followed_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    follower_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)
    followed = db.relationship('Follow', foreign_keys=[Follow.follower_id],
                               backref=db.backref('follower',lazy='joined'),
                               lazy='dynamic',
                               cascade='all, delete-orphan')
    follower = db.relationship('Follow', foreign_keys=[Follow.followed_id],
                               backref=db.backref('followed',lazy='joined'),
                               lazy='dynamic',
                               cascade='all, delete-orphan')

假設 followers 表如下數(shù)據(jù):

follower_id followed_id
1 2
1 3
2 3

這表格表示 id 為 1 的用戶(稱為:user1)關注了(followed) id 為 2、3 的用戶(稱為:user2、user3),user2 關注了 user3。
反過來說,user1 是 user2、user3 的關注者(followers)。
當我們執(zhí)行 user1.followed 的操作,是根據(jù)左側實體查找出對應的右側對象,查詢條件為 followers.c.follower_id == 1,得到 user2 和 user3,也就是 user1 關注的用戶。

>>> u1 = User.query.get(1)
>>> u1.followed.all()
[<User 2>, <User 3>]

當我們執(zhí)行 user3.followers 的操作,是根據(jù)右側實體找出左側對象,查詢條件為 followers.c.followed_id == 3,得到 user1、user2,也就是 use3 的關注者。

>>> u3 = User.query.get(3)
>>> u3.followers.all()
[<User 1>, <User 2>]

關注和取消關注
user1 關注 user2:

user1.followed.append(user2)

user1 取消關注 user2:

user1.followed.remove(user2)

更有效的方法是我們在 User 類的方法里集成關注和取消關注:

class User(UserMixin, db.Model):
    #...

    def is_following(self, user):
        """
        檢查是否已關注 user
        """
        return self.followed.filter(
            followers.c.followed_id == user.id).count() > 0

    def follow(self, user):
        if not self.is_following(user):
            self.followed.append(user)

    def unfollow(self, user):
        if self.is_following(user):
            self.followed.remove(user)
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容