在開發的時候遇到了一個很大的坑(還是因為是我不瞭解 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 後得到的同樣資料是採用 同一個例項 對不同資料行進行拼接.