2011-07-08 28 views
31

Câu hỏi lý thuyết ở đây:Sử dụng IS NULL hoặc IS NOT NULL trong điều kiện tham gia - Lý thuyết câu hỏi

Tại sao chỉ định bảng.field IS NULL hoặc table.field IS NOT NULL không hoạt động trên điều kiện kết nối (trái hoặc phải tham gia ví dụ) nhưng chỉ trong điều kiện ở đâu?

Không hoạt động Ví dụ:

- nên trả lại tất cả các lô hàng có bất kỳ lợi nhuận (giá trị không null) nào được lọc ra. Tuy nhiên, điều này trả về tất cả các lô hàng bất kể nếu có bất kỳ điều gì đáp ứng được tuyên bố [r.id is null].

SELECT 
    * 
FROM 
    shipments s 
LEFT OUTER JOIN returns r 
    ON s.id = r.id 
    AND r.id is null 
WHERE 
    s.day >= CURDATE() - INTERVAL 10 DAY 

dụ làm việc:

-Đây trả đúng số lượng hàng mà là tổng lô hàng, trừ liên quan đến một lợi nhuận (giá trị không null).

SELECT 
    * 
FROM 
    shipments s 
LEFT OUTER JOIN returns r 
    ON s.id = r.id 
WHERE 
    s.day >= CURDATE() - INTERVAL 10 DAY 
    AND r.id is null 

Tại sao lại xảy ra trường hợp này? Tất cả các điều kiện lọc khác giữa hai bảng được nối làm việc tốt, nhưng đối với một số lý do, các bộ lọc IS NULL và IS NOT NULL không hoạt động trừ khi trong câu lệnh where.

Lý do cho việc này là gì?

Trả lời

69

Ví dụ với bảng A và B:

A (parent)  B (child)  
============ ============= 
id | name  pid | name 
------------ ------------- 
    1 | Alex   1 | Kate 
    2 | Bill   1 | Lia 
    3 | Cath   3 | Mary 
    4 | Dale  NULL | Pan 
    5 | Evan 

Nếu bạn muốn tìm cha mẹ và con cái họ, bạn làm một INNER JOIN: Kết quả

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent INNER JOIN child 
    ON parent.id  = child.pid 

là mỗi trận đấu của một từ bảng bên trái và child 's pid từ bảng thứ hai sẽ hiển thị dưới dạng hàng trong kết quả:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
+----+--------+------+-------+ 

Bây giờ, ở trên không cho thấy các bậc cha mẹ mà không trẻ em (vì id của họ không có một trận đấu trong id của con, vì vậy bạn sẽ làm gì? Thay vào đó bạn thực hiện một phép nối ngoài. Có ba loại tham gia bên ngoài, bên trái, bên phải và bên ngoài tham gia đầy đủ. Chúng ta cần một trái như chúng ta muốn "thêm" hàng từ bảng bên trái (mẹ):

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 

Kết quả là bên cạnh các trận đấu trước đó, tất cả các bậc cha mẹ mà không có một trận đấu (đọc: không có đứa trẻ) cũng được hiển thị:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

Trường hợp tất cả những gì NULL đến từ đâu? Vâng, MySQL (hoặc bất kỳ RDBMS nào khác mà bạn có thể sử dụng) sẽ không biết phải đặt gì ở đây vì những bậc cha mẹ này không khớp với nhau, vì vậy không có pid cũng không phải child.name để phù hợp với những bậc cha mẹ đó. Vì vậy, nó đặt giá trị không đặc biệt này được gọi là NULL.

Điểm của tôi là NULLs này được tạo (trong tập kết quả) trong LEFT OUTER JOIN.


Vì vậy, nếu chúng ta muốn hiển thị chỉ các bậc phụ huynh KHÔNG có một đứa trẻ, chúng ta có thể thêm một WHERE child.pid IS NULL đến LEFT JOIN trên. Điều khoản WHERE được đánh giá (đã chọn) sau khi thực hiện xong JOIN.Vì vậy, nó là rõ ràng từ kết quả trên mà chỉ có ba hàng cuối cùng nơi pid là NULL sẽ được hiển thị:

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 

WHERE child.pid IS NULL 

Kết quả:

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

Bây giờ, những gì sẽ xảy ra nếu chúng ta di chuyển mà IS NULL kiểm tra từ WHERE để tham gia khoản ON?

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent LEFT JOIN child 
    ON parent.id = child.pid 
    AND child.pid IS NULL 

Trong trường hợp này, cơ sở dữ liệu tìm các hàng từ hai bảng phù hợp với các điều kiện này. Tức là, các hàng nơi parent.id = child.pidchild.pid IN NULL. Nhưng nó có thể tìm thấy không có kết quả như vậy vì không có child.pid có thể bằng một cái gì đó (1, 2, 3, 4 hoặc 5) và là NULL cùng một lúc!

Vì vậy, điều kiện:

ON parent.id = child.pid 
AND child.pid IS NULL 

tương đương với:

ON 1 = 0 

mà luôn luôn là False.

Vì vậy, tại sao nó trả về TẤT CẢ các hàng từ bảng bên trái? Bởi vì nó là một LEFT JOIN! Và trái tham gia trở lại hàng phù hợp (không có trong trường hợp này) và cũng hàng từ bảng bên trái mà không phù hợp kiểm tra (tất cả trong trường hợp này):

+----+--------+------+-------+ 
| id | parent | pid | child | 
+----+--------+------+-------+ 
| 1 | Alex | NULL | NULL | 
| 2 | Bill | NULL | NULL | 
| 3 | Cath | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
+----+--------+------+-------+ 

Tôi hy vọng lời giải thích ở trên là rõ ràng.



Sidenote (không liên quan trực tiếp đến câu hỏi của bạn): Tại sao trên trái đất không Pan hiển thị trong không ai trong số JOIN của chúng tôi? Bởi vì pid của mình là NULL và NULL trong (không phổ biến) logic của SQL là không bằng bất cứ điều gì vì vậy nó không thể phù hợp với bất kỳ id cha mẹ (mà là 1,2,3,4 và 5). Ngay cả khi có một NULL ở đó, nó vẫn sẽ không phù hợp bởi vì NULL không bằng bất cứ điều gì, thậm chí không NULL chính nó (đó là một logic rất lạ, thực sự!). Đó là lý do chúng tôi sử dụng séc đặc biệt IS NULL và không phải séc = NULL.

Vì vậy, sẽ Pan hiển thị nếu chúng tôi thực hiện RIGHT JOIN? Nó sẽ được thôi! Bởi vì một RIGHT JOIN sẽ hiển thị tất cả kết quả trận đấu đó (INNER đầu tiên THAM GIA chúng tôi đã làm) cộng với tất cả các hàng từ bảng QUYỀN mà không phù hợp (mà trong trường hợp của chúng ta là một, hàng (NULL, 'Pan').

SELECT id, parent.name AS parent 
    , pid, child.name AS child 

FROM 
     parent RIGHT JOIN child 
    ON parent.id  = child.pid 

quả :

+------+--------+------+-------+ 
| id | parent | pid | child | 
+---------------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| NULL | NULL | NULL | Pan | 
+------+--------+------+-------+ 

Thật không may, MySQL không có FULL JOIN.Bạn có thể thử nó trong RDBMS khác, và nó sẽ hiển thị:

+------+--------+------+-------+ 
| id | parent | pid | child | 
+------+--------+------+-------+ 
| 1 | Alex | 1 | Kate | 
| 1 | Alex | 1 | Lia | 
| 3 | Cath | 3 | Mary | 
| 2 | Bill | NULL | NULL | 
| 4 | Dale | NULL | NULL | 
| 5 | Evan | NULL | NULL | 
| NULL | NULL | NULL | Pan | 
+------+--------+------+-------+ 
+0

Bạn có thể giả mạo 'FULL JOIN' trong MySQL bằng cách lấy liên kết giữa' LEFT JOIN' và 'RIGHT JOIN' trong đó id là' NULL'. Điều này có những hạn chế - ví dụ, bạn không thể cập nhật hoặc xóa - và có lẽ là rắc rối nhiều hơn nó có giá trị. – Duncan

6

Phần NULL được tính SAU KHI tham gia thực tế, vì vậy đó là lý do tại sao nó cần phải ở trong mệnh đề where.

+0

Vì vậy, nếu tôi hiểu đúng, phần mềm RDMS bỏ qua tính toán rỗng trừ khi họ đang ở trong một mệnh đề WHERE nhưng thực hiện các điều kiện khác tham gia đồng thời bảng được tham gia ? – JoshG

+0

@JoshG, tôi nghĩ bạn đã hiểu đúng. Đối với RDMS để xác định nếu giá trị cột là NULL, nó sẽ kết hợp chúng lại với nhau trước tiên. Một khi nó đã tham gia chúng, nó sẽ xem xét mệnh đề WHERE và lọc các bản ghi dựa trên đó. Đây chính xác là lý do tại sao các chuyên gia SQL nói rằng bạn nên suy nghĩ về việc tham gia của mình và xem liệu có bất kỳ phần mệnh đề WHERE nào mà bạn có thể chuyển sang điều kiện JOIN hay không vì cách này sẽ xảy ra trên ít bản ghi hơn và sẽ nhanh hơn. –

2

Điều khoản WHERE được đánh giá sau khi điều kiện JOIN đã được xử lý.

+0

Cảm ơn bạn đã trả lời. Tại sao điều kiện tham gia 'IS NULL' bị bỏ qua trong khi các điều kiện khác được xử lý? – JoshG

+2

@JoshG: Bởi vì trạng thái NULL/NOT NULL không tồn tại cho đến * sau * JOIN được đánh giá. –

1

Kế hoạch thực hiện của bạn nên làm rõ điều này; JOIN được ưu tiên, sau đó các kết quả được lọc.

+0

Cảm ơn bạn đã trả lời. Vì vậy, tham gia + tất cả tham gia điều kiện lọc được tính toán, nhưng không Nulls tại thời điểm tham gia? Bất kỳ lý do tại sao nó sẽ bỏ qua một bộ lọc NULL nhưng không phải bộ lọc khác? – JoshG

2

Bạn đang thực hiện một LEFT OUTTER JOIN cho biết rằng bạn muốn mọi bộ từ bảng trên LEFT của câu lệnh bất kể nó có bản ghi khớp trong bảng RIGHT. Đây là trường hợp, kết quả của bạn đang được cắt tỉa từ bảng RIGHT nhưng bạn kết thúc với kết quả tương tự như khi bạn không bao gồm AND ở tất cả trong mệnh đề ON.

Thực hiện AND trong mệnh đề WHERE khiến nguyên nhân xảy ra sau khi LEFT JOIN diễn ra.

+0

Cảm ơn bạn đã trả lời. Điều đó có ý nghĩa, ngoại trừ logic này chỉ có vẻ ảnh hưởng đến các bộ lọc IS NULL AND IS NOT NULL, điều này thật lạ. Tôi có thể đặt bất kỳ bộ lọc nào khác trên điều kiện kết nối và nó sẽ hoạt động tốt. Bất kỳ ý tưởng tại sao đó là? – JoshG

+0

Vô hiệu được kiểm tra trong quá trình tham gia; do đó tất cả những gì bạn đang làm là kiểm tra các hàng hiện đang tồn tại trong bảng bên phải có id là null. Không phải giá trị đăng bài kết thúc lên bảng bên trái + bảng phải tuple (trong trường hợp không có kết hợp trong bảng bên phải, một bộ tuple NULL được sử dụng). Vì vậy, bằng cách làm r.id không phải là NULL trong mệnh đề ON bạn chỉ tìm kiếm nullity trong bảng r hiện có. – Suroot

3

Bộ lọc NULL thực sự không bị bỏ qua. Điều này là làm thế nào tham gia hai bảng làm việc.

Tôi sẽ cố gắng đi xuống với các bước được thực hiện bởi máy chủ cơ sở dữ liệu để làm cho nó hiểu. Ví dụ khi bạn thực hiện truy vấn mà bạn đã nói là bỏ qua điều kiện NULL. CHỌN * TỪ lô hàng s LEFT OUTER JOIN trả về r
ON s.id = r.id VÀ r.id là null ĐÂU s.day> = CURDATE() - INTERVAL 10 NGÀY

Điều đầu tiên xảy ra là tất cả các hàng từ bảng SHIPMENTS được chọn

trên máy chủ cơ sở dữ liệu bước tiếp theo sẽ bắt đầu chọn từng bản ghi từ bảng thứ hai (RETURNS).

trên bước thứ ba bản ghi từ bảng RETURNS sẽ đủ điều kiện đối với các điều kiện tham gia bạn đã cung cấp trong truy vấn trong trường hợp này là (s.id = r.id và r.id là NULL)

lưu ý rằng tiêu chuẩn này được áp dụng trên bước thứ ba chỉ quyết định xem máy chủ có nên chấp nhận hoặc từ chối bản ghi hiện tại của bảng RETURNS để nối thêm hàng đã chọn của bảng SHIPMENT hay không. Nó không thể ảnh hưởng đến việc lựa chọn bản ghi từ bảng SHIPMENT.

Và sau khi máy chủ được thực hiện với việc nối hai bảng chứa tất cả các hàng của bảng SHIPMENT và các hàng đã chọn của bảng RETURNS, nó áp dụng mệnh đề where trên kết quả trung gian. vì vậy khi bạn đặt (r.id là NULL) điều kiện trong mệnh đề where hơn tất cả các bản ghi từ kết quả trung gian với r.id = null được lọc ra.

Các vấn đề liên quan