Menu

Subquery ב-SQLite: שאילתות SELECT מקוננות ב-WHERE, FROM ו-SELECT

איך מקננים SELECT אחד בתוך אחר ב-SQLite: subquery סקלרי, IN/EXISTS, טבלאות נגזרות, subquery מתואם, ומתי JOIN קריא יותר.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

Subquery הוא SELECT בתוך SELECT

Subquery (תת שאילתה) הוא בדיוק מה שהשם מרמז: פקודת SELECT שמוחבאת בתוך פקודה אחרת, עטופה בסוגריים. SQLite מריץ את השאילתה הפנימית, לוקח את התוצאה שלה ומעביר אותה לחיצונית.

נכין דוגמה קטנה שנשתמש בה שוב ושוב:

חמש הזמנות, ארבעה לקוחות, ושניים מהם לא הזמינו כלום. נשתמש בזה לאורך כל הדף.

Subquery ב-WHERE: סינון לפי רשימה

הצורה הנפוצה ביותר: שולפים רשימת מזהים בשאילתה פנימית, ואז מסננים את השאילתה החיצונית לפיה.

השאילתה הפנימית מפיקה כל customer_id שמופיע ב-orders. השאילתה החיצונית משאירה רק את הלקוחות שה-id שלהם נמצא ברשימה הזו. Cleo, Boris ו-Ada מופיעים. Dmitri (בלי הזמנות) לא.

IN (SELECT ...) הוא התבנית המרכזית ל"שורות ב-A שיש להן התאמה ב-B". בראש, קראו אותו כ"כאשר הערך של העמודה הזו הוא אחד מהערכים שהשאילתה הפנימית מחזירה".

NOT IN: שימו לב ל-NULL

השאלה ההפוכה, "אילו לקוחות לא הזמינו?", נמצאת במרחק שורה אחת:

כאן זה עובד. אבל ל-NOT IN יש קצה חד: אם תת השאילתה מחזירה אי פעם NULL, כל ה-NOT IN הופך ל-NULL (שהוא לא TRUE), ומקבלים אפס שורות. מפתיע ושקט.

ההרגל הבטוח כשמשתמשים ב-NOT IN מול עמודה שעשויה להכיל NULL:

או השתמשו ב-NOT EXISTS, שאין לו את הבעיה הזו בכלל. נגיע לזה.

Subquery סקלרי: שורה אחת, עמודה אחת

תת שאילתה סקלרית מחזירה ערך בודד, שורה אחת ועמודה אחת, ואפשר להשתמש בה בכל מקום שבו מצופה ערך.

ה-SELECT MAX(total) FROM orders הפנימי מחזיר 200. השאילתה החיצונית מסננת אז את ההזמנות שתואמות לערך הזה. שימושי בכל פעם שצריך להשוות מול ערך מצטבר.

אפשר גם להשתמש בתת שאילתה סקלרית ברשימת ה-SELECT כדי לצרף ערך מחושב לכל שורה:

כל שורה של customers מריצה את השאילתה הפנימית פעם אחת, עם customers.id במקום המתאים. זה subquery מתואם, עוד על זה בהמשך. במקרים של "מספר אחד לכל שורה" כמו זה, LEFT JOIN עם GROUP BY בדרך כלל מהיר יותר, אבל הצורה הסקלרית קריאה להפליא.

EXISTS: רק לבדוק אם משהו תואם

EXISTS הוא בן הדוד השקט של IN. הערכים לא מעניינים אותו: הוא רק בודק אם תת השאילתה מחזירה שורה כלשהי. בדרך כלל כותבים בפנים SELECT 1, כי העמודה לא משנה.

זה מוצא לקוחות שביצעו לפחות הזמנה אחת מעל 100. השאילתה הפנימית מתייחסת ל-c.id מהשאילתה החיצונית, וזה מה שהופך אותה למתואמת. SQLite מפסיק לסרוק את הטבלה הפנימית ברגע שהוא מוצא התאמה, ולכן EXISTS לעיתים קרובות מהיר יותר מ-IN בשאלות כמו "האם לשורה הזו יש שורה קשורה?".

השלילה, NOT EXISTS, היא הדרך הבטוחה מבחינת NULL לשאול "אין שורה קשורה":

Subquery ב-FROM: טבלה נגזרת

תת שאילתה יכולה לעמוד בכל מקום שבו יכולה לעמוד טבלה, כולל פסוקית FROM. השאילתה הפנימית הופכת ל"טבלה נגזרת" זמנית עם שם, שאפשר לעשות עליה join, סינון או צבירה.

השאילתה הפנימית מחשבת סכום לכל לקוח. השאילתה החיצונית מחשבת את הממוצע של הסכומים האלה לכל מדינה. צבירות דו שלביות כאלה הן בדיוק המטרה של טבלאות נגזרות: כשאי אפשר לעשות הכול ב-GROUP BY אחד.

הכינוי AS per_customer הוא חובה: לכל טבלה נגזרת צריך להיות שם.

Subquery מתואם: ריצה לכל שורה חיצונית

תת שאילתה היא מתואמת כשהיא מתייחסת לעמודה מהשאילתה החיצונית. SQLite צריך לחשב מחדש את השאילתה הפנימית עבור כל שורה חיצונית, וזה גמיש אבל יכול להיות יקר.

לכל לקוח, מצאו את ההזמנה הגדולה ביותר שלו. השאילתה הפנימית תלויה ב-customers.id, ולכן היא רצה פעם אחת לכל לקוח. לקוחות בלי הזמנות מקבלים NULL, וזה בדיוק מה שהייתם רוצים.

Subquery מתואם מתאים באופן טבעי ל"עבור כל שורה ב-A, חשב משהו מ-B". אם הטבלה קטנה או שהחיפוש משתמש באינדקס, זה בסדר. בטבלאות גדולות בלי אינדקסים מתאימים, מדדו ביצועים לפני שאתם משחררים: JOIN עם GROUP BY מהיר יותר לעיתים קרובות.

Subquery מול JOIN: במה לבחור?

שתי השאילתות האלה עונות על אותה שאלה:

שתיהן מחזירות את אותן שורות. האופטימייזר של SQLite כותב לעיתים קרובות צורה אחת כצורה השנייה באופן פנימי. בחרו לפי קריאות:

  • השתמשו ב-subquery כשצריך רק לסנן, ואתם לא רוצים שעמודות מהטבלה הפנימית ילכלכו את התוצאה.
  • השתמשו ב-JOIN כשהתוצאה צריכה עמודות משתי הטבלאות.
  • השתמשו ב-EXISTS כשאתם שואלים "האם קיימת לפחות שורה קשורה אחת?": זה ברור יותר ונמנע ממלכודות ה-NULL של IN/NOT IN.

כשיש ספק, כתבו את הגרסה שמסבירה את עצמה כשקוראים אותה בקול.

מלכודת נפוצה: subquery שמחזיר כמה שורות

תת שאילתה שמשמשת עם = חייבת להחזיר לכל היותר שורה אחת. אם היא מחזירה יותר, SQLite בוחר אחת (למעשה באקראי) ומקבלים תוצאות שגויות בשקט, בלי שגיאה.

השתמשו ב-IN כשהשאילתה הפנימית עשויה להחזיר כמה שורות:

אם אתם מצפים לשורה אחת בדיוק ורוצים לאכוף את זה, הוסיפו LIMIT 1 ו-ORDER BY כדי שהבחירה תהיה לפחות דטרמיניסטית. עדיף: כתבו את השאילתה כך שהנתונים עצמם יבטיחו שורה אחת (סננו לפי עמודה ייחודית).

הבא בתור: Common Table Expressions

תת שאילתות ב-FROM הופכות למסורבלות מהר, במיוחד כשצריך את אותה טבלה נגזרת פעמיים, או כשהקינון מגיע לשלוש רמות. Common Table Expressions (WITH ... AS (...)) מאפשרות לתת שם לתת שאילתה מראש ולהתייחס אליה בשמה בהמשך הפקודה. זה הדף הבא.

שאלות נפוצות

מה זה subquery ב-SQLite?

Subquery (תת שאילתה) היא פקודת SELECT שמקוננת בתוך פקודה אחרת, עטופה בסוגריים. SQLite מריץ את השאילתה הפנימית ומעביר את התוצאה שלה לחיצונית. תת שאילתות יכולות להופיע ב-WHERE, ב-FROM, ב-SELECT ובעוד כמה פסוקיות.

מה ההבדל בין IN ל-EXISTS ב-SQLite?

IN (SELECT ...) בודק אם ערך תואם לשורה כלשהי שתת השאילתה מחזירה. EXISTS (SELECT ...) רק בודק אם תת השאילתה מפיקה שורה כלשהי בכלל, והערכים לא מעניינים אותו. EXISTS הוא בדרך כלל הבחירה הטובה יותר כשהשאילתה הפנימית מתייחסת לשורה החיצונית (subquery מתואם).

האם להשתמש ב-subquery או ב-JOIN ב-SQLite?

השתמשו ב-JOIN כשצריך בתוצאה עמודות משתי הטבלאות. השתמשו בתת שאילתה כשצריך רק לסנן או לחשב ערך בודד. האופטימייזר של SQLite ממילא כותב לעיתים קרובות צורה אחת כצורה השנייה, אז בחרו במה שקריא יותר.

מה זה subquery מתואם ב-SQLite?

Subquery מתואם מתייחס לעמודה מהשאילתה החיצונית, ולכן צריך לחשב אותו מחדש עבור כל שורה חיצונית. הוא גמיש אבל עלול להיות איטי בטבלאות גדולות. אם subquery מתואם מתגלה כצוואר בקבוק, כתיבה מחדש שלו כ-JOIN או כ-CTE עוזרת לעיתים קרובות.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל