1-3 Lý luận 3 giá trị và NULL

1-3 Lý luận 3 giá trị và NULL

Cái bẫy ngọt ngào của SQL

Hầu hết các ngôn ngữ lập trình thông thường thì sẽ trả lai 2 kết quả mang ý nghĩa đúng hay sai, nhưng tại SQL đang sử dụng một thể logic khá đặc biệt là lý luận 3 giá trị trong đó có thêm giá trị thứ 3 là "Unknown". Thuyết 3 giá trị này làm những lập trình viên phải đau đầu khi tần suất cho xem những động tác mang tính thủ thuật, mưu mẹo khá cao. Chương này sẽ đào sâu hơn lý giải thông qua những ví dụ mang tính thực tiễn và lý luận về thuyết 3 giá trị này.

Tùy trường hợp nhưng có thể chỉ cần trong cơ sở dữ liệu có một giá trị NULL thì có thể xảy ra khả năng từ query sẽ không trả lại được kết quả đúng. Hơn nữa,chúng ta cũng không có phương pháp nào để biết được đâu là kết quả không đúng được trả về từ query, tất cả kết quả chúng ta đều nhìn bằng con mắt nghi ngờ. Từ cơ sở dữ liệu có chứa kết quả NULL thì không thể tự tin chắc chắn hoàn toàn sẽ có được kết quả đúng. - C.J.Date

Mở đầu

Rất nhiền ngôn ngữ lập trình có dạng dữ liệu là dạng giá trị đúng sai (dạng BOOL, dạng BOOLEAN). Tất nhiên, ở SQL cũng tồn tại dạng giá trị đúng sai. Người dùng được định nghĩa trực tiếp dạng dữ liệu sử dụng là SQL-99, nhưng trong những trường hợp sử dụng đánh giá điều kiện như mệnh đề WHERE thì phép tính cũng được thực hiện theo dạng giá trị đúng sai.

Nhưng các bạn có biết sự khác nhau giữa dạng giá trị đúng sai của những ngôn ngữ lập trình thông thường với dạng giá trị đúng sai của SQL không? Đó chính là, so với dạng đúng sai của những ngôn ngữ thông thường sẽ trả lạo 2 giá trị là true, false thì SQL ngoài 2 giá thì đó còn mang thêm giá trị thứ ba là unknown. Vậy nên, so sánh với thuyết được gọi là thuyết 2 giá trị thông thường mang 2 giá trị đúng sai trên thì dạng lý luận của SQL là thuyết 3 giá trị (three-valued logic).

Vậy thì tại sao tại SQL thì thuyết 3 giá trị lại được đưa vào sử dụng? Đại số Bool là cơ sở của máy tính là luận lý 2 giá trị và bộ môn toán hay logic mà chúng ta được học cho đến cấp 3 cũng dựa theo luận lý 2 giá trị. Mặc dù là như vậy nhưng tại sao chỉ trong thế giới cơ sở dữ liệu quan hệ thì lại có mang một luận lý khác như thế này?

Câu trả lời đó chính là NULL. Vì cơ sở dữ liệu quan hệ mang vào giá trị NULL nên đồng thời nó mang vào giá trị đúng sai thứ 3. Luận lý 3 giá trị cũng thường xuyên làm cho những kĩ sư DB phải cực khổ khi cho xem những động tác phản trực quan và khá thủ thuật.

Tại chương này cùng với việc giải thích về luận lý 3 giá trị thì chúng tôi sẽ giải thích việc cần phải chú ý ở những mặt nào, cùng với việc phân tích những code nguồn trong ví dụ cụ thể. Trên tính chất của chủ đề thì nửa trước sẽ có nhiều câu chuyện gần với lý luận nhưng chúng tôi cũng mong muốn mọi người sẽ theo đến cuối cùng. Đối với những người đã hiểu về những lý luận hay có thể vừa nhìn ví dụ cụ thể mà có thể hiểu một cách nhanh chóng thì có thể chuyển đến nửa chương sau là chương thực hành, chỉ đọc chương lý luận dưới hình thức tham khảo cũng không sao.

Chương lý luận

2 loại NULL. Là luận lý 3 giá trị hay luận lý 4 giá trị?

Trước khi bắt đầu câu chuyện về luận lý 3 giá trị thì đầu tiên chúng ta sẽ bắt đầu với câu chuyện về NULL. Lý do là vì NULL chính là hạt nhân phân chia ra luận lý 3 giá trị này.

Từ có 2 loại NULL có thể mang lại cho người ta cảm giác kì lạ khi nghe. Đó là vì trong SQL chỉ tồn tại 1 loại NULL. Chính vì vậy, đầu tiên chúng tôi sẽ giải thích sự phân biệt với tư cách đây là kiến thức cơ bản.

2 loại NULL có nghĩa là "Unknown""Not Applicable/Inapplicable". Ví dụ, chúng ta không thể biết được màu mắt của những người đeo kính râm. Chuyện mắt của người ấy có màu là chắc chắn nhưng cho đến khi gỡ kính ra và điều tra thì màu mắt của người ta sẽ là unknown. Tuy nhiên, trong trường hợp này việc không nhìn thấy màu mắt là lại là vì tủ lạnh không thể áp dụng thuộc tính màu mắt được, đây được gọi là Inapplicable.

Khái niệm, "màu mắt của tủ lạnh" cũng vô ý nghĩa như những khái niệm "thể tích của hình tròn", "tỷ lệ sinh sản của đàn ông". Bình thường từ miệng chúng ta có thể nói là "Tôi không biết", nhưng cái sự không biết đó cũng có nhiều chủng loại. Khái niệm NULL là Inapplicable thì so với ý nghĩa chưa biết thì nó gần với ý nghĩa "vô nghĩa" hơn. Có nghĩa nếu chúng ta gán cho unknown ý nghĩa là "Bây giờ không hiểu nhưng nếu đầy đủ điều kiện thì có thể hiểu" thì "Inapplicable" sẽ mang ý nghĩa "Kể cả có cố gắng thế nào đi chăng nữa thì cũng không hiểu".

Người khởi xướng cho việc phân loại này đầu tiên chính là người khởi thủy ra cơ sở dữ liệu quan hệ E.F.Codd. Sự phân loại "Thông tin bị mất" của Codd được biểu thị như dưới đây.

Thông tin bị mất sẽ được chia làm 2 loại là giá trị của thuộc tính và bộ dữ liệu. Trong giá trị của thuộc tính sẽ chia ra là Unknown(Applicable A-marked), Inapllicable(I-marked) và những thứ khác. Trong bộ dữ liệu sẽ có sự kiện không được thành lập (non-event), sự kiện được thành lập rồi nhưng DB không biết (events not known to DBMS) và sự kiện không thể thành lập (inapplicable events).

Codd đã từng khởi xướng việc chia NULL một cách cẩn thận ra thành luận lý 4 giá trị.

Dưới đây là bảng hiển thị 4 giá trị logic. NULL UNKNOWN thì ta sẽ để applicable, còn NULL Not Applicable thì ta sẽ đưa vào bảng là giá trị inapplicable. Cho đến cùng thì đây cũng chỉ là tham khảo. Cho dù là tiến sĩ Codd có ơn lớn như thế nào thì cũng không phải chúng ta phải tiếp nhận tất cả mọi thứ.

x NOT x
t f
a a
i i
f t

|AND|t|a|i|f| |--------|--------| |t|t|a|i|f| |a|a|a|i|f| |i|i|i|i|f| |f|f|f|f|f|

|OR|t|a|i|f| |--------|--------| |t|t|t|t|t| |a|t|a|a|a| |i|t|a|i|f| |f|t|a|f|f|

Không biết là may mắn hay không may (Theo như tôi nghĩ là may mắn) nhưng hiện nay người ta không duy trì suy nghĩ đó nữa, hiện tại tất cả DBMS thì NULL được tập hợp lại thành 1 và tạo nên luận lý 3 giá trị. Tuy nhiên, sự phân loại này là một sự phân loại rất khéo léo nên sau này cũng có rất nhiều luận giả kế thừa và tiếp nối.

Tại sao không phải [=NULL] mà lại phải viết thành [IS NULL]?

Về vấn đề này thì chắc chắn không ít người đặt nghi vấn tại đây. Khi mới học về SQL trong một dãy, khi muốn chọn NULL thì viết như query dưới đây sẽ thành thất bại, chắc hầu hết mọi người đều mang kinh nghiệm như trên đúng không?

--SQL thất bại khi gọi ra NULL
SELECT *
  FROM tbl_A
 WHERE col_1 = NULL

Cũng như mọi người đã biết, SQL này sẽ thất bại. Đúng thì chúng ta phải viết là [col_1 IS NULL]. Lỗi sai này cũng giống như thời mới học C khi ta viết là [if(hoge=0)]. Mặc dù là như vậy nhưng tại sao khi so sánh bằng dấu [=] thì lại thất bại? Sự biểu hiện của dấu [=] chắc chắn là kiến thức thông thường mà chúng ta học từ tiểu học.

Tất nhiên, ở đây có lý do. Kết quả khi sử dụng dấu so sánh đối với NULL cũng trở thành giá trị UNKNOWN. Khi được chọn làm kết quả cho query thì chỉ dòng đánh giá điều kiện là mệnh đề WHERE thành true thôi. Những dòng false hay unknown không được lựa chọn. Không giới hạn đó là kí hiện so sánh nào thì tất cả những từ ngữ so sánh thông thường sẽ được thực hiện cùng một động tác. Kể cả khu vực col_1 là giá trị số hay là NULL thì kết quả vẫn trả về kết quả unknown.

--Tất cả những biểu thức dưới đây đều được trả kết quả unknown
1 = NULL
2 > NULL
3 < NULL
4 <> NULL
NULL = NULL

Vậy thì tại sao kết quả sử dụng kí hiệu so sánh thì kết quả tuyệt đối sẽ không trở thành đúng? Đó là vì NULL không là hằng số mà cũng chẳng phải là biến số. NULL chỉ là kí hiệu mang tính thị giác chỉ sự vô giá trị thôi, nó không hơn một kí hiệu. Mặt khác, những thứ có thể sử dụng dấu hiệu so sánh chỉ có thể là giá trị mà thôi.

Dành cho những người sẽ suy nghĩ rằng "NULL chả phải là giá trị thì sao. Ngu ngốc như vậy, điều ông nói tôi sẽ không bao giờ tin" thì tôi sẽ trích dẫn câu của Codd và Date đã viết.

  • "Chúng ta bắt đầu từ việc định nghĩa kí hiệu của apllicable value. Ta gọi nó là [A-mark]. Kí hiệu này đối với DBMS nó không được thừa nhận như một giá trị cũng không được thừa nhận như một biến số." (E.F.Codd "The Relational Model for Database Management: Version 2" p.173)
  • "Điều quan trọng liên quan đến NULL đó chính là một cách cẩn thận thì NULL không phải là giá trị".(C.J.Date "Khái luận Database System Bản thứ 6" p.619)

Vậy nên những cách viết như "NULL là một giá trị trong giá trị của dãy" hay "Giá trị NULL" thì đều là hiểu nhầm. Vì không phải là giá trị nên NULL cũng không thuộc miền xác định (domain). Ngược lại, tôi muốn hỏi những người cho rằng NULL là giá trị là Nếu NULL là giá trị thì cuối cùng nó mang hình dạng như thế nào?. Nếu là giá trị được chấp nhận trong cơ sở dữ liệu quan hệ thì chắc chắn giá trị nào cũng có hình dạng hoặc là số hoặc là chữ. Vậy thì nếu NULL là giá trị thì nó chắc chắn phải mang một hình dạng nào đó.

Lý do hiểu nhầm NULL là giá trị có thể có 2 lý do sau. Trong ngôn ngữ lập trình C thì vì NULL được định nghĩa nhưu một hằng số (trong nhiều xử lý thì có thể là hằng số 0) nên có thể mọi người bị nhầm lẫn trong điều đó. NULL trong SQL hay trong nhiều ngôn ngữ lập trình khác là một thứ hoàn toàn khác biệt.(Tham chiếu tại tài liệu tham khảo "Q&A về ngôn ngữ C sơ cấp")

Lý do thứ 2, từ [IS NULL] được cấu thành từ 2 từ, chúng ta có thể nhìn thấy [IS] như là từ so sánh và [NULL] là giá trị. Đặc biệt trong SQL thì ta lại có những từ như [IS TRUE] hay [IS FALSE], so sánh với đó mà suy nghĩa thì mang ấn tượng NULL là giá trị là chuyện không phải là vô lý. Tuy nhiên ngay cả trong quyển giải thích SQL căn bản thì điều chú ý này cũng được nhắc đến, [IS NULL] là một từ so sánh, mà chính xác hơn thì phải là [IS_NULL].

unknown và giá trị chân lý thứ 3

Như đoạn ở trên thì cuối cùng giá trị chân lý unknown đã xuất hiện. Cũng như đã nêu ra ở đầu chương, cơ sở dữ liệu liên quan thông qua việc sử dụng NULL đã mang đến giá trị chân lý thứ 3.

Tại đây cũng có một điểm các bạn nên chú ý. giá trị chân lý unknown khác với UNKNOWN là một loại NULL mà chúng ta đã biết. Đằng trước chính là một giá trị rồi từ đằng sau thì ngay cả là một biến số cũng không phải. Để dễ phân biệt thì với từ phía trước, chúng ta sẽ dùng chữ in thường là unknown để biểu thị, còn đằng sau thì chúng ta sẽ sử dụng tất cả bằng chữ in hoa để biểu thị. Để lý giải được sự khác nhau giữa hai từ đó, thì chúng ta lấy một ví dụ đơn giản là [x=x] để phân tích. Trường hợp x mang giá trị chân lý là unknown, thì mệnh đề [x=x] sẽ mang giá trị true. Còn mặt khác, x là UNKNOWN thì mệnh đề sẽ được đánh giá là unknown.

--Đây là sự so sánh của các giá trị
unknown = unknown -> true
--Trường hợp này là sự so sánh như [NULL=NULL]
UNKNOWN = UNKNOWN -> unknown

Như vậy chúng ta thử nhìn bảng chân lý của luận lý 3 giá trị.

x NOT x
t f
u u
f t

|AND|t|u|f| |--------|--------| |t|t|u|f| |u|u|u|f| |f|f|f|f|

|OR|t|u|f| |--------|--------| |t|t|t|t| |u|t|u|u| |f|t|u|f|

Phần ở giữa cột u và dòng u tạo thành dấu thập là phép toán không có ở luận lý 2 giá trị mà chỉ đặc biệt có ở luận lý 3 giá trị thôi. Tại những so sánh khác ở SQL thì chúng ta cũng có thể sử dụng phép toán của logic 3 giá trị này để tính.

Tuy nhiên, trong trường hợp NOT, nó đơn giản thì không nói, việc nhớ tất cả các trường hợp trong AND hay OR thì cũng không phải chuyện đơn giản. Tại đây, trong giá trị logic 3 giá trị thì mọi người hãy nghĩ đến thứ tự ưu tiên như tiếp theo đây.

  • Trường hợp AND: false>unknown>true
  • Trường hợp OR: true>unknown>false

Bên mạnh hơn sẽ thắng bên yếu. Ví dụ [true AND unknown] thì unknown mạnh hơn nên kết quả sẽ là unknown. Tuy nhiên trong trường hợp [true OR unknown] thì lần này true mạnh hơn nên kết quả sẽ là true. Nếu nhớ được thứ tự ưu tiên này thì những phép toán trong logic 3 giá trị này cũng dễ nhìn hơn. Đặc biệt trong trường hợp phép AND thì trường hợp nào có unknown thì kết quả nhất định sẽ không trở thành true được. (Ngược lại nếu kết quả của AND là true thì chỉ có trường hợp cả 2 điều kiện là true)

Câu chuyện về lý luận thì chỉ đến đây thôi. Tiếp theo, chúng ta sẽ cùng điều tra xem logic 3 giá trị này sẽ thể hiện sự kĩ thuật của mình như thế nào thông qua các ví dụ cụ thể của code nguồn. Đối với những người đã quen với logic 2 giá trị như chúng ta thì ban đầu có thể cảm thấy khó hiểu. Trong trường hợp như vậy hãy trở lại bảng chân lý, hãy thử tự tay mình tính toán thử xem sao.

Chương thực hành

1. Trường hợp qui luật triệt tam không được hình thành

Bây giờ, ta coi John là con người. Lúc đó, câu tiếp theo đây (từ giờ sẽ gọi là mệnh đề) đúng hay sai

John hoặc là 20 tuổi, hoặc không phải 20 tuổi.

Các bạn có nghĩ là nó đúng không? Đúng vậy, tỏng thế giới hiện thực thì đây hoàn toàn là một mệnh đề đúng. Không cần biết John là ai nhưng chắc chắn có mang một chỉ số đó chính là tuổi tác. Nếu vậy thì nhất định sẽ mang một số tuổi hoặc là 20 hoặc không phải là 20. Hay những mệnh đề như "Người ngoài trái đất hoặc tồn tại hoặc không tồn tại" đều là mệnh đề đúng. Như thế này, mệnh đề "Mệnh đề được hình thành từ mệnh đề và phủ định của mệnh đề đó là mệnh đề đúng" được gọi là Triệt tam trong logic 2 mệnh đề. Cũng giống như tên của nó, không chấp nhận giá trị ở giữa, sự đúng sai của mệnh đề rõ ràng như trắng đen, bằng ý nghĩa đó thì đây là nguyên lý rất quan trọng trong logic học cổ điển. Điểm quan trọng là sự có hay không chấp nhận nguyên lý này chính là sự khác nhau giữa logic cổ điển và logic phi cổ điển.

Vậy nếu qui luật triệt tam được chấp nhận trong SQL thì truy vấn tiếp theo đây chắc chắn sẽ chọn chọn tất cả các dòng trong bảng

--Hãy chọn sinh viên hoặc 20 tuổi hoặc không phải 20 tuổi
SELECT *
  FROM Students
 WHERE age = 20
    OR age <> 20

Nhưng trong SQL thì qui luật triệt tam không được chấp nhận. Các bạn hãy nghĩ bảng Students ở tron trạng thái sau đây.

Students
name age
Brown 22
Lary 19
John
Boggie 21

SQL này sẽ không thể chọn được đối tượng như John. Như ở phần lý thuyết đã nêu ra, trong trường hợp có NULL thì phép tính sẽ trở thành unknown. Về chi tiết thì dòng John được đánh giá bằng những cách sau.

--1. Tuổi của John là NULL (Là APPLICABLE NULL)

SELECT *
  FROM Students
 WHERE age = NULL
    OR age <> NULL;
--2. Nếu sử dụng NULL trong phép so sánh thì sẽ trở thành unknown

SELECT *
  FROM Students
 WHERE unknown
    OR unknown;
--3. Nếu là [unknown OR unknown] thì kết quả sẽ thành unknown
SELECT *
  FROM Students
 WHERE unkonwn;

Kết quả của SQL được chọn sẽ chỉ là những dòng được đánh giá là true. Để dòng của John cũng được bao hàm trong kết quả thì chính ta cần thiết phải thêm Điều kiện thứ 3 như tiếp theo đây.

--Thêm điều kiện thứ 3: "Tuổi của anh ta là 20 hoặc không phải 20 hoặc là kết quả chưa biết"
SELECT *
  FROM Students
 WHERE age = 20
    OR age <> 20
    Or age IS NULL

Cũng như thế này, có những trường hợp trên thế giới hiện thực là đúng nhưng trong SQL sẽ là không đúng thì trong SQL cũng thỉnh thoảng xảy ra. John trên thực tế có mang một chỉ số là tuổi. Nhưng đối với những người đang sử dụng bảng này như chúng ta thì chúng ta không biết anh ta bao nhiêu tuổi. Nếu nói theo một cách khác thì model quan hệ này không phải là model viết những gì hiện thực mà nó là model ghi lại trạng thái nhận thức của con người. Chính vì vậy tri thức chưa hoàn thiện mà hữu hạn của chúng ta sẽ được phản ánh lên bảng như vậy.

Đối với chúng ta thì tuổi của John có không biết đi chăng nữa nhưng tuổi của anh ta trên thế giới hiện tại thì mệnh đề "Hoặc là 20 hoặc không phải 20" thì đó là sự thật, đối với chúng ta thì một suy nghĩ tự nhiên cũng là như vậy nhưng đối trực quan đó đối với logic 3 giá trị thì không được tồn tại.

2. Trường hợp 2: NULL và hàm CASE

Đây đặc biệt là sai lầm dễ xảy ra khi cố sử dụng NULL như một điều kiện trong hàm CASE. Đầu tiên các bạn hãy nhìn hàm CASE dưới đây.

--Hàm CASE trả lại kết quả col_1 nếu là thì thì trả ○, nếu là NULL thì trả ×
CASE col_1
  WHEN 1     THEN '○'
  WHEN NULL  THEN '×'
 END

Trong hàm CASE này nhất định sẽ không trả về kết quả '×'. Lý do là WHEN thứ 2 chính là câu lược của [col_1 = NULL]. Cũng như mọi người đã biết thì đối với những phép toán như vậy thì kết quả sẽ trả lại kết quả unknown. Phương pháp đánh giá trong hàm CASE cũng giống như trường hợp câu lệnh WHERE, chỉ trường hợp trả lại kết quả true thì mới có thể thực hiện được. Đúng ra thì chúng ta cần thiết viết sử dụng hàm CASE tìm kiếm như tiếp theo đây.

CASE WHEN col_1 = 1     THEN '○'
     WHEN col_1 IS NULL  THEN '×'
END

Những lỗi sai loại này rất hay được nhìn thấy, chúng ta đã biết nguyên nhân xảy ra lỗi này đó chính là người ta hiểu lầm NULL là giá trị. Sự nghĩ nhầm NULL là giá trị thì đây là một lần mọi người phải suy nghĩ lại.

3. NOT IN và NOT EXITS không phải là đồng giá trị

Người ta viết EXITS thay cho IN rất hay được thực hiện như một thủ thuật của Performance Tuning. Đây là một sự chuyển đổi không có vấn đề gì. Nhưng vấn đề là trong trường hợp viết thay NOT IN và NOT EXITS thì sẽ không ra kết quả đồng nhất. Đầu tiên chúng nhìn bảng biểu hiện 2 lớp ở một trường như ví dụ dưới đây.

Class_A

|name|age|city| |--------|--------| |Brown|22|Tokyo| |Lary|19|Saitama| |Boggie|21|Chiba|

Class_B

|name|age|city| |--------|--------| |Saito|22|Tokyo| |Tajiri|23|Tokyo| |Yamada| |Tokyo| |Izumi|18|Chiba| |Muta|20|Chiba| |Ishikawa|19|Kanagawa|

Mọi người hãy chú ý đến điểm tuổi của Yamada-kun trong B Class là NULL. Chúng ta thử sử dụng bảng trên để tạo một truy vấn "Học sinh lớp A không cùng tuồi với học sinh lớp B ở Tokyo". Có nghĩa kết quả mong muốn nhận lại là Lary và Boggie. Brown đồng tuổi với Saito-kun nên bị loại. Nếu viết thì chúng ta có thể viết SQL như dưới đây.

--SQL chọn học sinh lớp A không cùng tuổi với học sinh lớp B ở Tokyo
SELECT *
  FROM Class_A
 WHERE age NOT IN (SELECT age
                     FROM Class_B
                    WHERE city = "Tokyo");

Vậy SQL này có thực sự chọn ra đúng 2 người như chúng ta mong muốn không? Rất tiếc là chuyện đó không xảy ra. Kết quả là rỗng, tức là không dòng nào được chọn.

Quả đúng là nếu tuổi của Yamada-kun không là NULL (Tuổi của 2 người kia không giống nhau) thì SQL này sẽ không vấn đề gì mà trả lại kết quả như ta mong muốn. Tuy nhiên, tại đây có một sự việc không tốt xảy ra đó chính là sự xuất hiện của NULL. Có những động tác nào xảy ra, chúng ta thử tiếp tục nhìn những ví dụ mang tính bậc thang dưới đây.

--1. Chạy Subquery, lấy danh sách tuổi.
SELECT *
  FROM Class_A
 WHERE age NOT IN (22, 23, NULL);
--2. Thay đổi đồng giá trị NOT IN thành NOT và IN
SELECT *
  FROM Class_A
 WHERE NOT age IN (22, 23, NULL)
--3. Thay đồng giá trị IN bằng OR
SELECT *
  FROM Class_A
 WHERE NOT ((age = 20) OR (age = 23) OR (age = NULL));
--4. Thay đồng giá trị bằng AND
SELECT *
  FROM Class_A
 WHERE NOT (age = 22) AND NOT(age = 23) AND NOT(age = NULL);
--5. Thay đồng giá trị NOT bằng <>
SELECT *
  FROM Class_A
 WHERE (age <> 22) AND (age <> 23) AND (age <> NULL);
--6. Nếu sử dụng NULL với <> thì kết quả sẽ trở thành unknown
SELECT *
  FROM Class_A
 WHERE (age <> 22) AND (age <> 23) AND unknown;
--7. Nếu trong AND có điều kiện unknown thì kết quả sẽ không bao giờ trở thành true
SELECT *
  FROM Class_A
 WHERE false hoặc unknown;

Về toàn thể tất cả những dòng trong lớp A thì đánh giá được tiến hành dựa trên những bước rắc rối như trên. Về kết quả thì câu WHERE không đánh giá dòng nào là true cả. Tức là trường hợp tồn tại NULL trong dãy lựa chọn của bảng được sử dụng trong Sub query của NOT IN thì tất cả kết quả của SQL sẽ trở nên rỗng. Đây là hiện tượng rất nguy hiểm.

Để có kết quả đúng thì chúng ta phải dùng EXISTS để viết

--SQL đúng: để Lary và Boggie được lựa chọn.
SELECT *
  FROM Class_A
 WHERE NOT EXISTS (SELECT *
                     FROM Class_B B
                    WHERE A.age = B.age
                      AND B.city = 'Tokyo');
Kết quả
name     age      city
------   -----    -------
Lary       19     Saitama
Boggie     21     Chiba

Tại đây chúng ta thử từng bậc theo process đánh giá dòng NULL của tuổi.

--1. Thực hiện so sánh với NULL trong Sub query
SELECT *
  FROM Class_A
 WHERE NOT EXISTS (SELECT *
                     FROM Class_B B
                    WHERE A.age = NULL
                      AND B.city = 'Tokyo');
--2. Nếu để = NULL thì kết quả sẽ trở thành unknown
SELECT *
  FROM Class_A
 WHERE NOT EXISTS (SELECT *
                     FROM Class_B B
                    WHERE unknown
                      AND B.city = 'Tokyo');
--3. Nếu trong câu AND có một điều kiện unknown thì kết quả sẽ không trở thành true
SELECT *
  FROM Class_A
 WHERE NOT EXISTS (SELECT *
                     FROM Class_B B
                    WHERE false hoặc unknown);
--4. Vì sub query không trả lại kết quả nên NOT EXISTS sẽ trở về true
SELECT *
  FROM Class_A
 WHERE true;

Nói cách khác, Yamada-kun được nhận như một nhân vật không cùng tuổi với ai cả. (Tuy nhiên đây không phải là kết quả cuối cùng, kết quả so sánh của Saito-kun và Tashiri-kun được nối với nhau bằng AND). Lý do trở thành kết quả này chính là EXISTS sẽ không bao giờ trả lại kết quả unknown. EXISTS chỉ trả lại kết quả true hay false thôi. Vì vậy nên ta có thể hiểu IN và EXISTS có thể thay đổi cho nhau nhưng NOT IN và NOT EXISTS mà thay đổi cho nhau sẽ phát sinh ra trường hợp kết quả không giống nhau. Chuyện không thể dựa vào trực cảm trong lập trình là một điều kiện khá khó khăn, nhưng đối với kĩ sư DB thì rất cần thiết phải lý giải được điều này.

4. Vị từ hạn định và NULL

SQL có mang 2 vị từ chỉ hạn định là ALL và ANY. ANY có thể dùng cùng giá trị với IN nên không hay được sử dụng. Nên tại đây chúng ta sẽ chú ý vào từ hay được sử dụng hơn là ALL.

ALL mang ý nghĩa "Tất cả bằng với ~", "Tất cả lớn hơn ~ ",... Với trường hợp tiếp theo đây, chúng ta nghĩ SQL để có được kết quả "Học sinh lớp A trẻ hơn tất cả những học sinh lớp B sống ở Tokyo" bằng bảng B class đã loại trừ kết quả NULL.

Class_A

|name|age|city| |--------|--------| |Brown|22|Tokyo| |Lary|19|Saitama| |Boggie|21|Chiba|

Class_B

|name|age|city| |--------|--------| |Saito|22|Tokyo| |Tajiri|23|Tokyo| |Yamada|20|Tokyo| |Izumi|18|Chiba| |Muta|20|Chiba| |Ishikawa|19|Kanagawa|

Nếu dùng vị từ ALL thì chúng ta có được dòng dưới đây.

--Lựa chọn học sinh lớp A trẻ hơn bất cứ ai sống ở Tokyo trong lớp B
SELECT *
  FROM Class_A
 WHERE age < ALL (SELECT age
                    FROM Class_B
                   WHERE city = 'Tokyo');
Kết quả
name     age      city
------   -----    -------
Lary       19     Saitama

Trẻ hơn một Yamada 20 tuổi thì chỉ có Lary mà thôi. Cho đến đây thì không có bất cứ vấn đề gì xảy ra. Vấn đề xảy ra khi cũng như ví dụ, tuổi của Yamada là không biết. Theo như trực quan thì trẻ hơn một Saito 22 tuổi thì cả hai người là Lary và Boggie đều được chọn. Nhưng kết quả của SQL lại trở nên rỗng. Điều này là vì vị từ ALL cũng chính là lược của công thức logic của hàng loạt điều kiện được nối bằng AND. Về chi tiết kết quả được đánh giá theo những bước dưới đây.

--1. Thực hiện Sub query để có được list tuổi tác
SELECT *
  FROM Class_A
 WHERE age < ALL (22, 23, NULL);
--2. Đổi lại bằng AND
SELECT *
  FROM Class_A
 WHERE (age < 22) AND (age < 23) AND (age < NULL);
--3. Sử dụng < đối với NULL thì kết quả trả về là unknown

SELECT *
  FROM Class_A
 WHERE (age < 22) AND (age < 23) AND unknown;
--4. Trong câu lênh AND nếu có unknown thì kết quả sẽ trả về unknown hoặc false
SELECT *
  FROM Class_A
 WHERE unknown hoặc false;

Các bạn thấy thế nào?

5. Vị từ giới hạn và hàm cực trị không phải cùng giá trị

Tôi nghĩ rằng cũng không ít người sử dụng thay từ ALL bằng hàm cực trị. Chúng ta thử viết lại SQL trên sử dụng hàm cực trị.

--Chọn học sinh lớp A trẻ hơn bất cứ ai sống ở Tokyo tại lớp B
SELECT *
  FROM Class_A
 WHERE age < (SELECT MIN(age)
                FROM Class_B
            WHERE city = 'Tokyo');
Kết quả
name     age      city
------   -----    -------
Lary       19     Saitama
Boggie     21     Chiba

Kì diệu trong trường hợp này đó chính là kể cả giá trị tuổi của Yamada-kun là NULL thì kết quả vẫn hiện là Lary và Boggie như chúng ta mong muốn. Đây là vì hàm cực trị khi thực hiện tập hợp thì có tính năng sẽ loại bỏ những giá trị NULL. Bằng cách sử dụng hàm cực trị bảng [Class_B] sẽ được tiếp nhận khi như bảng không tồn tại giá trị NULL.

Đối với những người đang nghĩ "Có gì đâu, như vậy chúng ta chỉ cần dùng toàn hàm số cực trị là được chứ gì?", rất tiếc, trong thế giới logic 3 giá trị thì sự việc không thể gánh một cách đơn giản như vậy được. Nếu chúng ta viết ra ý nghĩa của ALL và hàm số cực trị thì có thể viết như sau.

  • Vị từ ALL: Anh ấy trẻ hơn bất kì học sinh nào sống ở Tokyo
  • Hàm số cực trị: Anh ấy trẻ hơn người trẻ nhất sống ở Tokyo

Trong thế giới hiện thực thì chúng ta có thể nói hai mệnh đề trên là giống nhau. Kể cả trong bảng có xuất hiện giá trị NULL thì cũng không phá hoại tính đồng trị của bảng và được cho qua. Các bạn có biết còn một trường hợp chứng minh vị từ ALL và hàm số cực trị ko đòng trị không. Đó là trường hợp khi tập hợp nhập vào là tập hợp rỗng. Ví dụ, chúng ta nghĩ bảng B sẽ là trường hợp như sau.

|name|age|city| |--------|--------| |Izumi|18|Chiba| |Muta|20|Chiba| |Ishikawa|19|Kanagawa|

Cũng như những gì mọi người đã nhìn thấy, ở bảng B không xuất hiện một học sinh nào sinh sống ở Tokyo. Như vậy trong trường hợp này thì câu lệnh ALL sẽ trả lại giá trị là toàn bộ những học sinh có trong Class A. Mặt khác, nếu là hàm số cực trị thì không một dòng nào được lựa chọn. Cái này có nghĩa là tập hợp rỗng, hay chính là bảng B sẽ trả lại kết quả NULL vào hàm cực trị. Vậy nên đánh giá của SQL sử dụng hàm số cực trị sẽ có những bước như sau.

--1. Hàm số cực trị trả lại giá trị NULL
SELECT *
  FROM Class_A
 WHERE age < NULL;
--2. Nếu sử dụng dấu < cho NULL thì sẽ trả lại kết quả unknown
SELECT *
  FROM Class_A
 WHERE unknown

Trong trường hợp ngay từ đầu đã không tồn tại đối tượng so sánh thì tùy theo từng nghiệp vụ muốn trả lại kết quả hiện tất cả các dòng hoặc không hiện dòng nào. Nếu trong trường hợp cần trả lại kết quả là tất cả các dòng thì chúng ta sử dụng ALL hay chúng ta phải sử dụng hàm số COALESCE để chuyển kết quả NULL do hàm số cực trị trả về thành giá trị thích hợp.

6. Những hàm khác và NULL

Thực ra, trong trường hợp bảng là rỗng mà trả về là NULL thì không chỉ có hàm số cực không trị là có điều đó. Ngoài hàm COUNT thì những hàm khác cũng vậy. Vậy nên SQL dưới đây mặc dù là 1 hàm bình thường nhưng lại có thể cho ta xem những động tác đặc biệt.

--Chọn học sinh lớp A trẻ hơn bất cứ ai sống ở Tokyo tại lớp B
SELECT *
  FROM Class_A
 WHERE age < (SELECT AVG(age)
                FROM Class_B
            WHERE city = 'Tokyo');

Trong trường hợp không có học sinh nào ở Tokyo thì hàm AVG sẽ trả lại kết quả NULL. Vậy thì hàm WHERE bên ngoài sẽ luôn trả lại kết quả unknown và không một dòng nào được chọn. Trong trường hợp hàm SUM cũng vậy. Đây chính là 2 lựa chọn để chuyển NULL thành giá trị nào đó. Tại trường hợp INSERT giá trị tổng hay giá trị trung bình vảo dãy có NOT NULL, ngoài phương pháp này ra thì không có phương pháp chuyển đổi giá trị nào nữa.

Cái bẫy lòng vòng của hàm số cực trị và những hàm số khác là do nguyên nhân sinh ra từ cách làm của những hàm số đó nên chỉ thêm vào chế ước NOT NULL vào dãy giá trị của bảng cũng không triệt tiêu được tận gốc vấn đề này. Vậy nên trong khi lập trình thì chúng ta cần chú ý điều này.

Tóm tắt

Ở phần trên chúng ta đã nhìn vấn đề NULL, logic 3 giá trị và những vấn đề mang đến trong việc lập trình SQL trong cả 2 chương là chương lý luận và chương thực hành. Đối với những người lần đầu tiên biết đến logic 3 giá trị của SQL khi đọc quyển sách này thì có vẻ mọi thứ hơi hỗn loạn đúng không? Vậy cuối cùng chúng ta cùng tóm tắt lại một số điểm quan trọng trong chương này.

  1. NULL không phải là một giá trị
  2. Vì không phải giá trị nên nó không tích hợp với những dâu phép tính, so sánh
  3. Nếu vẫn cố tình ghép vào thì kết quả trả về sẽ là unknown.
  4. Những phép logic đối với unknown thì SQL sẽ có những động tác phản với trực quan.
  5. Trong những trường hợp này chúng ta dùng dạng bươc bậc thang để phân tích sẽ có hiệu quả.

Và cuối cùng nếu nói về đối sách tốt nhất khi đối diện với vấn đề mà NULL mang lại thì đó chính là thêm chế ước NOT NULL vào bảng để khử đi NULL. Nếu như vậy chúng ta có thể trở về với thế giới 2 giá trị tốt đẹp (Tất nhiên không phải là hoàn toàn). Đối với đối sách cụ thể này thì chúng ta sẽ cùng nói chuyện sau trong chương 2.

Mặt khác, đối với sự khó hiểu này nhưng vẫn có người có sở thích sâu về nó và muốn tìm hiểu thêm thì hãy tham khảo những cuốn sách dưới đây. Trong phần 2 (tr.269) chúng ta sẽ cùng suy nghĩ về ú nghĩa có luận lý 3 giá trị đối với logic học nếu có thể thì mọi người cũng hay tham khảo phần đó.

  1. **Joe Celko "プログラマのためのSQL 第2版" **

Đặc biệt là chương 6, hoặc 14.3, 15.1 là nhất định phải đọc. 2. C.J.Date "C.J.Date のデータベース実践講義" So với sách của Celko sẽ có nhiều điểm quan trọng giải thích một cách lý luận hơn. 3.戸田山和久”論理学をつくる”(名古屋大学出版会、2000) Là một cuốn sách đáng ngạc nhiên đối với môn logic nhập môn nhưng vẫn chạm đến một chút luận lý 3 giá trị. Tuy nhiên có một chút khác đối với luận lý 3 giá trị được dùng trong SQL nên trong điểm này thì chúng ta cần chú ý. Đây cũng là cuốn sách đáng đọc khi nhập môn vị từ logic. 4. 初級C言語Q&A

www.st.rim.or.jp/~phinloda/cqa/cqa3.html

Q: Tôi đã từng nhìn thấy code sử dụng 0 thay cho NULL Pointer. Tại sao 0 có thể dùng thay thế cho NULL? A: Trong trường hợp giá trị 0 hiện ra tại nơi pointer cần hiện ra, bộ dịch sẽ chọn cái đó như NULL pointer. Nếu có một biểu hiện kiểu if(p != 0) thì bộ dịch nghĩ rằng trong trường hợp bên trái là pointer thì bên phải cũng là pointer. Vậy thì 0 cũng được dịch như NULL Pointer