Bài đăng khá cũ, nhưng tôi chỉ dành một hoặc hai giờ cho việc này, vì vậy tôi muốn chia sẻ phát hiện của mình, đặc biệt là vì một số nhận xét khác được liệt kê không hoàn toàn đúng.
TL; DR
Đặt cho bảng con một bảng ngoại lai hoặc sửa đổi bảng hiện có, thêm ondelete='CASCADE'
:
parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))
Và một trong những mối quan hệ sau:
a) Cái này trên bảng mẹ:
children = db.relationship('Child', backref='parent', passive_deletes=True)
b) Hoặc cái này trên bảng con:
parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))
Chi tiết
Trước hết, mặc dù câu trả lời được chấp nhận nói gì, mối quan hệ cha / con không được thiết lập bằng cách sử dụng relationship
, nó được thiết lập bằng cách sử dụng ForeignKey
. Bạn có thể đặt relationship
bảng cha hoặc bảng con và nó sẽ hoạt động tốt. Mặc dù, rõ ràng trên các bảng con, bạn phải sử dụngbackref
hàm ngoài đối số từ khóa.
Tùy chọn 1 (ưu tiên)
Thứ hai, SqlAlchemy hỗ trợ hai kiểu xếp tầng khác nhau. Cái đầu tiên, và cái tôi khuyên dùng, được xây dựng trong cơ sở dữ liệu của bạn và thường có dạng một ràng buộc đối với khai báo khóa ngoại. Trong PostgreSQL, nó trông như thế này:
CONSTRAINT child_parent_id_fkey FOREIGN KEY (parent_id)
REFERENCES parent_table(id) MATCH SIMPLE
ON DELETE CASCADE
Điều này có nghĩa là khi bạn xóa một bản ghi parent_table
, thì tất cả các hàng tương ứng trong đó child_table
sẽ bị cơ sở dữ liệu xóa cho bạn. Nó nhanh chóng và đáng tin cậy và có lẽ là đặt cược tốt nhất của bạn. Bạn đã thiết lập điều này trong SqlAlchemy thông quaForeignKey
như thế này (một phần của định nghĩa bảng con):
parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))
parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))
Các ondelete='CASCADE'
là phần tạo ra ON DELETE CASCADE
trên bàn.
Gotcha!
Có một cảnh báo quan trọng ở đây. Chú ý làm thế nào tôi có một relationship
được chỉ định với passive_deletes=True
? Nếu bạn không có điều đó, toàn bộ mọi thứ sẽ không hoạt động. Điều này là do theo mặc định khi bạn xóa một bản ghi mẹ, SqlAlchemy làm một điều gì đó thực sự kỳ lạ. Nó đặt các khóa ngoại của tất cả các hàng con thành NULL
. Vì vậy, nếu bạn xóa một hàng khỏi parent_table
vị trí id
= 5, thì về cơ bản nó sẽ thực thi
UPDATE child_table SET parent_id = NULL WHERE parent_id = 5
Tại sao bạn muốn điều này, tôi không biết. Tôi sẽ rất ngạc nhiên nếu nhiều công cụ cơ sở dữ liệu thậm chí cho phép bạn đặt một khóa ngoại hợp lệ NULL
, tạo ra một đứa trẻ mồ côi. Có vẻ như một ý tưởng tồi, nhưng có thể có một trường hợp sử dụng. Dù sao, nếu bạn để SqlAlchemy làm điều này, bạn sẽ ngăn cơ sở dữ liệu không thể dọn dẹp các phần tử con bằng cách ON DELETE CASCADE
bạn thiết lập. Điều này là do nó dựa vào các khóa ngoại đó để biết hàng con nào cần xóa. Khi SqlAlchemy đã đặt tất cả chúng thành NULL
, cơ sở dữ liệu không thể xóa chúng. Việc thiết lập passive_deletes=True
ngăn chặn SqlAlchemy NULL
nhập các khóa ngoại.
Bạn có thể đọc thêm về xóa thụ động trong tài liệu SqlAlchemy .
Lựa chọn 2
Một cách khác bạn có thể làm là để SqlAlchemy làm điều đó cho bạn. Điều này được thiết lập bằng cách sử dụng cascade
đối số củarelationship
. Nếu bạn có mối quan hệ được xác định trên bảng mẹ, nó sẽ giống như sau:
children = relationship('Child', cascade='all,delete', backref='parent')
Nếu mối quan hệ là trên trẻ em, bạn làm điều đó như sau:
parent = relationship('Parent', backref=backref('children', cascade='all,delete'))
Một lần nữa, đây là con nên bạn phải gọi một phương thức có tên backref
và đưa dữ liệu tầng vào đó.
Với điều này, khi bạn xóa một hàng mẹ, SqlAlchemy sẽ thực sự chạy các câu lệnh xóa để bạn xóa các hàng con. Điều này có thể sẽ không hiệu quả bằng việc để cơ sở dữ liệu này xử lý nếu đối với bạn, vì vậy tôi không khuyên bạn nên dùng nó.
Đây là tài liệu SqlAlchemy về các tính năng xếp tầng mà nó hỗ trợ.