Đó là toàn bộ điểm hạn chế của khóa ngoại: chúng ngăn bạn xóa dữ liệu được đề cập ở nơi khác để duy trì tính toàn vẹn tham chiếu.
Có hai lựa chọn:
- Xóa các hàng từ
INVENTORY_ITEMS
đầu tiên, sau đó các hàng từ STOCK_ARTICLES
.
- Sử dụng
ON DELETE CASCADE
cho định nghĩa chính.
1: Xóa theo đúng thứ tự
Cách hiệu quả nhất để thực hiện việc này thay đổi tùy thuộc vào độ phức tạp của truy vấn quyết định hàng nào sẽ xóa. Một mô hình chung có thể là:
BEGIN TRANSACTION
SET XACT_ABORT ON
DELETE INVENTORY_ITEMS WHERE STOCK_ARTICLE IN (<select statement that returns stock_article.id for the rows you are about to delete>)
DELETE STOCK_ARTICLES WHERE <the rest of your current delete statement>
COMMIT TRANSACTION
Điều này tốt cho các truy vấn đơn giản hoặc để xóa một mục chứng khoán, nhưng với câu lệnh xóa của bạn có chứa một WHERE NOT EXISTS
mệnh đề lồng trong đó WHERE IN
có thể tạo ra một kế hoạch rất không hiệu quả, vì vậy hãy kiểm tra kích thước tập dữ liệu thực tế và sắp xếp lại truy vấn nếu cần.
Cũng lưu ý các báo cáo giao dịch: bạn muốn đảm bảo cả hai quá trình xóa hoàn thành hoặc không thực hiện được. Nếu hoạt động đã xảy ra trong một giao dịch, rõ ràng bạn sẽ cần phải thay đổi điều này để phù hợp với quy trình xử lý lỗi và giao dịch hiện tại của bạn.
2: Sử dụng ON DELETE CASCADE
Nếu bạn thêm tùy chọn xếp tầng vào khóa ngoại thì SQL Server sẽ tự động làm điều này cho bạn, loại bỏ các hàng khỏi INVENTORY_ITEMS
để đáp ứng các ràng buộc mà không có gì nên tham chiếu đến các hàng bạn đang xóa. Chỉ cần thêm ON DELETE CASCADE
vào định nghĩa FK như vậy:
ALTER TABLE <child_table> WITH CHECK
ADD CONSTRAINT <fk_name> FOREIGN KEY(<column(s)>)
REFERENCES <parent_table> (<column(s)>)
ON DELETE CASCADE
Một lợi thế ở đây là việc xóa là giảm một câu lệnh nguyên tử (mặc dù, như thường lệ, không xóa 100%), cần phải lo lắng về các cài đặt giao dịch và khóa. Dòng thác thậm chí có thể hoạt động trên nhiều cấp độ cha / con / cháu / ... nếu chỉ có một đường dẫn giữa cha mẹ và tất cả con cháu (tìm kiếm "nhiều đường dẫn tầng" để biết ví dụ về nơi mà điều này có thể không hoạt động).
LƯU Ý: Tôi và nhiều người khác coi việc xóa xếp tầng là nguy hiểm vì vậy nếu bạn sử dụng tùy chọn này, hãy cẩn thận ghi lại đúng vào thiết kế cơ sở dữ liệu của bạn để bạn và các nhà phát triển khác không gặp phải nguy hiểm sau này . Tôi tránh xóa tầng bất cứ nơi nào có thể vì lý do này.
Một vấn đề phổ biến gây ra với xóa tầng là khi ai đó cập nhật dữ liệu bằng cách thả và tạo lại các hàng thay vì sử dụng UPDATE
hoặc MERGE
. Điều này thường được thấy trong đó "cập nhật các hàng đã tồn tại, chèn những hàng không" (đôi khi được gọi là hoạt động UPSERT) và mọi người không biết về MERGE
tuyên bố thấy dễ thực hiện hơn:
DELETE <all rows that match IDs in the new data>
INSERT <all rows from the new data>
hơn
-- updates
UPDATE target
SET <col1> = source.<col1>
, <col2> = source.<col2>
...
, <colN> = source.<colN>
FROM <target_table> AS target JOIN <source_table_or_view_or_statement> AS source ON source.ID = target.ID
-- inserts
INSERT <target_table>
SELECT *
FROM <source_table_or_other> AS source
LEFT OUTER JOIN
<target_table> AS target
ON target.ID = source.ID
WHERE target.ID IS NULL
Vấn đề ở đây là câu lệnh xóa sẽ xếp thành các hàng con và câu lệnh chèn sẽ không tạo lại chúng, vì vậy trong khi cập nhật bảng cha, bạn vô tình làm mất dữ liệu từ (các) bảng con.
Tóm lược
Có, bạn phải xóa các hàng con đầu tiên.
Có một lựa chọn khác : ON DELETE CASCADE
.
Nhưng ON DELETE CASCADE
có thể nguy hiểm , vì vậy sử dụng cẩn thận.
Lưu ý bên lề: sử dụng MERGE
(hoặc - UPDATE
và - INSERT
nơi MERGE
không có sẵn) khi bạn cần một UPSERT
thao tác, không DELETE
- thay thế - bằng - INSERT
để tránh rơi vào bẫy do người khác sử dụng ON DELETE CASCADE
.
INVENTORY_ITEMS
được thêm vào giữa haiDELETE
s.