在开发的时候遇到了一个很大的坑(还是因为是我不了解 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 后得到的同样数据是采用 同一个实例 对不同数据行进行拼接.