SQLAlchemy Join Query 得到的不同数据行相同数据是同一个实例

681 阅读2分钟

在开发的时候遇到了一个很大的坑(还是因为是我不了解 SQLAlchemy 吧😷), 记录一下免得往后再踩...

事情是这样的, 有三张表, 快速地模拟一下表结构:

student表
|------+--------+----------+--------------|
|  id  +  name  +  gender  +  deleted_at  |
|------+--------+----------+--------------|

course表
|------+---------+--------------+--------------|
|  id  +  title  +  teacher_id  +  deleted_at  |
|------+---------+--------------+--------------|

student_course_rel表
|------+--------------+-------------+--------------|
|  id  |  student_id  +  course_id  +  deleted_at  |
|------+--------------+-------------+--------------|

这里模拟了一个场景需求, 需要列出某位老师所教授的不同课程的学生列表, 并且要支持全量更新(更新的时候传入的列表为全部学生列表(新增和删除)) 这个时候我要 Join 三张表, 并且同一个学生对应的不同课程的 rel id 也得记录, 这样我就可以知道那些是新增的那些是删除的. 也就是说, 我需要将用户和课程的 rel id 进行绑定. 所以, 我就用这条语句进行 Query:

teacher_id = 'teacher_id'
query_result = db.session.query(Course, Student, StudentCourseRel)\
    .join(StudentCourseRel, Course.id == StudentCourseRel.course_id)\
    .join(Student, StudentCourseRel.student_id == Student.id)\
    .filter(Course.teacher_id == teacher_id, Course.deleted_at.is_(None),
            Student.deleted_at.is_(None), StudentCourseRel.deleted_at.is_(None))\
    .all()
# query 出来的结果大致是这样的
[(course_a, stu_1, rel_1), (course_a, stu_2, rel_2), (course_b, stu_1, rel_3), (course_c, stu_3, rel_4)]

这个时候, query 出来的结果确实是我想要的, 每个用户每个课程都会有一条记录. 列表没有任何问题. 但是在更新的时候就出 bug 了.
问题就出在student, course, rel_id绑定这一步用户. 和课程关系的绑定我是遍历数据列表, 并且将 rel id 作为 student 的一个属性.

# 处理数据
result = set()
for item in query_result:
    course, student, rel = item
    if course not in result:
        course.students = []
        result.add(container)
    user.rel_id = rel.id
    course.users.append(user)
return result

就上例而言, SQLAlchemy 得到的数据中, course_a 所对应的 stu_1 和 course_b 所对应的 stu_1 是同一个实例, 也就是说在遍历并且进行赋值 rel_id 的时候, 后项会直接覆盖前项, 所以会造成数据的混乱.

这里就暴露出来一个点, 也就是说 SQLAlchemy 在处理 Join 后得到的同样数据是采用 同一个实例 对不同数据行进行拼接.