級聯是在一對多關係中父表與子表進行聯動操作的資料庫術語。因為父表與子表通過外來鍵關聯,所以對父表或子表的增、刪、改操作會對另一張表產生相應的影響。適當的利用級聯可以開發出更優雅、健壯的資料庫程式。本節學習SQLAlchemy中級聯的操作方法。
注意:SQLAlchemy級聯獨立於SQL本身針對外來鍵的級聯定義。即使在資料庫表定義中沒有定義on delete等屬性,也不影響開發者在SQLAlchemy中使用級聯。
1、級聯定義
SQLAlchemy中的級聯通過對父表中的relationship屬性定義cascade引數來實現,程式碼如下:
from sqlalchemy import Table,Column,Integer,ForeignKey,String
from sqlalchemy.orm import relationship,backref
from sqlalchemy.ext.declarative import declarative_base
Base=declarative_base()
class Class(Base):
__tablename__=`class`
class_id=Column(Integer,primary_key=True)
name=Column(String(50))
level=Column(Integer)
address=Column(String(50))
students=relationship("Student",backref="class_",cascade="all")
class Student(Base):
__tablename__=`student`
student_id=Column(Integer,primary_key=True)
name=Column(String(50))
age=Column(Integer)
gender=Column(String(10))
address=Column(String(50))
class_id=Column(Integer,ForeignKey(`class.class_id`))
上述程式碼定義了班級表Class(父表)和學生表Student(子表)。一對多的關係有父表中的relationship屬性students進行定義。relationship中的cascade引數定義了要在該關係上實現的級聯方法為:all。
SQLAlchemy中另外一種設定級聯的方式是在子表的relationship的backref中進行設定。比如上述程式碼可以寫為如下形式,意義保持不變:
from sqlalchemy import Table,Column,Integer,ForeignKey,String
from sqlalchemy.orm import relationship,backref
from sqlalchemy.ext.declarative import declarative_base
Base=declarative_base()
class Class(Base):
__tablename__=`class`
class_id=Column(Integer,primary_key=True)
name=Column(String(50))
level=Column(Integer)
address=Column(String(50))
class Student(Base):
__tablename__=`student`
student_id=Column(Integer,primary_key=True)
name=Column(String(50))
age=Column(Integer)
gender=Column(String(10))
address=Column(String(50))
class_id=Column(Integer,ForeignKey(`class.class_id`))
class_=relationship("Class",backref="students",cascade="all")
上述程式碼沒有在父表Class中設定relationship和cascade,而是在子表中設定了。
SQLAlchemy中可選的cascade取值範圍如下表所示:
可選值 | 意義 |
---|---|
save-update | 當一個父物件被新增到session中時,該物件當時關聯的子物件也自動被新增到session中。 |
merge | Session.merge是一個對資料庫物件進行新增或更新的辦法。cascade取值為merge時的意義為:當父物件進行merge操作時,該物件當時關聯的子物件也會被merge |
expunge | Session.expunge是一種將物件從session中移除的方法。cascade取值為expunge時的意義為:當父物件進行了expunge操作時,該物件當時關聯的子物件也會被從session中刪除。 |
delete | 當父物件被delete時,會自動將該子物件刪除 |
delete-orphan | 當子物件不再與任何父物件關聯時,會自動將該子物件刪除 |
refresh-expire | Session.expire是一種設定物件已過期、下次引用時需要從資料庫即時讀取的方法。cascade取值為refredh-expire時的意義為:當父物件進行了expire操作時,該物件當時關聯的子物件也進行expire操作。 |
all | 是一個集合值,表示:save-update、merge、refresh-expire、expunge、delete同時被設定 |
多個cascade屬性可以通過逗號分隔並同時賦值給cascade。例如:
students=relationship("Student",backref="class_",cascade="save-update,merge,expunge")
在預設情況下,任何relationship的級聯屬性都被設定為cascade=”save-update,merge”。下面就常用的引數:save-update、delet、delete-orphan的功能進行舉例說明。
2、save-update級聯
save-update級聯是指當一個父物件被新增到session中時,該物件當時關聯的子物件也自動被新增到session中。
通過如下程式碼建立一個父物件class和兩個子物件students1與students2
class_=Class()
student1,student2=Student(),Student()
class_.students.append(student1)
class_.students.append(student2)
如果父子級聯關係包含save-update,則只需要將父物件儲存到session中,子物件會自動被儲存。
session.add(class_)
if student1 in session:
print("The student1 has been added too!")
上面程式碼將會列印:”The student1 has been added too!”
技巧:”in“語句可以判斷某物件是否被關聯到了session中。已被關聯的物件在session被commit時會被寫入到視訊庫中。
即使父物件已經被新增到session中,新關聯的子物件仍然可以被新增:
class_=Class()
session.add(class_)
students3=Student()
if student3 in session:
print("The student3 is added before append to the class_!")
class_.students.append(students)
if student1 in session:
print("The student3 is added after append to the class_!")
這段程式碼列印”The student3 is added after append to the class_!“
3、delete級聯
顧名思義,delete級聯是指當父物件被從session中刪除時,其關聯的子物件也自動被從session中delete。通過一個例子演示delete的作用,假設資料庫中class表和students表的內容如下表所示:
class表:
class_id | name | level | address |
---|---|---|---|
1 | 三年二班 | 3 | 理想路520號1樓 |
2 | 五年一班 | 5 | 理想路520號3樓 |
3 | 五年二班 | 5 | 理想路520號3樓 |
student表:
student_id | class_id | name | age | gender | address | contactor |
---|---|---|---|---|---|---|
1 | 1 | 理想 | 10 | 男 | 靜安區 | Null |
2 | 1 | mark | 10 | 女 | 靜安區 | Null |
3 | 1 | 小馬哥 | 9 | 女 | 閘口區 | 張三 |
4 | 2 | 張苗 | 10 | 男 | 寶山區 | NULL |
5 | 2 | 小黑 | 12 | 女 | 靜安區 | 李四 |
6 | 2 | 喵喵 | 11 | 男 | 閘北區 | NULL |
7 | 1 | 韓永躍 | 10 | 男 | 靜安區 | NULL |
8 | 3 | 小鏡鏡 | 12 | 男 | 閘北區 | NULL |
9 | 3 | 小鏡子 | 12 | 女 | 寶山區 | NULL |
從例表中可知,系統中有3個班級,他們分別有4、3、2個學生。如果SQLAlchemy中沒有把它們的relationship的cascade設定為delete,則刪除父表內容不會刪除相應的子表內容,而是把子表的相應外來鍵設定為空。比如:
class_=session.query(Class).filter(name="三年二班").first() #三年二班的class_id為1
session.delete(class_) #刪除class_id為1的班級
當cascade不包含delete時,上述程式碼中的delete語句相當於執行了如下SQL語句:
UPDATE student SET class_id=None WHERE class_id=1;
DELETE FROM class WHERE class_id=1;
COMMIT;
執行後資料表class和student的內容變化如下所示:
class表:
class_id | name | level | address |
---|---|---|---|
2 | 五年一班 | 5 | 理想路520號3樓 |
3 | 五年二班 | 5 | 理想路520號3樓 |
student表:
student_id | class_id | name | age | gender | address | contactor |
---|---|---|---|---|---|---|
1 | NULL | 理想 | 10 | 男 | 靜安區 | Null |
2 | NULL | mark | 10 | 女 | 靜安區 | Null |
3 | NULL | 小馬哥 | 9 | 女 | 閘口區 | 張三 |
4 | 2 | 張苗 | 10 | 男 | 寶山區 | NULL |
5 | 2 | 小黑 | 12 | 女 | 靜安區 | 李四 |
6 | 2 | 喵喵 | 11 | 男 | 閘北區 | NULL |
7 | 1 | 韓永躍 | 10 | 男 | 靜安區 | NULL |
8 | 3 | 小鏡鏡 | 12 | 男 | 閘北區 | NULL |
9 | 3 | 小鏡子 | 12 | 女 | 寶山區 | NULL |
此時將表定義中的relationship的cascade屬性設定為delete:
students=relationship("Student",backref="class",cascade="delete")
現在通過如下語句刪除“五年一班”:
class_=session.query(Class).filter(name="五年一班").first() #五年一班的class_id為2
session.delete(class_) #刪除class_id為2的班級
當cascade包含“delete”時,上述程式碼中的delete語句相當於執行了如下SQL語句:
DELETE FROM student WHERE class=2;
DELETE FROM class WHERE class=2;
COMMIT;
執行後資料庫表class和student的內容變化如下表所示:
class表:
class_id | name | level | address |
---|---|---|---|
3 | 五年二班 | 5 | 理想路520號3樓 |
student表:
student_id | class_id | name | age | gender | address | contactor |
---|---|---|---|---|---|---|
1 | NULL | 理想 | 10 | 男 | 靜安區 | Null |
2 | NULL | mark | 10 | 女 | 靜安區 | Null |
3 | NULL | 小馬哥 | 9 | 女 | 閘口區 | 張三 |
7 | NULL | 韓永躍 | 10 | 男 | 靜安區 | NULL |
8 | 3 | 小鏡鏡 | 12 | 男 | 閘北區 | NULL |
9 | 3 | 小鏡子 | 12 | 女 | 寶山區 | NULL |
4、delete-orphan級聯
delete-orphan級聯是指當子物件不再與任何父物件關聯時,會自動將該子物件刪除。設定父表與子表的relationship中的cascade包含“delete-orphan”:
students=relationship("Student",backref="class",cascade="delete-orphan")
通過如下程式碼將於班級“五年一班”關聯的學生全部脫離:
class_=session.query(Class).filter(name="五年二班").first()
unattachedStudent=[]
while len(class_.students)>0:
unattachedStudent.append(class_.students.pop()) #與父物件脫離
session.commit #顯示地提交事務
上述程式碼中沒有顯示地刪除任何學生,但由於使用了delete-orphan級聯,所以被脫離出班級物件的學生會在session事務提交時會自動從資料庫中刪除。程式碼執行後資料庫表中的內容的變化:
class表:
class_id | name | level | address |
---|---|---|---|
3 | 五年二班 | 5 | 理想路520號3樓 |
student表:
student_id | class_id | name | age | gender | address | contactor |
---|---|---|---|---|---|---|
1 | NULL | 理想 | 10 | 男 | 靜安區 | Null |
2 | NULL | mark | 10 | 女 | 靜安區 | Null |
3 | NULL | 小馬哥 | 9 | 女 | 閘口區 | 張三 |
7 | NULL | 韓永躍 | 10 | 男 | 靜安區 | NULL |