Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Các nguyên tắc cơ bản về biểu thức bảng, Phần 11 - Quan điểm, Cân nhắc sửa đổi

Bài viết này là phần thứ mười một trong loạt bài về biểu thức bảng. Cho đến nay, tôi đã đề cập đến các bảng và CTE có nguồn gốc và gần đây đã bắt đầu đề cập đến các chế độ xem. Trong Phần 9, tôi đã so sánh các chế độ xem với các bảng và CTE dẫn xuất và trong Phần 10, tôi đã thảo luận về các thay đổi DDL và ý nghĩa của việc sử dụng SELECT * trong truy vấn bên trong của chế độ xem. Trong bài viết này, tôi tập trung vào các cân nhắc sửa đổi.

Như bạn có thể biết, bạn được phép sửa đổi dữ liệu trong bảng cơ sở một cách gián tiếp thông qua các biểu thức bảng được đặt tên như dạng xem. Bạn có thể kiểm soát quyền sửa đổi đối với các chế độ xem. Trên thực tế, bạn có thể cấp cho người dùng quyền sửa đổi dữ liệu thông qua các chế độ xem mà không cần cấp cho họ quyền trực tiếp sửa đổi các bảng bên dưới.

Bạn cần phải biết về sự phức tạp và hạn chế nhất định áp dụng cho các sửa đổi thông qua các chế độ xem. Điều thú vị là một số sửa đổi được hỗ trợ có thể mang lại kết quả đáng ngạc nhiên, đặc biệt nếu người dùng sửa đổi dữ liệu không biết rằng họ đang tương tác với một chế độ xem. Bạn có thể áp đặt thêm các hạn chế đối với các sửa đổi thông qua các lượt xem bằng cách sử dụng một tùy chọn có tên là CHỌN KIỂM TRA mà tôi sẽ đề cập trong bài viết này. Là một phần của phạm vi đề cập, tôi sẽ mô tả sự mâu thuẫn kỳ lạ giữa cách CHỌN KIỂM TRA trong chế độ xem và ràng buộc CHECK trong các sửa đổi xử lý bảng — cụ thể là những sửa đổi liên quan đến NULL.

Dữ liệu mẫu

Là dữ liệu mẫu cho bài viết này, tôi sẽ sử dụng các bảng có tên Đơn hàng và Chi tiết đơn hàng. Sử dụng mã sau để tạo các bảng này trong tempdb và điền chúng với một số dữ liệu mẫu ban đầu:

 SỬ DỤNG tempdb; ĐI DROP TABLE NẾU TỒN TẠI dbo.OrderDetails, dbo.Orders; ĐI TẠO BẢNG dbo.Orders (orderid INT NOT NULL CONSTRAINT PK_Orders PRIMARY KEY, orderdate DATE NOT NULL, shipdate DATE NULL); CHÈN VÀO Dbo.Orders (orderid, orderdate, shippingdate) CÁC GIÁ TRỊ (1, '20210802', '20210804'), (2, '20210802', '20210805'), (3, '20210804', '20210806'), ( 4, '20210826', NULL), (5, '20210827', NULL); CREATE TABLE dbo.OrderDetails (orderid INT NOT NULL CONSTRAINT FK_OrderDetails_Orders TÀI LIỆU THAM KHẢO dbo.Orders, productiontid INT NOT NULL, qty INT NOT NULL, unitprice NUMERIC (12, 2) NOT NULL, chiết khấu NUMERIC PKERIC (5, 4) NOT NULL, CONSTRAINT KEY (orderid, productiontid)); CHÈN VÀO Dbo.OrderDetails (orderid ,osystemtid, qty, unitprice, chiết khấu) CÁC GIÁ TRỊ (1, 1001, 5, 10,50, 0,05), (1, 1004, 2, 20,00, 0,00), (2, 1003, 1, 52,99, 0,10), (3, 1001, 1, 10,50, 0,05), (3, 1003, 2, 54,99, 0,10), (4, 1001, 2, 10,50, 0,05), (4, 1004, 1, 20,30, 0,00) , (4, 1005, 1, 30,10, 0,05), (5, 1003, 5, 54,99, 0,00), (5, 1006, 2, 12,30, 0,08); 

Bảng Đơn hàng chứa tiêu đề đơn hàng và bảng OrderDetails chứa các dòng đơn hàng. Các đơn hàng chưa được vận chuyển có NULL trong cột ngày vận chuyển. Nếu bạn thích một thiết kế không sử dụng NULL, bạn có thể sử dụng một ngày cụ thể trong tương lai cho các đơn hàng chưa được vận chuyển, chẳng hạn như “99991231.”

LỰA CHỌN KIỂM TRA

Để hiểu các trường hợp bạn muốn sử dụng TÙY CHỌN KIỂM TRA như một phần trong định nghĩa của một chế độ xem, trước tiên, chúng tôi sẽ kiểm tra những gì có thể xảy ra khi bạn không sử dụng nó.

Đoạn mã sau tạo một chế độ xem được gọi là FastOrders đại diện cho các đơn đặt hàng được giao trong vòng bảy ngày kể từ khi chúng được đặt:

 TẠO HOẶC AL SAU XEM dbo.FastOrdersAS CHỌN orderid, orderdate, shippingdate FROM dbo.Orders WHERE DATEDIFF (ngày, ngày đặt hàng, ngày vận chuyển) <=7; ĐI 

Sử dụng mã sau để chèn qua chế độ xem một đơn đặt hàng được giao sau hai ngày kể từ khi được đặt:

 CHÈN VÀO dbo.FastOrders (orderid, orderdate, shippingdate) VALUES (6, '20210805', '20210807'); 

Truy vấn chế độ xem:

 CHỌN * TỪ dbo.FastOrders; 

Bạn nhận được kết quả sau, bao gồm đơn đặt hàng mới:

 orderid orderdate ngày vận chuyển ------------- ---------------- 1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-066 2021-08-05 2021-08-07 

Truy vấn bảng bên dưới:

 CHỌN * TỪ dbo.Orders; 

Bạn nhận được kết quả sau, bao gồm đơn đặt hàng mới:

 orderid orderdate ngày vận chuyển ------------- ---------------- 1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-07 

Hàng đã được chèn vào bảng cơ sở bên dưới thông qua chế độ xem.

Tiếp theo, chèn qua chế độ xem một hàng được vận chuyển 10 ngày sau khi được đặt, mâu thuẫn với bộ lọc truy vấn bên trong của chế độ xem:

 CHÈN VÀO dbo.FastOrders (orderid, orderdate, shippingdate) VALUES (7, '20210805', '20210815'); 

Câu lệnh hoàn thành thành công, báo cáo một hàng bị ảnh hưởng.

Truy vấn chế độ xem:

 CHỌN * TỪ dbo.FastOrders; 

Bạn nhận được kết quả sau, không bao gồm đơn đặt hàng mới:

 orderid orderdate ngày vận chuyển ------------- ---------------- 1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-066 2021-08-05 2021-08-07 

Nếu bạn biết FastOrders là một chế độ xem, điều này có vẻ hợp lý. Rốt cuộc, hàng đã được chèn vào bảng bên dưới và nó không đáp ứng bộ lọc truy vấn bên trong của chế độ xem. Nhưng nếu bạn không biết rằng FastOrders là một dạng xem chứ không phải một bảng cơ sở, thì hành vi này sẽ có vẻ đáng ngạc nhiên.

Truy vấn bảng Đơn hàng cơ bản:

 CHỌN * TỪ dbo.Orders; 

Bạn nhận được kết quả sau, bao gồm đơn đặt hàng mới:

 orderid orderdate ngày vận chuyển ------------- ---------------- 1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-077 2021-08-05 2021-08- 15 

Bạn có thể gặp phải một hành vi đáng ngạc nhiên tương tự nếu bạn cập nhật thông qua chế độ xem giá trị ngày vận chuyển trong một hàng hiện là một phần của chế độ xem đến một ngày khiến nó không đủ điều kiện là một phần của chế độ xem nữa. Cập nhật như vậy thường được cho phép, nhưng một lần nữa, nó lại diễn ra trong bảng cơ sở bên dưới. Nếu bạn truy vấn chế độ xem sau khi cập nhật như vậy, hàng đã sửa đổi dường như đã biến mất. Trên thực tế, nó vẫn ở đó trong bảng bên dưới, nó chỉ không được coi là một phần của chế độ xem nữa.

Chạy mã sau để xóa các hàng bạn đã thêm trước đó:

 XÓA khỏi dbo.Orders WHERE orderid> =6; 

Nếu bạn muốn ngăn các sửa đổi xung đột với bộ lọc truy vấn bên trong của chế độ xem, hãy thêm CÓ CHỌN KIỂM TRA ở cuối truy vấn bên trong như một phần của định nghĩa chế độ xem, như sau:

 TẠO HOẶC AL SAU XEM dbo .FastOrdersAS CHỌN orderid, orderdate, shippingdate FROM dbo.Orders WHERE DATEDIFF (ngày, ngày đặt hàng, ngày vận chuyển) <=7 VỚI CHỌN KIỂM TRA; ĐI 

Chèn và cập nhật thông qua chế độ xem được phép miễn là chúng tuân thủ bộ lọc của truy vấn bên trong. Nếu không, chúng sẽ bị từ chối.

Ví dụ:sử dụng mã sau để chèn qua chế độ xem một hàng không xung đột với bộ lọc truy vấn bên trong:

 CHÈN VÀO dbo.FastOrders (orderid, orderdate, shippingdate) VALUES (6, '20210805', '20210807'); 

Hàng được thêm thành công.

Cố gắng chèn một hàng xung đột với bộ lọc:

 CHÈN VÀO dbo.FastOrders (orderid, orderdate, shippingdate) VALUES (7, '20210805', '20210815'); 

Lần này hàng bị từ chối với lỗi sau:

Mức 16, Trạng thái 1, Dòng 135
Cố gắng chèn hoặc cập nhật không thành công do chế độ xem đích chỉ định VỚI CHỌN KIỂM TRA hoặc kéo dài một chế độ xem chỉ định CÓ CHỌN KIỂM TRA và một hoặc nhiều hàng kết quả từ hoạt động không đủ điều kiện theo Ràng buộc CHECK OPTION.

NULL Sự không nhất quán

Nếu bạn đã làm việc với T-SQL một thời gian, bạn có thể biết rõ về sự phức tạp của việc sửa đổi nói trên và chức năng CHECK OPTION phục vụ. Thông thường, ngay cả những người có kinh nghiệm cũng thấy cách xử lý KHÔNG ĐỦ đối với CHỌN KIỂM TRA là đáng ngạc nhiên. Trong nhiều năm, tôi đã từng nghĩ về CHỌN LỰA CHỌN KIỂM TRA theo quan điểm phục vụ cùng một chức năng như ràng buộc KIỂM TRA trong định nghĩa của bảng cơ sở. Đó cũng là cách tôi sử dụng để mô tả tùy chọn này khi viết hoặc giảng dạy về nó. Thật vậy, miễn là không có NULL nào liên quan đến vị từ bộ lọc, thì sẽ rất thuận tiện để nghĩ về cả hai theo các thuật ngữ tương tự. Chúng hành xử nhất quán trong trường hợp như vậy - chấp nhận các hàng đồng ý với vị ngữ và từ chối các hàng xung đột với nó. Tuy nhiên, cả hai xử lý NULL không nhất quán.

Khi sử dụng CHỌN LỰA CHỌN KIỂM TRA, một sửa đổi được phép thông qua chế độ xem miễn là vị từ đánh giá là true, nếu không, nó sẽ bị từ chối. Điều này có nghĩa là nó bị từ chối khi vị từ của chế độ xem đánh giá là sai hoặc không xác định (khi có liên quan đến NULL). Với ràng buộc CHECK, việc sửa đổi được phép khi vị từ của ràng buộc đánh giá là true hoặc không xác định và bị từ chối khi vị từ đánh giá là false. Đó là một sự khác biệt thú vị! Trước tiên, hãy xem điều này thực tế, sau đó chúng tôi sẽ thử và tìm ra logic đằng sau sự không nhất quán này.

Cố gắng chèn qua chế độ xem một hàng có NULL ngày vận chuyển:

 CHÈN VÀO dbo.FastOrders (orderid, orderdate, shippingdate) VALUES (8, '20210828', NULL); 

Vị từ của chế độ xem đánh giá là không xác định và hàng bị từ chối với lỗi sau:

Msg 550, Mức 16, Trạng thái 1, Dòng 147
Việc cố gắng chèn hoặc cập nhật không thành công do chế độ xem đích chỉ định VỚI CHỌN KIỂM TRA hoặc kéo dài một chế độ xem chỉ định CÓ CHỌN KIỂM TRA và một hoặc nhiều hàng kết quả từ thao tác không đủ điều kiện theo ràng buộc CHECK OPTION.

Hãy thử chèn tương tự vào bảng cơ sở có ràng buộc KIỂM TRA. Sử dụng mã sau để thêm một ràng buộc như vậy vào định nghĩa bảng Đơn đặt hàng của chúng tôi:

 ALTER TABLE dbo.Orders ADD CONSTRAINT CHK_Orders_FastOrder CHECK (DATEDIFF (ngày, ngày đặt hàng, ngày vận chuyển) <=7); 

Đầu tiên, để đảm bảo ràng buộc hoạt động khi không có NULL liên quan, hãy thử chèn đơn đặt hàng sau với ngày vận chuyển cách ngày đặt hàng 10 ngày:

 CHÈN VÀO dbo.Orders (orderid, orderdate, shippingdate) CÁC GIÁ TRỊ (7, '20210805', '20210815'); 

Cố gắng chèn này bị từ chối với lỗi sau:

Msg 547, Mức 16, Trạng thái 0, Dòng 159
Câu lệnh INSERT xung đột với ràng buộc CHECK "CHK_Orders_FastOrder". Xung đột xảy ra trong cơ sở dữ liệu "tempdb", bảng "dbo.Orders".

Sử dụng mã sau để chèn một hàng có NULL ngày vận chuyển:

 CHÈN VÀO dbo.Orders (orderid, orderdate, shippingdate) VALUES (8, '20210828', NULL); 

Ràng buộc CHECK được cho là loại bỏ các trường hợp sai, nhưng trong trường hợp của chúng tôi, vị từ đánh giá là không xác định, vì vậy hàng được thêm thành công.

Truy vấn bảng Đơn hàng:

 CHỌN * TỪ dbo.Orders; 

Bạn có thể xem thứ tự mới trong đầu ra:

 orderid orderdate ngày vận chuyển ------------- ---------------- 1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-078 2021-08-28 NULL 

Logic đằng sau sự mâu thuẫn này là gì? Bạn có thể lập luận rằng ràng buộc CHECK chỉ nên được thực thi khi vị từ của ràng buộc bị vi phạm rõ ràng, nghĩa là khi nó đánh giá là false. Bằng cách này, nếu bạn chọn cho phép NULL trong cột được đề cập, các hàng có NULL trong cột sẽ được phép mặc dù vị từ của ràng buộc đánh giá là không xác định. Trong trường hợp của chúng tôi, chúng tôi thể hiện các đơn hàng chưa được vận chuyển bằng NULL trong cột ngày vận chuyển và chúng tôi cho phép các đơn hàng chưa được vận chuyển trong bảng đồng thời thực thi quy tắc "đơn hàng nhanh" chỉ đối với các đơn hàng đã vận chuyển.

Đối số để sử dụng logic khác với một chế độ xem là việc sửa đổi chỉ được phép thông qua chế độ xem nếu hàng kết quả là một phần hợp lệ của chế độ xem. Nếu vị từ của chế độ xem đánh giá là không xác định, ví dụ:khi ngày vận chuyển là NULL, thì hàng kết quả không phải là một phần hợp lệ của chế độ xem, do đó nó bị từ chối. Chỉ các hàng mà vị từ đánh giá là true mới là phần hợp lệ của chế độ xem và do đó được phép.

NULL làm cho ngôn ngữ thêm phức tạp. Dù muốn hay không, nếu dữ liệu của bạn hỗ trợ chúng, bạn cần đảm bảo rằng mình hiểu cách T-SQL xử lý chúng.

Tại thời điểm này, bạn có thể loại bỏ ràng buộc KIỂM TRA khỏi bảng Đơn hàng và cũng có thể thả chế độ xem FastOrders để dọn dẹp:

 ALTER TABLE dbo.Orders DROP CONSTRAINT CHK_Orders_FastOrder; DROP XEM NẾU TỒN TẠI dbo.FastOrders; 

Hạn chế TOP / OFFSET-FETCH

Các sửa đổi thông qua các chế độ xem liên quan đến bộ lọc TOP và OFFSET-FETCH thường được cho phép. Tuy nhiên, giống như cuộc thảo luận trước đó của chúng tôi về các chế độ xem được xác định mà không có LỰA CHỌN KIỂM TRA, kết quả của việc sửa đổi như vậy có thể lạ đối với người dùng nếu họ không biết mình đang tương tác với một chế độ xem.

Hãy xem xét chế độ xem sau đại diện cho các đơn đặt hàng gần đây làm ví dụ:

 TẠO HOẶC AL SAU XEM dbo.RecentOrdersAS CHỌN ĐẦU (5) orderid, orderdate, shipdate FROM dbo. 

Sử dụng mã sau để chèn qua 6 đơn đặt hàng gần đây:

 INSERT INTO dbo.RecentOrders (orderid, orderdate, shippingdate) VALUES (9, '20210801', '20210803'), (10, '20210802', '20210804'), (11, '20210829', '20210831' ), (12, '20210830', '20210902'), (13, '20210830', '20210903'), (14, '20210831', '20210903'); 

Truy vấn chế độ xem:

 CHỌN * TỪ dbo.RecentOrders; 

Bạn nhận được kết quả sau:

 orderid orderdate ngày vận chuyển ------------- ---------------- 14 2021-08-31 2021-09-0313 2021 -08-30 2021-09-0312 2021-08-30 2021-09-0211 2021-08-29 2021-08-318 2021-08-28 NULL 

Từ sáu đơn đặt hàng được chèn, chỉ có bốn là một phần của chế độ xem. Điều này có vẻ hoàn toàn hợp lý nếu bạn biết rằng bạn đang truy vấn một chế độ xem dựa trên một truy vấn có bộ lọc TOP. Nhưng có vẻ lạ nếu bạn nghĩ rằng bạn đang truy vấn một bảng cơ sở.

Truy vấn trực tiếp bảng Đơn đặt hàng bên dưới:

 CHỌN * TỪ dbo.Orders; 

Bạn nhận được kết quả sau hiển thị tất cả các đơn đặt hàng đã thêm:

 orderid orderdate ngày vận chuyển ------------- ---------------- 1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-078 2021-08-28 NULL9 2021-08 -01 2021-08-0310 2021-08-02 2021-08-0411 2021-08-29 2021-08-3112 2021-08-30 2021-09-0213 2021-08-30 2021-09-0314 2021-08 -31 2021-09-03 

Nếu bạn thêm LỰA CHỌN KIỂM TRA vào định nghĩa chế độ xem, các câu lệnh CHÈN và CẬP NHẬT đối với chế độ xem sẽ bị từ chối. Sử dụng mã sau để áp dụng thay đổi này:

 TẠO HOẶC AL SAU XEM dbo.RecentOrdersAS CHỌN ĐẦU (5) orderid, orderdate, shipdate FROM dbo. 

Cố gắng thêm đơn hàng qua chế độ xem:

 CHÈN VÀO dbo.RecentOrders (orderid, orderdate, shippingdate) VALUES (15, '20210801', '20210805'); 

Bạn gặp lỗi sau:

Msg 4427, Mức 16, Trạng thái 1, Dòng 247
Không thể cập nhật dạng xem "dbo.RecentOrders" vì chế độ xem hoặc dạng xem mà nó tham chiếu được tạo bằng WITH CHECK OPTION và định nghĩa của nó chứa mệnh đề TOP hoặc OFFSET.

SQL Server đừng cố tỏ ra quá thông minh ở đây. Nó sẽ từ chối thay đổi ngay cả khi hàng bạn cố gắng chèn vào sẽ trở thành một phần hợp lệ của chế độ xem tại thời điểm đó. Ví dụ:hãy thử thêm một đơn đặt hàng có ngày gần đây hơn sẽ nằm trong top 5 tại thời điểm này:

 CHÈN VÀO dbo.RecentOrders (orderid, orderdate, shippingdate) VALUES (15, '20210904', '20210906'); 

Cố gắng chèn vẫn bị từ chối với lỗi sau:

Msg 4427, Mức 16, Trạng thái 1, Dòng 254
Không thể cập nhật chế độ xem "dbo.RecentOrders" vì chế độ xem hoặc chế độ xem mà nó tham chiếu được tạo bằng WITH CHECK OPTION và định nghĩa của nó chứa mệnh đề TOP hoặc OFFSET.

Cố gắng cập nhật một hàng thông qua chế độ xem:

 CẬP NHẬT dbo.RecentOrders SET ngày vận chuyển =DATEADD (ngày, 2, ngày đặt hàng); 

Trong trường hợp này, thay đổi đã cố gắng cũng bị từ chối với lỗi sau:

Msg 4427, Mức 16, Trạng thái 1, Dòng 260
Không thể cập nhật dạng xem "dbo.RecentOrders" vì chế độ xem hoặc dạng xem mà nó tham chiếu được tạo bằng WITH CHECK OPTION và định nghĩa của nó chứa mệnh đề TOP hoặc OFFSET.

Lưu ý rằng việc xác định chế độ xem dựa trên truy vấn với TOP hoặc OFFSET-FETCH và CHỌN KIỂM TRA sẽ dẫn đến việc thiếu hỗ trợ cho các câu lệnh CHÈN và CẬP NHẬT thông qua chế độ xem.

Việc xóa thông qua chế độ xem như vậy được hỗ trợ. Chạy mã sau để xóa tất cả năm đơn đặt hàng gần đây nhất hiện tại:

 XÓA khỏi dbo.RecentOrders; 

Lệnh hoàn tất thành công.

Truy vấn bảng:

 CHỌN * TỪ dbo.Orders; 

Bạn nhận được kết quả đầu ra sau khi xóa các đơn đặt hàng có ID 8, 11, 12, 13 và 14.

 orderid orderdate ngày vận chuyển ------------- ---------------- 1 2021-08-02 2021-08-042 2021 -08-02 2021-08-053 2021-08-04 2021-08-064 2021-08-26 NULL5 2021-08-27 NULL6 2021-08-05 2021-08-079 2021-08-01 2021-08- 0310 2021-08-02 2021-08-04 

Tại thời điểm này, hãy chạy mã sau để dọn dẹp trước khi chạy các ví dụ trong phần tiếp theo:

 XÓA khỏi dbo.Orders WHERE orderid> 5; DROP XEM NẾU TỒN TẠI dbo.RecentOrders; 

Tham gia

Cập nhật dạng xem kết hợp nhiều bảng được hỗ trợ, miễn là chỉ một trong các bảng cơ sở bên dưới bị ảnh hưởng bởi sự thay đổi.

Hãy xem xét chế độ xem sau khi tham gia Đơn hàng và Chi tiết Đơn hàng làm ví dụ:

 TẠO HOẶC ALTER XEM dbo.OrdersOrderDetailsAS CHỌN O.orderid, O.orderdate, O.shippeddate, OD.productid, OD.qty, OD.unitprice, OD.discount FROM dbo.Orders AS O INNER JOIN dbo.OrderDetails AS OD ON O.orderid =OD.orderid; ĐI 

Cố gắng chèn một hàng qua dạng xem, vì vậy cả hai bảng cơ sở bên dưới sẽ bị ảnh hưởng:

 CHÈN VÀO dbo.OrdersOrderDetails (orderid, orderdate, shippingdate ,osystemtid, qty, unitprice, chiết khấu) GIÁ TRỊ (6, '20210828', NULL, 1001, 5, 10.50, 0.05); 

Bạn gặp lỗi sau:

Msg 4405, Mức 16, Trạng thái 1, Dòng 306
Chế độ xem hoặc chức năng 'dbo.OrdersOrderDetails' không thể cập nhật vì sửa đổi ảnh hưởng đến nhiều bảng cơ sở.

Cố gắng chèn một hàng qua dạng xem, vì vậy chỉ bảng Đơn hàng mới bị ảnh hưởng:

 CHÈN VÀO dbo.OrdersOrderDetails (orderid, orderdate, shippingdate) VALUES (6, '20210828', NULL); 

Lệnh này hoàn tất thành công và hàng được chèn vào bảng Đơn hàng bên dưới.

Nhưng điều gì sẽ xảy ra nếu bạn cũng muốn có thể chèn một hàng dọc theo chế độ xem vào bảng OrderDetails? Với định nghĩa dạng xem hiện tại, điều này là không thể (thay vì kích hoạt sang một bên) vì dạng xem trả về cột orderid từ bảng Đơn hàng chứ không phải từ bảng OrderDetails. Đủ để một cột từ bảng OrderDetails không thể tự động nhận giá trị của nó sẽ không phải là một phần của chế độ xem để ngăn chặn việc chèn vào OrderDetails thông qua chế độ xem. Tất nhiên, bạn luôn có thể quyết định chế độ xem sẽ bao gồm cả orderid từ Đơn đặt hàng và orderid từ OrderDetails. Trong trường hợp như vậy, bạn sẽ phải chỉ định hai cột với các bí danh khác nhau vì tiêu đề của bảng được biểu thị bằng chế độ xem phải có tên cột duy nhất.

Sử dụng mã sau để thay đổi định nghĩa chế độ xem để bao gồm cả hai cột, bí danh từ Đơn hàng dưới dạng O_orderid và một từ OrderDetails dưới dạng OD_orderid:

 TẠO HOẶC ALTER XEM dbo.OrdersOrderDetailsAS CHỌN O.orderid AS O_orderid, O.orderdate, O.shippeddate, OD.orderid AS OD_orderid, OD.productid, OD.qty, OD.unitprice, OD.discount FROM dbo.Orders AS O INNER JOIN dbo.OrderDetails AS OD ON O.orderid =OD.orderid; GO 

Bây giờ bạn có thể chèn các hàng qua dạng xem vào Đơn hàng hoặc Chi tiết đơn hàng, tùy thuộc vào bảng mà danh sách cột mục tiêu đến từ bảng nào. Dưới đây là một ví dụ để chèn một vài dòng đơn hàng được liên kết với đơn hàng 6 thông qua chế độ xem vào OrderDetails:

 CHÈN VÀO dbo.OrdersOrderDetails (OD_orderid, productiontid, qty, unitprice, chiết khấu) CÁC GIÁ TRỊ (6, 1001, 5, 10.50, 0.05), (6, 1002, 5, 20.00, 0.05); 

Các hàng được thêm thành công.

Truy vấn chế độ xem:

 CHỌN * TỪ dbo.OrdersOrderDetails WHERE O_orderid =6; 

Bạn nhận được kết quả sau:

 O_orderid orderdate ngày vận chuyển OD_orderid productiontid qty unitprice chiết khấu ------------- ---------------- ---- ----------- ----------------- 6 2021-08-28 NULL 61001 5 10.50 0.05006 2021-08-28 NULL 6 1002 5 20,00 0,0500 

Một hạn chế tương tự được áp dụng cho các câu lệnh CẬP NHẬT thông qua chế độ xem. Cập nhật được phép miễn là chỉ có một bảng cơ sở bên dưới bị ảnh hưởng. Nhưng bạn được phép tham chiếu các cột từ cả hai bên trong câu lệnh miễn là chỉ một bên được sửa đổi.

Ví dụ:câu lệnh CẬP NHẬT sau đây thông qua chế độ xem đặt ngày đặt hàng của hàng trong đó ID đơn đặt hàng của dòng đặt hàng là 6 và ID sản phẩm là 1001 thành “20210901:”

 CẬP NHẬT dbo.OrdersOrderDetails SET orderdate ='20210901' WHERE OD_orderid =6 AND productiontid =1001; 

Chúng tôi sẽ gọi đây là tuyên bố này Tuyên bố cập nhật 1.

Cập nhật hoàn tất thành công với thông báo sau:

 (1 hàng bị ảnh hưởng) 

Điều quan trọng cần lưu ý ở đây là câu lệnh lọc theo các phần tử từ bảng OrderDetails, nhưng ngày đặt hàng của cột đã sửa đổi là từ bảng Đơn hàng. Vì vậy, trong kế hoạch SQL Server xây dựng cho câu lệnh này, nó phải tìm ra đơn hàng nào cần được sửa đổi trong bảng Đơn hàng. Kế hoạch cho tuyên bố này được thể hiện trong Hình 1.

Hình 1:Kế hoạch cập nhật câu lệnh 1

Bạn có thể xem kế hoạch bắt đầu như thế nào bằng cách lọc OrderDetails bên cạnh orderid =6 và productiontid =1001, và các Order bên cạnh orderid =6, kết hợp cả hai. Kết quả chỉ có một hàng. Phần có liên quan duy nhất cần lưu ý đối với hoạt động này là ID đơn hàng nào trong bảng Đơn hàng đại diện cho các hàng cần được cập nhật. Trong trường hợp của chúng tôi, đó là đơn hàng có ID đơn hàng 6. Ngoài ra, toán tử Tính toán vô hướng chuẩn bị một thành viên có tên là Expr1002 với giá trị mà câu lệnh sẽ gán cho cột ngày đặt hàng của đơn hàng đích. Phần cuối cùng của kế hoạch với toán tử Cập nhật chỉ mục theo cụm áp dụng cập nhật thực tế cho hàng trong Đơn hàng có ID đơn hàng 6, đặt giá trị ngày đặt hàng của nó thành Expr1002.

Điểm quan trọng cần nhấn mạnh ở đây là chỉ một hàng có orderid 6 trong bảng Đơn hàng đã được cập nhật. Tuy nhiên, hàng này có hai kết quả phù hợp trong kết quả của phép kết hợp với bảng OrderDetails — một với ID sản phẩm 1001 (mà bản cập nhật ban đầu đã lọc) và một có ID sản phẩm 1002 (mà bản cập nhật ban đầu không lọc). Truy vấn chế độ xem tại thời điểm này, lọc tất cả các hàng có ID đơn hàng 6:

 CHỌN * TỪ dbo.OrdersOrderDetails WHERE O_orderid =6; 

Bạn nhận được kết quả sau:

 O_orderid orderdate ngày vận chuyển OD_orderid productiontid qty unitprice chiết khấu ------------- ---------------- ---- ----------- ----------------- 6 2021-09-01 NULL 61001 5 10.50 0.05006 2021-09-01 NULL 6 1002 5 20,00 0,0500 

Cả hai hàng đều hiển thị ngày đặt hàng mới, mặc dù bản cập nhật ban đầu chỉ lọc hàng có ID sản phẩm 1001. Tuy nhiên, một lần nữa, điều này có vẻ hoàn toàn hợp lý nếu bạn biết mình đang tương tác với một chế độ xem kết hợp hai bảng cơ sở bên dưới bìa, nhưng có vẻ rất lạ nếu bạn không nhận ra điều này.

Thật kỳ lạ, SQL Server thậm chí còn hỗ trợ các bản cập nhật không xác định trong đó nhiều hàng nguồn (từ OrderDetails trong trường hợp của chúng tôi) khớp với một hàng mục tiêu duy nhất (trong trường hợp Đơn hàng). Về mặt lý thuyết, một cách để xử lý một trường hợp như vậy là từ chối nó. Thật vậy, với một câu lệnh MERGE trong đó nhiều hàng nguồn khớp với một hàng đích, SQL Server từ chối nỗ lực này. Nhưng không phải với CẬP NHẬT dựa trên một phép nối, cho dù trực tiếp hay gián tiếp thông qua một biểu thức bảng được đặt tên như một dạng xem. SQL Server chỉ đơn giản xử lý nó như một bản cập nhật không xác định.

Hãy xem xét ví dụ sau, mà chúng tôi sẽ gọi là Tuyên bố 2:

 CẬP NHẬT dbo.OrdersOrderDetails SET orderdate =CASE WHEN unitprice> =20.00 THEN '20210902' ELSE '20210903' END WHERE OD_orderid =6; 

Hy vọng rằng bạn sẽ tha thứ cho tôi rằng đó là một ví dụ giả tạo, nhưng nó minh họa cho vấn đề.

Có hai hàng đủ điều kiện trong dạng xem, đại diện cho hai hàng dòng thứ tự nguồn đủ điều kiện từ bảng OrderDetails bên dưới. Nhưng chỉ có một hàng mục tiêu đủ điều kiện trong bảng Đơn hàng bên dưới. Hơn nữa, trong một hàng OrderDetails nguồn, biểu thức CASE được chỉ định trả về một giá trị ('20210902') và trong hàng OrderDetails nguồn khác, nó trả về một giá trị khác ('20210903'). SQL Server nên làm gì trong trường hợp này? Như đã đề cập, một tình huống tương tự với câu lệnh MERGE sẽ dẫn đến lỗi, từ chối thay đổi đã cố gắng. Tuy nhiên, với một câu lệnh UPDATE, SQL Server chỉ đơn giản là tung một đồng xu. Về mặt kỹ thuật, điều này được thực hiện bằng cách sử dụng một hàm tổng hợp nội bộ được gọi là BẤT KỲ.

Vì vậy, cập nhật của chúng tôi hoàn tất thành công, báo cáo 1 hàng bị ảnh hưởng. Kế hoạch cho tuyên bố này được thể hiện trong Hình 2.


Hình 2:Kế hoạch cập nhật câu lệnh 2

Có hai hàng trong kết quả của phép nối. Hai hàng này trở thành hàng nguồn cho bản cập nhật. Nhưng sau đó, một toán tử tổng hợp áp dụng hàm BẤT KỲ sẽ chọn một (bất kỳ) giá trị orderid và một (bất kỳ) giá trị đơn vị từ các hàng nguồn này. Cả hai hàng nguồn đều có cùng giá trị orderid, vì vậy thứ tự phù hợp sẽ được sửa đổi. Nhưng tùy thuộc vào giá trị đơn vị nguồn nào mà tổng hợp BẤT KỲ chọn, điều này sẽ xác định giá trị nào mà biểu thức CASE sẽ trả về, sau đó được sử dụng làm giá trị ngày đặt hàng được cập nhật trong thứ tự đích. Bạn chắc chắn có thể thấy một lập luận chống lại việc hỗ trợ một bản cập nhật như vậy, nhưng nó được hỗ trợ đầy đủ trong SQL Server.

Hãy truy vấn chế độ xem để xem kết quả của sự thay đổi này (bây giờ là lúc bạn đặt cược cho kết quả):

 CHỌN * TỪ dbo.OrdersOrderDetails WHERE O_orderid =6; 

Tôi nhận được kết quả sau:

 O_orderid orderdate ngày vận chuyển OD_orderid productiontid qty unitprice chiết khấu ------------- ---------------- ---- ----------- ----------------- 6 2021-09-03 NULL 61001 5 10.50 0.05006 2021-09-03 NULL 6 1002 5 20,00 0,0500 

Chỉ một trong hai giá trị đơn giá nguồn được chọn và sử dụng để xác định ngày đặt hàng của đơn hàng mục tiêu duy nhất, tuy nhiên khi truy vấn chế độ xem, giá trị ngày đặt hàng được lặp lại cho cả hai dòng lệnh khớp. Như bạn có thể nhận ra, kết quả cũng có thể xảy ra vào ngày khác (2021-09-02) vì việc lựa chọn giá trị đơn vị là không xác định. Đồ lập dị!

Vì vậy, trong các điều kiện nhất định, các câu lệnh INSERT và UPDATE được phép thông qua các khung nhìn nối nhiều bảng bên dưới. Tuy nhiên, không được phép xóa đối với các chế độ xem như vậy. Làm cách nào SQL Server có thể cho biết bên nào được cho là đích để xóa?

Đây là nỗ lực để áp dụng cách xóa như vậy thông qua chế độ xem:

 XÓA khỏi dbo.OrdersOrderDetails WHERE O_orderid =6; 

Nỗ lực này bị từ chối với lỗi sau:

Msg 4405, Mức 16, Trạng thái 1, Dòng 377
Chế độ xem hoặc chức năng 'dbo.OrdersOrderDetails' không thể cập nhật vì sửa đổi ảnh hưởng đến nhiều bảng cơ sở.

Tại thời điểm này, hãy chạy mã sau để dọn dẹp:

 XÓA KHỎI dbo.OrderDetails WHERE orderid =6; XÓA KHỎI dbo.Orders WHERE orderid =6; XÓA XEM NẾU TỒN TẠI dbo.OrdersOrderDetails; 

Các cột có nguồn gốc

Một hạn chế khác đối với các sửa đổi thông qua các khung nhìn liên quan đến các cột dẫn xuất. Nếu cột chế độ xem là kết quả của quá trình tính toán, SQL Server sẽ không cố thiết kế ngược công thức của nó khi bạn cố gắng chèn hoặc cập nhật dữ liệu thông qua chế độ xem — thay vào đó, nó sẽ từ chối các sửa đổi đó.

Hãy xem xét chế độ xem sau đây làm ví dụ:

 TẠO HOẶC ALTER XEM dbo.OrderDetailsNetPriceAS CHỌN orderid, productiontid, qty, unitprice * (1.0 - chiết khấu) NHƯ netunitprice, chiết khấu TỪ dbo.OrderDetails; ĐI 

Chế độ xem tính toán cột netunitprice dựa trên cột đơn giá và chiết khấu của bảng OrderDetails bên dưới.

Truy vấn chế độ xem:

 CHỌN * TỪ dbo.OrderDetailsNetPrice; 

Bạn nhận được kết quả sau:

 orderid productiontid qty netunitprice chiết khấu ----------- ------------- ---------------- ---- --------- 1 1001 5 9,975000 0,05001 1004 2 20,000000 0,00002 1003 1 47,691000 0,10003 1001 1 9,975000 0,05003 1003 2 49,491000 0,10004 1001 2 9,975000 0,05004 1004 1 20.300000 0,00004 1005 1 28.595000 0,05005 1003 5 54.990000 0,00005 1006 2 11.316000 0.0800 

Cố gắng chèn một hàng qua chế độ xem:

 CHÈN VÀO dbo.OrderDetailsNetPrice (orderid ,osystemtid, qty, netunitprice, chiết khấu) GIÁ TRỊ (1, 1005, 1, 28.595, 0.05); 

Về mặt lý thuyết, bạn có thể tìm ra hàng nào cần được chèn vào bảng OrderDetails bên dưới bằng cách thiết kế ngược giá trị đơn giá của bảng cơ sở từ giá trị netunitprice và chiết khấu của chế độ xem. SQL Server không cố gắng thiết kế ngược như vậy, nhưng từ chối việc chèn đã cố gắng với lỗi sau:

Msg 4406, Mức 16, Trạng thái 1, Dòng 412
Cập nhật hoặc chèn chế độ xem hoặc hàm 'dbo.OrderDetailsNetPrice' không thành công vì nó chứa trường dẫn xuất hoặc trường hằng số.

Cố gắng bỏ qua cột đã tính khỏi phần chèn:

 CHÈN VÀO dbo.OrderDetailsNetPrice (orderid ,osystemtid, qty, chiết khấu) CÁC GIÁ TRỊ (1, 1005, 1, 0,05); 

Bây giờ chúng ta quay lại yêu cầu rằng tất cả các cột từ bảng bên dưới bằng cách nào đó không nhận được giá trị của chúng tự động phải là một phần của chèn và ở đây chúng ta thiếu cột đơn giá. Việc chèn này không thành công với lỗi sau:

Msg 515, Mức 16, Trạng thái 2, Dòng 421
Không thể chèn giá trị NULL vào cột 'unitprice', bảng 'tempdb.dbo.OrderDetails'; cột không cho phép null. INSERT không thành công.

Nếu bạn muốn hỗ trợ chèn thông qua dạng xem, về cơ bản bạn có hai tùy chọn. Một là bao gồm cột đơn giá trong định nghĩa chế độ xem. Another is to create an instead of trigger on the view where you handle the reverse engineering logic yourself.

At this point, run the following code for cleanup:

DROP VIEW IF EXISTS dbo.OrderDetailsNetPrice;

Set Operators

As mentioned in the last section, you’re not allowed to modify a column in a view if the column is a result of a computation. The columns modified in the view using INSERT and UPDATE statements have to map directly to the underlying base table’s columns with no manipulation. In the list of restrictions to modifications through views, T-SQL’s documentation specifies that columns formed by using the set operators UNION, UNION ALL, EXCEPT, and INTERSECT amount to a computation and therefore are also not updatable.

One exception to this restriction is when using the UNION ALL operator to combine rows from different tables to form an updatable partitioned view. That’s a big topic in its own right. I’ll cover it briefly here to give you a sense, and you can investigate it further if you like in the product’s documentation.

Partitioned views predates table and index partitioning in SQL Server. The basic idea is that you can store disjoint subsets of rows in different base tables and have a view that unifies the rows from the different tables using a UNION ALL operator. If certain requirements are met, you can not only read the data through the view but also modify it through the view. SQL Server will figure out how to direct the modifications through the view to the right underlying tables.

The requirements for supporting modifications through such a view include having a partitioning column. Each of the underlying tables needs to have a CHECK constraint based on the partitioning column that defines a disjoint subset of rows. Also, the partitioning column needs to be part of the table’s primary key, meaning it cannot allow NULLs.

Consider the Orders table you used earlier in this article. Suppose that instead of holding all orders in one table, you want to store unshipped orders in one table (called UnshippedOrders) and shipped orders in another table (called ShippedOrders). You also want to create a view called Orders combining the rows from both tables. You want the view to be updatable.

Let’s start by removing any existing objects before creating the new ones:

DROP VIEW IF EXISTS dbo.Orders;DROP TABLE IF EXISTS dbo.OrderDetails, dbo.Orders;DROP TABLE IF EXISTS dbo.ShippedOrders, dbo.UnshippedOrders;

The partitioning column in our example is the shippeddate column. Our first obstacle is that we want to represent unshipped orders with a NULL shippeddate, but the partitioning column cannot allow NULLs. One possible workaround is to decide on some specific future date to represent unshipped orders. For example, the maximum supported date December 31st, 9999. Then you could have a CHECK constraint in the UnshippedOrders table checking that the shipped date is this specific one, and a CHECK constraint in the ShippedOrders table checking that the shipped date is before this one. This will meet the requirement for disjoint sets of rows.

Another obstacle is that the partitioning column needs to be part of the primary key. Originally the primary key was based on the orderid column alone. Now it will need to be extended to be based on (orderid, shippeddate). You will probably still want to enforce uniqueness based on orderid alone. To achieve this, you’ll need to add a unique constraint based on orderid.

With all this in mind, here are the definitions of the ShippedOrders and UnshippedOrders tables:

CREATE TABLE dbo.ShippedOrders( orderid INT NOT NULL, orderdate DATE NOT NULL, shippeddate DATE NOT NULL, CONSTRAINT PK_ShippedOrders PRIMARY KEY(orderid, shippeddate), CONSTRAINT UNQ_ShippedOrders_orderid UNIQUE(orderid), CONSTRAINT CHK_ShippedOrders_shippeddate CHECK(shippeddate <'99991231')); CREATE TABLE dbo.UnshippedOrders( orderid INT NOT NULL, orderdate DATE NOT NULL, shippeddate DATE NOT NULL DEFAULT('99991231'), CONSTRAINT PK_UnshippedOrders PRIMARY KEY(orderid, shippeddate), CONSTRAINT UNQ_UnshippedOrders_orderid UNIQUE(orderid), CONSTRAINT CHK_UnshippedOrders_shippeddate CHECK(shippeddate ='99991231'));

You then create the Orders view, unifying the rows from the two tables using the UNION ALL operator, like so:

CREATE OR ALTER VIEW dbo.OrdersAS SELECT orderid, orderdate, shippeddate FROM dbo.ShippedOrders UNION ALL SELECT orderid, orderdate, shippeddate FROM dbo.UnshippedOrders;GO

Since this view meets all requirements for updatability, you can insert, update, and delete rows through the view. SQL Server will direct the changes to the right underlying tables. As an example, the following statement inserts a few rows, including both shipped and unshipped orders:

INSERT INTO dbo.Orders(orderid, orderdate, shippeddate) VALUES(1, '20210802', '20210804'), (2, '20210802', '20210805'), (3, '20210804', '20210806'), (4, '20210826', '99991231'), (5, '20210827', '99991231');

The plan for this code is shown in Figure 3.

Figure 3:Plan for INSERT statement against partitioned view

As you can see, a Compute Scalar operator computes for each source row a member called Ptn1018. This member is set to 0 for shipped orders (shippeddate <'9999-12-31') and 1 for unshipped orders (shippeddate ='9999-12-31'). The rows are spooled along with the member Ptn1018, and then the spool is read twice. Once filtering the rows where Ptn1018 =0, inserting those into the underlying ShippedOrders table, and another time filtering the rows where Ptn1018 =1, inserting those into the underlying UnshippedOrders table.If this seems like an attractive option, consider it very carefully. Remember this is an old feature, predating table and index partitioning. There are many requirements, restrictions, and complications, including optimization complications, integrity enforcement complications, and others. As mentioned, here I just wanted to cover it briefly to describe the exception to the modification restriction involving set operators.When you’re done, run the following code for cleanup:

DROP VIEW IF EXISTS dbo.Orders;DROP TABLE IF EXISTS dbo.OrderDetails, dbo.Orders;DROP TABLE IF EXISTS dbo.ShippedOrders, dbo.UnshippedOrders;

Tóm tắt

When I started the coverage of views, one of the first things I explained was that a view is a table. You can read data from a view and you can modify data through a view. But you need to understand that modifications through the view are restricted in a few ways, and the outcome of such modifications could be surprising in some cases.

Using the CHECK OPTION, you’re only allowed to update and insert rows through the view as long as the result rows are considered a valid part of the view. This means unlike a CHECK constraint in a table, the CHECK OPTION rejects changes where the inner query’s filter evaluates to unknown (when a NULL is involved). You’re not allowed to insert or update rows through a view if it’s defined with the CHECK OPTION and uses the TOP or OFFSET-FETCH filters. But you’re allowed to delete rows through such a view.

If a view joins multiple base tables, inserts and updates through the view are allowed provided that only one underlying base table is affected. Oddly, if a modification of a single target row involves multiple related source rows, the modification is allowed but is processed as a nondeterministic one. In such a case, SQL Server uses the internal ANY aggregate the pick a single value from the source rows.

You cannot update or insert rows through a view where at least one of the updated columns is a derived one resulting from a computation. The same applies when using a set operator, with an exception when using the UNION ALL operator to create an updatable partitioned view.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sử dụng Jenkins với Kubernetes AWS, Phần 3

  2. Đăng nhập bằng các dịch vụ bên ngoài

  3. Tải xuống bản sao cơ sở dữ liệu của bạn

  4. Đẩy xuống tổng hợp được nhóm

  5. 7 công cụ sơ đồ hóa cơ sở dữ liệu miễn phí cho các thư mục dữ liệu bận rộn