GROUP BY מקבץ שורות לקבוצות
פונקציות צבירה כמו COUNT, SUM ו-AVG מצמצמות הרבה שורות למספר אחד. GROUP BY מאפשר לעשות את זה לכל קטגוריה: מספר אחד לכל לקוח, לכל חודש, לכל סטטוס. כל ערך ייחודי (או צירוף ערכים) הופך לשורה אחת בתוצאה.
שלושה לקוחות, שלוש שורות בפלט. שש השורות המקוריות נעלמו: הן כווצו לקבוצות לפי לקוח, ו-COUNT(*) ו-SUM(amount) חושבו בתוך כל אחת.
המודל המנטלי: GROUP BY customer אומר "התייחסו לכל השורות עם אותו לקוח כקבוצה אחת". פונקציות הצבירה פועלות אז על כל קבוצה בנפרד.
מה אפשר לשים ברשימת ה-SELECT
כאן אנשים נתקלים בקשיים. כשמשתמשים ב-GROUP BY, כל עמודה ברשימת ה-SELECT חייבת להופיע בפסוקית GROUP BY או להיות בתוך פונקציית צבירה. אחרת הערך דו-משמעי: מאיזו שורה בקבוצה הוא אמור להגיע?
אם הייתם כותבים SELECT region, rep, SUM(amount) עם GROUP BY region, SQLite הייתה מריצה את זה בשמחה (היא מתירנית במקום שבו מסדי נתונים אחרים דוחים את זה), אבל rep הייתה נבחרת שרירותית מתוך הקבוצה. הייתם מקבלים שם של נציג אחד לכל אזור בלי שום הבטחה איזה. אל תסמכו על זה: קבצו לפי כל עמודה שאינה עוברת צבירה ושאתם מציגים.
HAVING מסנן קבוצות אחרי הצבירה
WHERE מסנן שורות לפני הקיבוץ. HAVING מסנן קבוצות אחרי הקיבוץ. זה כל ההבדל, וזו הסיבה שאי אפשר לשים COUNT(*) > 1 בפסוקית WHERE: בזמן ש-WHERE רץ, הספירה עוד לא קיימת.
Cleo ביצעה רק הזמנה אחת, ולכן הקבוצה שלה מסוננת החוצה. Ada ו-Boris נשארים. התנאי רץ מול הערך המצטבר של כל קבוצה, לא מול שורות בודדות.
אפשר להפנות לכינויי עמודות מרשימת ה-SELECT ישירות ב-HAVING, ו-SQLite מאפשרת את זה:
זה לרוב קריא יותר מחזרה על SUM(amount) בפסוקית HAVING.
WHERE מול HAVING: השתמשו בשניהם יחד
שתי הפסוקיות לא באות אחת במקום השנייה. WHERE מצמצם אילו שורות משתתפות בקיבוץ; HAVING מצמצם אילו קבוצות מגיעות לפלט. רוב השאילתות האמיתיות משתמשות בשתיהן.
קראו אותה מלמעלה למטה לפי סדר הביצוע:
WHERE status = 'paid': משמיטים לגמרי שורות שקיבלו החזר.GROUP BY customer: מקבצים את מה שנשאר לפי לקוח.SUM(amount)רץ לכל קבוצה.HAVING SUM(amount) > 75: משאירים רק קבוצות שעוברות את הסף.
Boris (80 + 20 = 100) ו-Cleo (200) שורדים. ההזמנה המשולמת היחידה של Ada הייתה 50, וזה לא עומד בסף.
כמה תנאים וכמה עמודות קיבוץ
HAVING מקבל את אותם אופרטורים בוליאניים כמו WHERE, AND, OR, NOT, ואפשר לקבץ לפי יותר מעמודה אחת כדי לקבל תת-קבוצות:
כל זוג (region, quarter) הוא קבוצה נפרדת. פסוקית ה-HAVING דורשת גם סכום מעל 100 וגם לפחות שתי עסקאות. רק ('North', 'Q1') ו-('South', 'Q2') עומדים בתנאים.
תבנית מעשית: מציאת כפילויות
שאילתת GROUP BY ... HAVING COUNT(*) > 1 היא הדרך המקובלת למצוא ערכים כפולים בעמודה:
שתי כפילויות צצות. מכאן בדרך כלל מחליטים אם למזג חשבונות, להוסיף אילוץ UNIQUE או לנקות את הנתונים, אבל שאילתת הגילוי נראית אותו דבר בכל פעם.
HAVING בלי GROUP BY
זה לא שגרתי אבל חוקי. בלי GROUP BY, כל קבוצת התוצאות נחשבת לקבוצה אחת, ו-HAVING מסנן אותה כשלם: מקבלים את כל הערכים המצטברים או כלום:
שורת התוצאה היחידה מופיעה כי הסכום הוא 160. שנו את הסף ל-> 200 והשאילתה לא תחזיר שום שורה. בפועל כמעט תמיד תצמידו HAVING ל-GROUP BY, אבל טוב לדעת שהשפה לא דורשת את זה.
סיכום מהיר
GROUP BYמכווץ שורות לקבוצות לפי מפתח; פונקציות הצבירה רצות בתוך כל קבוצה.- כל עמודה שאינה עוברת צבירה ב-
SELECTצריכה להופיע ב-GROUP BY. WHEREמסנן שורות לפני הקיבוץ;HAVINGמסנן קבוצות אחריו.- פונקציות צבירה כמו
COUNT(*)ו-SUM(...)שייכות ל-HAVING, אף פעם לא ל-WHERE. HAVINGמקבל תנאים מורכבים ויכול להפנות לכינויים מה-SELECT.
הצעד הבא: מפתחות זרים
צבירה על טבלה אחת שימושית, אבל רוב הסכמות האמיתיות מפזרות נתונים על כמה טבלאות: הזמנות כאן, לקוחות שם, מוצרים במקום אחר. מפתחות זרים הם הדרך לחבר את הטבלאות האלה כך שהקשרים יישארו עקביים. זה הפרק הבא.
שאלות נפוצות
מה ההבדל בין WHERE ל-HAVING ב-SQLite?
WHERE מסנן שורות בודדות לפני שהן מקובצות. HAVING מסנן קבוצות אחרי הצבירה. כלומר, WHERE amount > 100 משאיר רק שורות מעל 100, ואילו HAVING SUM(amount) > 100 משאיר רק קבוצות שהסכום שלהן מעל 100. פונקציות צבירה כמו COUNT או SUM אסורות ב-WHERE, ובשביל זה בדיוק יש HAVING.
אפשר להשתמש ב-HAVING בלי GROUP BY ב-SQLite?
כן. בלי GROUP BY, SQLite מתייחסת לכל קבוצת התוצאות כקבוצה אחת, ו-HAVING מסנן את הקבוצה הזו כיחידה. השאילתה מחזירה שורה אחת או אף שורה. זה נדיר בפועל: בדרך כלל אם יש HAVING, יש גם GROUP BY לצידו.
איך מסננים קבוצות לפי COUNT ב-SQLite?
שימו את פונקציית הצבירה ב-HAVING, לא ב-WHERE. לדוגמה, SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 1 מחזירה לקוחות עם יותר מהזמנה אחת. ב-SQLite אפשר גם להפנות בתוך HAVING לכינוי של עמודה מרשימת ה-SELECT.