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

NULL Complexities - Phần 2

Bài viết này là bài thứ hai trong loạt bài về độ phức tạp NULL. Tháng trước, tôi đã giới thiệu NULL làm điểm đánh dấu của SQL cho bất kỳ loại giá trị nào bị thiếu. Tôi đã giải thích rằng SQL không cung cấp cho bạn khả năng phân biệt giữa thiếu và có thể áp dụng (A-giá trị) và thiếu và không thể áp dụng (I-giá trị) điểm đánh dấu. Tôi cũng đã giải thích cách so sánh liên quan đến NULL hoạt động với hằng số, biến, tham số và cột. Tháng này, tôi tiếp tục thảo luận bằng cách đề cập đến sự mâu thuẫn trong điều trị NULL trong các phần tử T-SQL khác nhau.

Tôi sẽ tiếp tục sử dụng cơ sở dữ liệu mẫu TSQLV5 như tháng trước trong một số ví dụ của tôi. Bạn có thể tìm thấy tập lệnh tạo và điền cơ sở dữ liệu này tại đây và sơ đồ ER của nó tại đây.

Không nhất quán về điều trị

Như bạn đã thu thập được, điều trị NULL không phải là nhỏ. Một số sự nhầm lẫn và phức tạp liên quan đến thực tế là việc xử lý các NULL có thể không nhất quán giữa các phần tử khác nhau của T-SQL cho các hoạt động tương tự. Trong các phần sắp tới, tôi mô tả xử lý NULL trong tính toán tuyến tính so với tổng hợp, mệnh đề ON / WHERE / HAVING, ràng buộc CHECK so với tùy chọn CHECK, các phần tử IF / WHILE / CASE, câu lệnh MERGE, tính khác biệt và nhóm, cũng như thứ tự và tính duy nhất.

Tính toán tuyến tính so với tính toán tổng hợp

T-SQL, và tương tự đối với SQL tiêu chuẩn, sử dụng logic xử lý NULL khác nhau khi áp dụng một hàm tổng hợp thực tế như SUM, MIN và MAX trên các hàng so với khi áp dụng cùng một phép tính như một hàm tuyến tính trên các cột. Để chứng minh sự khác biệt này, tôi sẽ sử dụng hai bảng mẫu có tên # T1 và # T2 mà bạn tạo và điền bằng cách chạy mã sau:

 DROP TABLE NẾU TỒN TẠI # T1, # T2; SELECT * INTO # T1 FROM (VALUES (10, 5, NULL)) AS D (col1, col2, col3); CHỌN * VÀO # T2 TỪ (VALUES (10), (5), (NULL)) AS D (col1); 

Bảng # T1 có ba cột được gọi là col1, col2 và col3. Nó hiện có một hàng với các giá trị cột 10, 5 và NULL, tương ứng:

 CHỌN * TỪ # T1; 
 col1 col2 col3 ----------- ----------- ----------- 10 5 NULL 

Bảng # T2 có một cột được gọi là col1. Nó hiện có ba hàng với các giá trị 10, 5 và NULL trong cột1:

 CHỌN * TỪ # T2; 
 col1 ----------- 105NULL 

Khi áp dụng tính toán tổng hợp, chẳng hạn như phép cộng tuyến tính qua các cột, sự hiện diện của bất kỳ đầu vào NULL nào sẽ mang lại kết quả NULL. Truy vấn sau thể hiện hành vi này:

 CHỌN col1 + col2 + col3 AS totalFROM # T1; 

Truy vấn này tạo ra kết quả sau:

Tổng
 ----------- NULL 

Ngược lại, các hàm tổng hợp thực tế, được áp dụng trên các hàng, được thiết kế để bỏ qua các đầu vào NULL. Truy vấn sau thể hiện hành vi này bằng cách sử dụng hàm SUM:

 CHỌN SUM (col1) AS totalFROM # T2; 

Truy vấn này tạo ra kết quả sau:

 tổng ----------- 15 Cảnh báo:Giá trị rỗng bị loại bỏ bởi phép toán tổng hợp hoặc SET khác. 

Lưu ý cảnh báo được yêu cầu bởi tiêu chuẩn SQL cho biết sự hiện diện của đầu vào NULL đã bị bỏ qua. Bạn có thể loại bỏ các cảnh báo như vậy bằng cách tắt tùy chọn phiên ANSI_WARNINGS.

Tương tự, khi được áp dụng cho một biểu thức đầu vào, hàm COUNT đếm số hàng có giá trị đầu vào không phải NULL (trái ngược với hàm COUNT (*) chỉ đếm số hàng). Ví dụ:thay thế SUM (col1) bằng COUNT (col1) trong truy vấn trên trả về số lượng là 2.

Thật kỳ lạ, nếu bạn áp dụng tổng hợp COUNT cho một cột được xác định là không cho phép NULL, trình tối ưu hóa sẽ chuyển đổi biểu thức COUNT () thành COUNT (*). Điều này cho phép sử dụng bất kỳ chỉ mục nào cho mục đích đếm thay vì yêu cầu sử dụng chỉ mục có chứa cột được đề cập. Đó là một lý do nữa ngoài việc đảm bảo tính nhất quán và tính toàn vẹn của dữ liệu còn khuyến khích bạn thực thi các ràng buộc như NOT NULL và các ràng buộc khác. Những ràng buộc như vậy cho phép trình tối ưu hóa linh hoạt hơn trong việc xem xét các lựa chọn thay thế tối ưu hơn và tránh các công việc không cần thiết.

Dựa trên logic này, hàm AVG chia tổng các giá trị không phải NULL cho số giá trị không phải NULL. Hãy xem xét truy vấn sau đây làm ví dụ:

 CHỌN AVG (1.0 * col1) AS avgallFROM # T2; 

Ở đây tổng các giá trị col1 không phải NULL 15 được chia cho số giá trị không phải NULL 2. Bạn nhân col1 với ký tự số 1,0 để buộc chuyển đổi ngầm định các giá trị đầu vào số nguyên thành số nguyên để nhận được phép chia số chứ không phải số nguyên phân công. Truy vấn này tạo ra kết quả sau:

 avgall --------- 7.500000 

Tương tự, các tổng MIN và MAX bỏ qua các đầu vào NULL. Hãy xem xét truy vấn sau:

 CHỌN MIN (col1) AS mincol1, MAX (col1) AS maxcol1FROM # T2; 

Truy vấn này tạo ra kết quả sau:

 mincol1 maxcol1 ----------- ----------- 5 10 

Cố gắng áp dụng tính toán tuyến tính nhưng mô phỏng ngữ nghĩa hàm tổng hợp (bỏ qua NULL) không tốt. Giả lập SUM, COUNT và AVG không quá phức tạp, nhưng nó yêu cầu bạn kiểm tra mọi đầu vào cho NULL, như sau:

 CHỌN col1, col2, col3, CASE WHEN COALESCE (col1, col2, col3) LÀ NULL THEN NULL ELSE COALESCE (col1, 0) + COALESCE (col2, 0) + COALESCE (col3, 0) KẾT THÚC LÀ sumall, CASE KHI col1 KHÔNG NULL THÌ 1 ELSE 0 KẾT THÚC + TRƯỜNG HỢP KHI col2 KHÔNG NULL THÌ 1 ELSE 0 KẾT THÚC + TRƯỜNG HỢP KHI col3 KHÔNG NULL THÌ 1 ELSE 0 KẾT THÚC NHƯ cntall, TRƯỜNG HỢP KHI COALESCE (col1, col2, col3) LÀ KHÔNG ĐỦ THÌ NULL ELSE 1.0 * (COALESCE (col1, 0) + COALESCE (col2, 0) + COALESCE (col3, 0)) / (TRƯỜNG HỢP KHI col1 KHÔNG ĐẦY ĐỦ THÌ 1 ELSE 0 KẾT THÚC + TRƯỜNG HỢP KHI col2 KHÔNG ĐỦ THÌ 1 ELSE 0 KẾT THÚC + TRƯỜNG HỢP KHI col3 KHÔNG ĐẦY ĐỦ THÌ 1 ELSE 0 END) KẾT THÚC NHƯ avgallFROM # T1; 

Truy vấn này tạo ra kết quả sau:

 col1 col2 col3 sumall cntall avgall ----------- ----------- ----------- --- ----------- --------------- 10 5 NULL 15 2 7.500000000000 

Việc cố gắng áp dụng mức tối thiểu hoặc tối đa dưới dạng tính toán tuyến tính cho nhiều hơn hai cột đầu vào là khá phức tạp ngay cả trước khi bạn thêm logic để bỏ qua NULL vì nó liên quan đến việc lồng nhiều biểu thức CASE trực tiếp hoặc gián tiếp (khi bạn sử dụng lại bí danh cột). Ví dụ:đây là một truy vấn tính toán giá trị tối đa giữa col1, col2 và col3 trong # T1, không có phần bỏ qua NULL:

 CHỌN col1, col2, col3, TRƯỜNG HỢP KHI col1 LÀ NULL HOẶC col2 LÀ NULL HOẶC col3 LÀ NULL THEN NULL ELSE max2 END AS maxallFROM # T1 CROSS ÁP DỤNG (GIÁ TRỊ (TRƯỜNG HỢP KHI col1> =col2 THEN col1 ELSE col2 END)) ÁP DỤNG CHÉO AS A1 (max1) (CÁC GIÁ TRỊ (TRƯỜNG HỢP KHI max1> =col3 THÌ max1 ELSE col3 END)) AS A2 (max2); 

Truy vấn này tạo ra kết quả sau:

 col1 col2 col3 maxall ----------- ----------- ----------- -10 5 KHÔNG ĐẦY ĐỦ 

Nếu bạn kiểm tra kế hoạch truy vấn, bạn sẽ thấy biểu thức mở rộng sau tính toán kết quả cuối cùng:

 [Expr1005] =Toán tử vô hướng (TRƯỜNG HỢP KHI CÓ TRƯỜNG HỢP KHI [# T1]. [col1] KHÔNG ĐẦY ĐỦ THÌ [# T1]. [col1] KHÔNG ĐỦ TRƯỜNG HỢP KHI [# T1]. [col2] KHÔNG ĐẦY ĐỦ THÌ [ # T1]. [Col2] ELSE [# T1]. [Col3] END END LÀ NULL THEN NULL ELSE CASE KHI CASE WHEN [# T1]. [Col1]> =[# T1]. [Col2] THEN [# T1] . [col1] ELSE [# T1]. [col2] END> =[# T1]. [col3] THEN CASE WHEN [# T1]. [col1]> =[# T1]. [col2] THEN [# T1] . [col1] ELSE [# T1]. [col2] END ELSE [# T1]. [col3] END END) 

Và đó là khi chỉ có ba cột liên quan. Hãy tưởng tượng có hàng tá cột liên quan!

Bây giờ thêm vào logic này để bỏ qua NULL:

 CHỌN col1, col2, col3, max2 AS maxallFROM # T1 ÁP DỤNG CHÉO (GIÁ TRỊ (TRƯỜNG HỢP KHI col1> =col2 HOẶC col2 LÀ KHÔNG ĐỦ THÌ col1 ELSE col2 END)) ÁP DỤNG CHÉO A1 (max1) AS A1 (GIÁ TRỊ (TRƯỜNG HỢP KHI max1> =col3 OR col3 IS NULL THEN max1 ELSE col3 END)) AS A2 (max2); 

Truy vấn này tạo ra kết quả sau:

 col1 col2 col3 maxall ----------- ----------- ----------- -10 5 NULL 10 

Oracle có một cặp hàm được gọi là GREATEST và LEAST áp dụng các phép tính tối thiểu và tối đa tương ứng như các phép tính tuyến tính cho các giá trị đầu vào. Các hàm này trả về NULL cho bất kỳ đầu vào NULL nào giống như hầu hết các phép tính tuyến tính thực hiện. Có một mục phản hồi mở yêu cầu nhận các chức năng tương tự trong T-SQL, nhưng yêu cầu này không được chuyển trong thay đổi trang web phản hồi mới nhất của họ. Nếu Microsoft thêm các chức năng như vậy vào T-SQL, sẽ thật tuyệt nếu có tùy chọn kiểm soát việc có bỏ qua NULL hay không.

Trong khi đó, có một kỹ thuật thanh lịch hơn nhiều so với các kỹ thuật đã nói ở trên, tính toán bất kỳ loại tổng hợp nào dưới dạng tuyến tính trên các cột bằng cách sử dụng ngữ nghĩa hàm tổng hợp thực tế bỏ qua NULL. Bạn sử dụng kết hợp toán tử ÁP DỤNG CHÉO và truy vấn bảng dẫn xuất chống lại hàm tạo giá trị bảng xoay các cột thành các hàng và áp dụng tổng hợp như một hàm tổng hợp thực tế. Dưới đây là một ví dụ minh họa các phép tính MIN và MAX, nhưng bạn có thể sử dụng kỹ thuật này với bất kỳ hàm tổng hợp nào mà bạn thích:

 CHỌN col1, col2, col3, maxall, minallFROM # T1 ÁP DỤNG CHÉO (CHỌN MAX (mycol), MIN (mycol) TỪ (VALUES (col1), (col2), (col3)) AS D1 (mycol)) AS D2 (maxall, minall); 

Truy vấn này tạo ra kết quả sau:

 col1 col2 col3 maxall minall ----------- ----------- ----------- - ----------- 10 5 ĐẦY 10 5 

Còn nếu bạn muốn điều ngược lại? Điều gì sẽ xảy ra nếu bạn cần tính tổng hợp trên các hàng, nhưng tạo ra NULL nếu có bất kỳ đầu vào NULL nào? Ví dụ:giả sử rằng bạn cần tính tổng tất cả các giá trị col1 từ # T1, nhưng trả về NULL nếu bất kỳ đầu vào nào là NULL. Điều này có thể đạt được bằng kỹ thuật sau:

 SELECT SUM (col1) * NULLIF (MIN (TRƯỜNG HỢP KHI col1 LÀ KHÔNG ĐỦ THÌ 0 ELSE 1 END), 0) AS sumallFROM # T2; 

Bạn áp dụng tổng hợp MIN cho biểu thức CASE trả về số không cho đầu vào NULL và số không cho đầu vào không NULL. Nếu có bất kỳ đầu vào NULL nào, kết quả của hàm MIN là 0, nếu không thì là 1. Sau đó, sử dụng hàm NULLIF, bạn chuyển đổi kết quả 0 thành NULL. Sau đó, bạn nhân kết quả của hàm NULLIF với tổng ban đầu. Nếu có bất kỳ đầu vào NULL nào, bạn nhân tổng ban đầu với NULL tạo ra NULL. Nếu không có đầu vào NULL, bạn nhân kết quả của tổng ban đầu với 1, thu được tổng ban đầu.

Quay lại các phép tính tuyến tính tạo ra NULL cho bất kỳ đầu vào NULL nào, cùng một logic áp dụng cho việc nối chuỗi bằng toán tử +, như truy vấn sau minh họa:

 SỬ DỤNG TSQLV5; CHỌN trống, quốc gia, khu vực, thành phố, quốc gia + N ',' + khu vực + N ',' + thành phố AS emplocationFROM HR. Nhân viên; 

Truy vấn này tạo ra kết quả sau:

 empid vùng đất nước khu vực thành phố phân bổ ----------------- ---------------- -------------- ---------------- 1 USA WA Seattle USA, WA, Seattle2 USA WA Tacoma USA, WA, Tacoma3 USA WA Kirkland USA, WA, Kirkland4 USA WA Redmond USA, WA, Redmond5 UK NULL London NULL6 UK ​​NULL London NULL7 UK NULL London NULL8 USA WA Seattle USA, WA, Seattle9 UK NULL London NULL 

Bạn muốn nối các phần vị trí của nhân viên thành một chuỗi, sử dụng dấu phẩy làm dấu phân cách. Nhưng bạn muốn bỏ qua đầu vào NULL. Thay vào đó, khi bất kỳ đầu vào nào là NULL, bạn sẽ nhận được kết quả là NULL. Một số tắt tùy chọn phiên CONCAT_NULL_YIELDS_NULL, điều này khiến đầu vào NULL được chuyển đổi thành chuỗi trống cho mục đích nối, nhưng tùy chọn này không được khuyến nghị vì nó áp dụng hành vi không chuẩn. Hơn nữa, bạn sẽ bị bỏ lại với nhiều dấu phân cách liên tiếp khi có đầu vào NULL, đây thường không phải là hành vi mong muốn. Một tùy chọn khác là thay thế rõ ràng đầu vào NULL bằng một chuỗi trống bằng cách sử dụng hàm ISNULL hoặc COALESCE, nhưng điều này thường dẫn đến mã dài dòng. Một tùy chọn thanh lịch hơn nhiều là sử dụng hàm CONCAT_WS, được giới thiệu trong SQL Server 2017. Hàm này nối các đầu vào, bỏ qua NULL, sử dụng dấu phân tách được cung cấp làm đầu vào đầu tiên. Đây là truy vấn giải pháp sử dụng chức năng này:

 SELECT empid, country, region, city, CONCAT_WS (N ',', country, region, city) AS emplocationFROM HR.E Employees; 

Truy vấn này tạo ra kết quả sau:

 empid vùng đất nước khu vực thành phố phân bổ ----------------- ---------------- -------------- ---------------- 1 USA WA Seattle USA, WA, Seattle2 USA WA Tacoma USA, WA, Tacoma3 USA WA Kirkland USA, WA, Kirkland4 USA WA Redmond USA, WA, Redmond5 UK NULL London UK, London6 UK NULL London UK, London7 UK NULL London UK, London8 USA WA Seattle USA, WA, Seattle9 UK NULL London UK, London 

BẬT / ĐÂU / CÓ

Khi sử dụng các mệnh đề truy vấn WHERE, HAVING và ON cho mục đích lọc / đối sánh, điều quan trọng cần nhớ là chúng sử dụng logic vị từ ba giá trị. Khi bạn có liên quan đến logic ba giá trị, bạn muốn xác định chính xác cách mệnh đề xử lý các trường hợp TRUE, FALSE và UNKNOWN. Ba mệnh đề này được thiết kế để chấp nhận các trường hợp ĐÚNG, và bác bỏ các trường hợp SAI và KHÔNG BIẾT.

Để chứng minh hành vi này, tôi sẽ sử dụng một bảng có tên là Danh sách liên hệ mà bạn tạo và điền bằng cách chạy mã sau:.

 DROP TABLE NẾU TỒN TẠI dbo.Contacts; ĐI TẠO BẢNG dbo.Contacts (id INT NOT NULL CONSTRAINT PK_Contacts PRIMARY KEY, tên VARCHAR (10) NOT NULL, theo giờ NUMERIC (12, 2) NULL CONSTRAINT CHK_Contacts_hourlyrate CHECK) )); CHÈN VÀO Dbo.Contacts (id, tên, tốc độ hàng giờ) GIÁ TRỊ (1, 'A', 100.00), (2, 'B', 200.00), (3, 'C', NULL); 

Lưu ý rằng địa chỉ liên hệ 1 và 2 có mức giá theo giờ áp dụng còn liên hệ 3 thì không, do đó, giá theo giờ được đặt thành NULL. Hãy xem xét truy vấn sau đây để tìm kiếm các địa chỉ liên hệ có tỷ lệ tích cực theo giờ:

 CHỌN id, tên, tốc độ hàng giờFROM dbo.ContactsWHERE tốc độ hàng giờ> 0,00; 

Vị từ này đánh giá là TRUE cho các địa chỉ liên hệ 1 và 2, và UNKNOWN cho địa chỉ liên hệ 3, do đó đầu ra chỉ chứa các địa chỉ liên hệ 1 và 2:

 id tên hàng giờ ----------- ---------- ----------- 1 A 100,002 B 200,00 

Suy nghĩ ở đây là khi bạn chắc chắn rằng vị từ là đúng, bạn muốn trả lại hàng, nếu không, bạn muốn loại bỏ nó. Điều này thoạt đầu có vẻ tầm thường, cho đến khi bạn nhận ra rằng một số thành phần ngôn ngữ cũng sử dụng vị ngữ hoạt động khác.

Ràng buộc KIỂM TRA so với tùy chọn KIỂM TRA

Ràng buộc CHECK là một công cụ mà bạn sử dụng để thực thi tính toàn vẹn trong một bảng dựa trên một vị từ. Vị từ được đánh giá khi bạn cố gắng chèn hoặc cập nhật các hàng trong bảng. Không giống như các mệnh đề phù hợp và lọc truy vấn chấp nhận các trường hợp ĐÚNG và từ chối các trường hợp FALSE và UNKNOWN, ràng buộc CHECK được thiết kế để chấp nhận các trường hợp TRUE và UNKNOWN và từ chối các trường hợp FALSE. Suy nghĩ ở đây là khi bạn chắc chắn rằng vị từ là sai, bạn muốn từ chối thay đổi đã cố gắng, nếu không, bạn muốn cho phép điều đó.

Nếu bạn xem xét định nghĩa của bảng Danh bạ của chúng tôi, bạn sẽ nhận thấy rằng bảng này có ràng buộc KIỂM TRA sau đây, từ chối các liên hệ có mức giá theo giờ không dương tính:

 CONSTRAINT CHK_Contacts_hourlyrate CHECK (tốc độ hàng giờ> 0,00) 

Lưu ý rằng ràng buộc sử dụng cùng một vị từ như bạn đã sử dụng trong bộ lọc truy vấn trước đó.

Cố gắng thêm một địa chỉ liên hệ với tỷ lệ tích cực theo giờ:

 CHÈN VÀO Dbo.Contacts (id, tên, tốc độ hàng giờ) GIÁ TRỊ (4, 'D', 150,00); 

Nỗ lực này thành công.

Cố gắng thêm một địa chỉ liên hệ với tỷ lệ NULL giờ:

 CHÈN VÀO Dbo.Contacts (id, tên, tốc độ hàng giờ) GIÁ TRỊ (5, 'E', NULL); 

Nỗ lực này cũng thành công vì ràng buộc KIỂM TRA được thiết kế để chấp nhận các trường hợp ĐÚNG và KHÔNG BIẾT. Đó là trường hợp bộ lọc truy vấn và ràng buộc KIỂM TRA được thiết kế để hoạt động khác nhau.

Cố gắng thêm một địa chỉ liên hệ với tỷ lệ theo giờ không phụ thuộc:

 CHÈN VÀO Dbo.Contacts (id, tên, tốc độ hàng giờ) GIÁ TRỊ (6, 'F', -100,00); 

Nỗ lực này không thành công với lỗi sau:

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

T-SQL cũng cho phép bạn thực thi tính toàn vẹn của các sửa đổi thông qua các khung nhìn bằng cách sử dụng tùy chọn KIỂM TRA. Một số nghĩ về tùy chọn này phục vụ mục đích tương tự như ràng buộc KIỂM TRA miễn là bạn áp dụng sửa đổi thông qua chế độ xem. Ví dụ:hãy xem xét chế độ xem sau, chế độ xem này sử dụng bộ lọc dựa trên tốc độ hàng giờ của vị từ> 0,00 và được xác định bằng tùy chọn KIỂM TRA:

 TẠO HOẶC AL SAU XEM dbo.MyContactsASSELECT id, name, hourlyrateFROM dbo.ContactsWHERE hourslyrate> 0.00WITH CHECK OPTION; 

Hóa ra, không giống như ràng buộc KIỂM TRA, tùy chọn KIỂM TRA dạng xem được thiết kế để chấp nhận các trường hợp ĐÚNG và từ chối cả các trường hợp SAI và KHÔNG BIẾT. Vì vậy, nó thực sự được thiết kế để hoạt động giống như bộ lọc truy vấn bình thường cũng nhằm mục đích thực thi tính toàn vẹn.

Thử chèn một hàng có tỷ lệ giờ dương qua chế độ xem:

 CHÈN VÀO dbo.MyContacts (id, tên, tốc độ hàng giờ) GIÁ TRỊ (7, 'G', 300,00); 

Nỗ lực này thành công.

Thử chèn một hàng có tỷ lệ NULL giờ qua chế độ xem:

 CHÈN VÀO Dbo.MyContacts (id, tên, tốc độ hàng giờ) GIÁ TRỊ (8, 'H', NULL); 

Nỗ lực này không thành công với lỗi sau:

Msg 550, Mức 16, Trạng thái 1, Dòng 473
Cố gắng chèn hoặc cập nhật không thành công do chế độ xem đích chỉ định CÓ 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.

Suy nghĩ ở đây là khi bạn thêm tùy chọn KIỂM TRA vào chế độ xem, bạn chỉ muốn cho phép các sửa đổi dẫn đến các hàng được chế độ xem trả về. Điều đó hơi khác so với suy nghĩ với ràng buộc KIỂM TRA — từ chối những thay đổi mà bạn chắc chắn rằng vị từ là sai. Điều này có thể hơi khó hiểu. Nếu bạn muốn chế độ xem cho phép các sửa đổi đặt tốc độ hàng giờ thành NULL, bạn cần bộ lọc truy vấn để cho phép các sửa đổi đó bằng cách thêm HOẶC tốc độ hàng giờ LÀ NULL. Bạn chỉ cần nhận ra rằng ràng buộc CHECK và tùy chọn CHECK được thiết kế để hoạt động khác nhau đối với trường hợp UNKNOWN. Cái trước chấp nhận nó trong khi cái sau từ chối nó.

Truy vấn bảng Liên hệ sau tất cả các thay đổi ở trên:

 SELECT id, name, hourlyrateFROM dbo.Contacts; 

Bạn sẽ nhận được kết quả sau tại thời điểm này:

 id tên hàng giờ ----------- ---------- ----------- 1 A 100,002 B 200,003 C NULL4 D 150,005 E NULL7 G 300,00 

IF / WHILE / CASE

Các phần tử ngôn ngữ IF, WHILE và CASE hoạt động với các vị từ.

Câu lệnh IF được thiết kế như sau:

 IF   ELSE  

Thật trực quan khi mong đợi có một khối TRUE theo sau mệnh đề IF và một khối FALSE theo sau mệnh đề ELSE, nhưng bạn cần nhận ra rằng mệnh đề ELSE thực sự được kích hoạt khi vị từ là FALSE hoặc UNKNOWN. Về mặt lý thuyết, một ngôn ngữ logic ba giá trị có thể có một câu lệnh IF với sự tách biệt của ba trường hợp. Một cái gì đó như thế này:

 IF  WHEN TRUE  WHEN FALSE  WHEN UNKNOWN  

Và thậm chí cho phép kết hợp các kết quả hợp lý để nếu bạn muốn kết hợp FALSE và UNKNOWN thành một phần, bạn có thể sử dụng một cái gì đó như sau:

 IF  WHEN TRUE  WHEN FALSE HOẶC UNKNOWN  

Trong khi đó, bạn có thể mô phỏng các cấu trúc như vậy bằng cách lồng các câu lệnh IF-ELSE và tìm kiếm các NULL trong toán hạng một cách rõ ràng bằng toán tử IS NULL.

Câu lệnh WHILE chỉ có một khối TRUE. Nó được thiết kế như sau:

 WHILE   

Câu lệnh hoặc khối BEGIN-END tạo thành phần thân của vòng lặp được kích hoạt trong khi vị từ là TURE. Ngay khi vị từ là FALSE hoặc UNKNOWN, điều khiển sẽ chuyển đến câu lệnh sau vòng lặp WHILE.

Không giống như IF và WHILE, là các câu lệnh thực thi mã, CASE là một biểu thức trả về một giá trị. Cú pháp của một đã tìm kiếm Biểu thức CASE như sau:

 CASE WHEN  THEN  WHEN  THEN  ... WHEN  THEN  ELSE  END 

Biểu thức CASE được thiết kế để trả về biểu thức theo sau mệnh đề THEN tương ứng với vị từ WHEN đầu tiên được đánh giá là TRUE. Nếu có mệnh đề ELSE, mệnh đề đó sẽ được kích hoạt nếu không có vị từ WHEN nào là ĐÚNG (tất cả đều SAI hoặc KHÔNG BIẾT). Vắng một mệnh đề ELSE rõ ràng, một ELSE NULL ngầm được sử dụng. Nếu bạn muốn xử lý riêng trường hợp UNKNOWN, bạn có thể tìm kiếm các NULL trong toán hạng của vị từ một cách rõ ràng bằng cách sử dụng toán tử IS NULL.

Đ đơn giản Biểu thức CASE sử dụng các so sánh dựa trên đẳng thức ngầm định giữa biểu thức nguồn và các biểu thức được so sánh:

 CASE  WHEN  THEN  KHI  THEN  ... WHEN  THEN  ELSE  END 

Biểu thức CASE đơn giản được thiết kế tương tự như biểu thức CASE đã tìm kiếm về cách xử lý logic ba giá trị, nhưng vì các phép so sánh sử dụng phép so sánh dựa trên đẳng thức ngầm định, bạn không thể xử lý riêng trường hợp UNKNOWN. Cố gắng sử dụng NULL trong một trong các biểu thức được so sánh trong mệnh đề WHEN là vô nghĩa vì so sánh sẽ không dẫn đến TRUE ngay cả khi biểu thức nguồn là NULL. Hãy xem xét ví dụ sau:

 DECLARE @input AS INT =NULL; CHỌN CASE @input KHI NULL THEN 'Đầu vào là NULL' ELSE 'Đầu vào không phải là NULL' END; 

Điều này được chuyển đổi hoàn toàn thành như sau:

 DECLARE @input AS INT =NULL; CHỌN TRƯỜNG HỢP KHI @input =NULL THEN 'Đầu vào là NULL' ELSE 'Đầu vào không phải là NULL' END; 

Do đó, kết quả là:

Đầu vào không phải là NULL

Để phát hiện đầu vào NULL, bạn cần sử dụng cú pháp biểu thức CASE đã tìm kiếm và toán tử IS NULL, như sau:

 DECLARE @input AS INT =NULL; CHỌN TRƯỜNG HỢP KHI @input LÀ NULL THÌ 'Đầu vào là NULL' ELSE 'Đầu vào không phải là NULL' END; 

Lần này kết quả là:

Đầu vào là NULL

MERGE

Câu lệnh MERGE được sử dụng để hợp nhất dữ liệu từ một nguồn vào một đích. Bạn sử dụng một vị từ hợp nhất để xác định các trường hợp sau và áp dụng một hành động chống lại mục tiêu:

  • Hàng nguồn được so khớp với hàng đích (được kích hoạt khi tìm thấy kết quả khớp cho hàng nguồn mà vị từ hợp nhất là ĐÚNG):áp dụng CẬP NHẬT hoặc XÓA đối với mục tiêu
  • Hàng nguồn không được so khớp với hàng đích (được kích hoạt khi không tìm thấy kết quả phù hợp nào cho hàng nguồn mà vị từ hợp nhất là TRUE, thay vì đối với tất cả vị từ là FALSE hoặc UNKNOWN):áp dụng CHÈN đối với mục tiêu
  • Một hàng mục tiêu không được so khớp với một hàng nguồn (được kích hoạt khi không tìm thấy kết quả phù hợp nào cho hàng mục tiêu trong đó vị từ hợp nhất là ĐÚNG, thay vì đối với tất cả các vị từ là FALSE hoặc UNKNOWN):áp dụng CẬP NHẬT hoặc XÓA đối với mục tiêu

Tất cả ba trường hợp đều tách TRUE cho một nhóm và FALSE hoặc UNKNOWN cho một nhóm khác. Bạn không nhận được các phần riêng biệt để xử lý ĐÚNG, xử lý SAI và xử lý các trường hợp KHÔNG BIẾT.

Để chứng minh điều này, tôi sẽ sử dụng một bảng có tên T3 mà bạn tạo và điền bằng cách chạy mã sau:

 DROP TABLE IF tồn tại dbo.T3; ĐI TẠO BẢNG dbo.T3 (col1 INT NULL, col2 INT NULL, CONSTRAINT UNQ_T3 UNIQUE (col1)); CHÈN VÀO CÁC GIÁ TRỊ dbo.T3 (col1) (1), (2), (NULL); 

Hãy xem xét câu lệnh MERGE sau:

 MERGE INTO dbo.T3 AS TGTUSING (VALUES (1, 100), (3, 300)) AS SRC (col1, col2) TRÊN SRC.col1 =TGT.col1 KHÔNG PHÙ HỢP VỚI CÁC GIÁ TRỊ CHÈN (col1, col2) (SRC.col1, SRC.col2) KHI CHƯA ĐƯỢC ĐỐI VỚI NGUỒN THÌ CẬP NHẬT SET col2 =-1; CHỌN col1, col2 TỪ dbo.T3; 

Hàng nguồn trong đó col1 là 1 được khớp với hàng mục tiêu trong đó col1 là 1 (vị từ là TRUE) và do đó col2 của hàng đích được đặt thành 100.

Hàng nguồn trong đó col1 là 3 không được khớp với bất kỳ hàng đích nào (đối với tất cả các vị từ là FALSE hoặc UNKNOWN) và do đó một hàng mới được chèn vào T3 với 3 là giá trị col1 và 300 là giá trị col2.

Các hàng đích trong đó col1 là 2 và col1 là NULL không được khớp với bất kỳ hàng nguồn nào (đối với tất cả các hàng, vị từ là FALSE hoặc UNKNOWN) và do đó trong cả hai trường hợp, col2 trong các hàng đích được đặt thành -1.

Truy vấn chống lại T3 trả về kết quả sau sau khi thực hiện câu lệnh MERGE ở trên:

 col1 col2 ----------- ----------- 1 1002 -1NULL -13 300 

Giữ bảng T3 xung quanh; nó được sử dụng sau này.

Tính khác biệt và nhóm

Không giống như so sánh được thực hiện bằng cách sử dụng toán tử bình đẳng và bất bình đẳng, so sánh được thực hiện cho mục đích phân biệt và nhóm nhóm các NULL lại với nhau. Một NULL được coi là không khác biệt với NULL khác, nhưng NULL được coi là khác biệt với một giá trị không phải NULL. Do đó, việc áp dụng mệnh đề DISTINCT sẽ loại bỏ các lần xuất hiện trùng lặp của NULL. Truy vấn sau đây chứng minh điều này:

 CHỌN DISTINCT quốc gia, khu vực TỪ Nhân sự. Nhân viên; 

Truy vấn này tạo ra kết quả sau:

 vùng quốc gia --------------- --------------- Vương quốc Anh NULLUSA WA 

Có nhiều nhân viên với quốc gia Hoa Kỳ và khu vực NULL, và sau khi loại bỏ các trùng lặp, kết quả chỉ hiển thị một lần xuất hiện của sự kết hợp.

Giống như tính khác biệt, việc nhóm cũng nhóm các NULL với nhau, như truy vấn sau minh họa:

 CHỌN quốc gia, khu vực, COUNT (*) AS số 

Truy vấn này tạo ra kết quả sau:

 các số khu vực quốc gia ------------------------------- ----------- UK NULL 4USA WA 5 

Một lần nữa, tất cả bốn nhân viên ở quốc gia Anh và khu vực NULL đã được nhóm lại với nhau.

Đặt hàng

Đặt hàng coi nhiều NULL có cùng giá trị đặt hàng. Tiêu chuẩn SQL giao nó cho việc triển khai để chọn thứ tự NULL đầu tiên hoặc cuối cùng so với các giá trị không phải NULL. Microsoft đã chọn coi NULL là có giá trị thứ tự thấp hơn so với không phải NULL trong SQL Server, vì vậy khi sử dụng hướng thứ tự tăng dần, T-SQL sắp xếp thứ tự NULL trước. Truy vấn sau đây chứng minh điều này:

 CHỌN id, tên, tốc độ hàng giờFROM dbo.ContactsORDER THEO tốc độ hàng giờ; 

Truy vấn này tạo ra kết quả sau:

 id tên tốc độ hàng giờ ----------- ---------- ----------- 3 C NULL5 E NULL1 A 100,004 D 150,002 B 200,007 G 300,00 

Tháng tới, tôi sẽ bổ sung thêm về chủ đề này, thảo luận về các phần tử tiêu chuẩn giúp bạn kiểm soát hành vi đặt hàng NULL và cách giải quyết cho các phần tử đó trong T-SQL.

Tính độc đáo

Khi thực thi tính duy nhất trên cột NULLable bằng cách sử dụng ràng buộc UNIQUE hoặc chỉ mục duy nhất, T-SQL xử lý NULL giống như các giá trị không phải NULL. Nó từ chối các NULL trùng lặp như thể một NULL không phải là duy nhất từ ​​một NULL khác.

Nhớ lại rằng bảng T3 của chúng ta có một ràng buộc DUY NHẤT được xác định trên col1. Đây là định nghĩa của nó:

 CONSTRAINT UNQ_T3 UNIQUE (col1) 

Truy vấn T3 để xem nội dung hiện tại của nó:

 CHỌN * TỪ dbo.T3; 

Nếu bạn đã chạy tất cả các sửa đổi đối với T3 từ các ví dụ trước đó trong bài viết này, bạn sẽ nhận được kết quả sau:

 col1 col2 ----------- ----------- 1 1002 -1NULL -13 300 

Cố gắng thêm hàng thứ hai có NULL trong cột1:

 CHÈN VÀO GIÁ TRỊ dbo.T3 (col1, col2) (NULL, 400); 

Bạn gặp lỗi sau:

Bản tin thứ 2627, Mức 14, Trạng thái 1, Dòng 558
Vi phạm ràng buộc KEY DUY NHẤT 'UNQ_T3'. Không thể chèn khóa trùng lặp trong đối tượng 'dbo.T3'. Giá trị khóa trùng lặp là ().

Hành vi này thực sự không đạt tiêu chuẩn. Tháng tới, tôi sẽ mô tả thông số kỹ thuật tiêu chuẩn và cách mô phỏng nó trong T-SQL.

Kết luận

Trong phần thứ hai này của loạt bài về độ phức tạp NULL, tôi đã tập trung vào sự mâu thuẫn trong xử lý NULL giữa các phần tử T-SQL khác nhau. Tôi đã đề cập đến các phép tính tuyến tính so với tổng hợp, lọc và so khớp các mệnh đề, ràng buộc CHECK so với tùy chọn CHECK, các phần tử IF, WHILE và CASE, câu lệnh MERGE, tính khác biệt và nhóm, thứ tự và tính duy nhất. Những mâu thuẫn mà tôi đã đề cập càng nhấn mạnh tầm quan trọng của việc hiểu đúng cách xử lý NULL trong nền tảng bạn đang sử dụng, để đảm bảo rằng bạn viết mã chính xác và mạnh mẽ. Tháng tới, tôi sẽ tiếp tục loạt bài này bằng cách đề cập đến các tùy chọn xử lý NULL theo tiêu chuẩn SQL không có trong T-SQL và cung cấp các giải pháp thay thế được hỗ trợ trong T-SQL.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Beverly Hills 90210 và ZIP + 4:Xử lý địa chỉ trong mô hình dữ liệu

  2. Prisma, cách đảo ngược thứ tự

  3. 30 câu hỏi phỏng vấn truy vấn SQL hàng đầu bạn phải thực hành vào năm 2022

  4. Loại bỏ sự trùng lặp của biểu thức vị trí trong ứng dụng

  5. Các nguyên tắc cơ bản về biểu thức bảng, Phần 3 - Bảng có nguồn gốc, cân nhắc tối ưu hóa