LEFT JOIN שומר את כל מה שבצד השמאלי
INNER JOIN מחזיר רק שורות שבהן שני הצדדים תואמים. לעתים קרובות זה מה שרוצים, אבל לא תמיד. לפעמים "אין התאמה" היא בעצמה התשובה שאתם מחפשים: משתמשים שלא ביצעו הזמנה, מוצרים שמעולם לא נמכרו, פוסטים בלי תגובות. בשביל אלה צריך LEFT JOIN.
LEFT JOIN מחזיר כל שורה מהטבלה השמאלית. אם בטבלה הימנית יש שורה תואמת, מקבלים את העמודות התואמות. אם לא, עדיין מקבלים את השורה השמאלית, והעמודות של הצד הימני חוזרות כ-NULL.
ל-Cleo אין הזמנות, אבל היא עדיין מופיעה, עם NULL בעמודה total. החליפו את LEFT JOIN ב-INNER JOIN ו-Cleo נעלמת לגמרי.
המודל המנטלי
קראו את השאילתה מלמעלה למטה וחשבו על הטבלה השמאלית כעוגן. כל שורה ב-users תופיע בפלט, לא משנה מה. ה-LEFT JOIN שואל אז, עבור כל משתמש: "האם יש שורה תואמת ב-orders?"
- נמצאה התאמה → מדביקים את העמודות התואמות לשורת המשתמש.
- כמה התאמות → נוצרת שורת פלט אחת לכל התאמה (ל-Ada יש שתי הזמנות, ולכן היא מופיעה פעמיים).
- אין התאמה → נוצרת שורה אחת עם
NULLבכל עמודה מהטבלה הימנית.
המקרה האחרון הוא כל הסיבה לקיומו של LEFT JOIN. NULL כאן לא אומר "אנחנו לא יודעים": הוא אומר "אין שום דבר בצד הימני להדביק".
LEFT OUTER JOIN היא אותה פעולה. מילת המפתח OUTER אופציונלית ב-SQLite, ורוב האנשים משמיטים אותה.
מציאת שורות ללא התאמה
שימוש קלאסי ב-LEFT JOIN: למצוא שורות בטבלה השמאלית שאין להן התאמה בצד הימני. הטריק הוא לסנן לפי עמודה מהטבלה הימנית שהיא NOT NULL בנתונים עצמם, בדרך כלל המפתח הראשי שלה, ולבדוק אם היא NULL אחרי החיבור:
רק Cleo חוזרת. ה-JOIN מצרף נתוני הזמנות במקום שבו הם קיימים; ה-WHERE o.id IS NULL משאיר אז רק את השורות שבהן הצירוף נכשל. לפעמים קוראים לזה "anti-join".
ON מול WHERE: המלכודת העדינה
זה הבאג הנפוץ ביותר עם LEFT JOIN, ושווה לעצור עליו. תנאים נכנסים לפסוקית ה-ON או לפסוקית ה-WHERE, אבל ב-outer join הם מתנהגים שונה מאוד.
ONרץ בזמן החיבור. התנאים שם קובעים אילו שורות מהצד הימני נחשבות להתאמה.WHEREרץ אחרי שהחיבור הפיק את השורות שלו. הוא מסנן את התוצאה המשולבת.
ראו מה קורה כששמים תנאי על הטבלה הימנית ב-WHERE:
ל-Cleo אין הזמנה, ולכן o.status הוא NULL בשורה שלה, ו-NULL = 'shipped' לא מתקיים: היא מסוננת החוצה. הסטטוס של Boris הוא 'pending', וגם הוא נשמט. ה-LEFT JOIN התנהג בשקט כמו INNER JOIN.
הפתרון: להעביר את התנאי ל-ON, כך שהוא יסנן התאמות ולא שורות פלט:
עכשיו כל משתמש מופיע. Ada מקבלת את ההזמנה שנשלחה; Boris מקבל NULL (ההזמנה הממתינה שלו לא נחשבה להתאמה); Cleo מקבלת NULL (אין לה הזמנות בכלל). זו התשובה הנכונה כשהשאלה היא "הראו לי כל משתמש, ואת ההזמנות שנשלחו שלו אם יש".
כלל אצבע: תנאים על הטבלה השמאלית יכולים להיכנס ל-WHERE. תנאים על הטבלה הימנית שייכים כמעט תמיד ל-ON, אלא אם אתם רוצים במפורש למצוא שורות ללא התאמה עם IS NULL.
ספירה עם LEFT JOIN
משימה נפוצה: לספור שורות קשורות לכל אב, כולל אבות עם אפס. INNER JOIN היה משמיט את האפסים. LEFT JOIN יחד עם COUNT של עמודה מהצד הימני נותן את התשובה הנכונה:
שני דברים ששווה לשים לב אליהם:
COUNT(o.id)סופר שורות מהצד הימני שאינן NULL. Cleo מקבלת0, לא1, כיCOUNTמתעלם מ-NULL. אם הייתם כותביםCOUNT(*), Cleo הייתה מקבלת1(השורה קיימת, פשוט יש בה NULL). כמעט תמיד,COUNT(right.id)הוא מה שאתם רוצים.COALESCE(SUM(o.total), 0)הופך את הסכוםNULLשל Cleo ל-0. בלי זה, היא הייתה מופיעה עם הכנסותNULL, מה שנכון טכנית אבל מכוער בתצוגה.
חיבור כמה טבלאות
LEFT JOIN ניתן לשרשור. כל JOIN לוקח את התוצאה המצטברת ומחבר אליה טבלה נוספת. ברגע שהפכתם עמודה לכזו שיכולה להיות NULL בעזרת LEFT JOIN, המשיכו להשתמש ב-LEFT JOIN לכל טבלה שתלויה בה, אחרת ה-INNER JOIN הבא ישמיט בשקט שורות שרציתם לשמור.
שלושה משתמשים חוזרים. ל-Ada יש הזמנה ומשלוח. ל-Boris יש הזמנה אבל אין משלוח (carrier הוא NULL). ל-Cleo אין הזמנה, ולכן גם o.total וגם s.carrier הם NULL. שרשרת ה-LEFT JOIN שומרת על כל משתמש, לא משנה באיזה שלב בשרשרת הקשרים הנתונים נגמרים.
מתי LEFT JOIN הוא הבחירה הנכונה
השתמשו ב-LEFT JOIN כשהשאלה עוסקת ביסודה בטבלה השמאלית, והטבלה הימנית היא מידע משלים. ניסוחים כמו "כל משתמש, עם ההזמנות שלו אם יש" או "כל המוצרים והביקורת האחרונה שלהם" מתורגמים ישירות ל-LEFT JOIN.
השתמשו ב-INNER JOIN כששני הצדדים נדרשים באותה מידה: "הזמנות עם פרטי המשתמש שלהן" לא הגיוני להזמנה בלי משתמש, ולכן הסינון של INNER JOIN הוא מה שאתם רוצים.
אם אתם מוצאים את עצמכם כותבים LEFT JOIN ... WHERE right.col IS NOT NULL, רציתם INNER JOIN. אם אתם מוצאים את עצמכם כותבים LEFT JOIN ... WHERE right.col IS NULL, רציתם anti-join, ועשיתם את זה נכון.
הצעד הבא: Self-Join
לפעמים הטבלה שאליה רוצים להתחבר היא אותה טבלה שכבר שולפים ממנה: עובדים והמנהלים שלהם, קטגוריות וקטגוריות האב שלהן, זוגות משתמשים באותה עיר. זה self-join, והוא העמוד הבא.
שאלות נפוצות
מה עושה LEFT JOIN ב-SQLite?
LEFT JOIN מחזיר כל שורה מהטבלה השמאלית, ועוד שורות תואמות מהטבלה הימנית כשהן קיימות. אם אין התאמה בטבלה הימנית, עדיין מקבלים את השורה השמאלית, והעמודות של הצד הימני חוזרות כ-NULL. LEFT OUTER JOIN הוא אותו דבר: OUTER הוא אופציונלי ב-SQLite.
מה ההבדל בין LEFT JOIN ל-INNER JOIN ב-SQLite?
INNER JOIN מחזיר רק שורות שבהן תנאי החיבור מתקיים בשתי הטבלאות. LEFT JOIN מחזיר את כל השורות מהטבלה השמאלית בכל מקרה, וממלא ב-NULL את עמודות הצד הימני שלא נמצאה להן התאמה. השתמשו ב-LEFT JOIN כש'אין התאמה' היא בעצמה תשובה בעלת משמעות, כמו משתמשים עם אפס הזמנות.
למה ה-LEFT JOIN שלי ב-SQLite מתנהג כמו INNER JOIN?
כמעט תמיד בגלל פסוקית WHERE שמסננת לפי עמודה מהצד הימני בלי להתחשב ב-NULL. תנאים על הטבלה הימנית שייכים לפסוקית ה-ON, לא ל-WHERE, או שצריך לכתוב WHERE right.col IS NULL כדי למצוא שורות ללא התאמה. WHERE right.col = 'x' משמיט בשקט כל שורה ללא התאמה.