שגיאות נפוצות ב-SQL, חלק ב

1. מתן כינויים ארוכים עם רווחים לעמודות

בעוד שמבחינה טכנית אפשרי להעניק כינויים ארוכים ומורכבים לעמודות (כולל שימוש ברווחים), ההרגל נחשב לפחות מומלץ ממספר סיבות: 

  • כינויים ארוכים מקשים על הבנת השאילתה והופכים אותה לפחות קריאה. כינוי מסורבל עלול להקשות על מי שיקרא את השאילתה בעתיד או אפילו על המשתמש עצמו כאשר יחזור לקוד מאוחר יותר. לרוב, ניתן לבחור כינוי קצר, תמציתי ומדויק יותר המורכב ממילה אחת או שתיים לכל היותר.
  • מורכבות בשימוש עתידי: כאשר נצטרך להשתמש בכינויים ארוכים בהמשך השאילתה (למשל במיון עם ORDER BY, סינון עם WHERE, או קיבוץ עם GROUP BY), כינויים ארוכים שמכילים רווחים דורשים שימוש במרכאות או תווים מיוחדים. הדבר מגדיל את הסיבוכיות בקוד ומעלה את הסיכוי לטעויות לא מכוונות או לפספוסים בתהליך הפיתוח.

 

שורה תחתונה [1] – מומלץ להשתמש בכינויים קצרים: כדי לשמור על קוד נקי וקריא, מקובל לתת לעמודות כינויים קצרים כאשר יש צורך להשתמש במספר מילים, תוך הפרדה בקו תחתון (_). ככה השאילתה שלנו הופכת לפשוטה יותר לתחזוקה ושימוש עתידי, ומקטינה את הצורך בטיפול מיוחד בעת עבודה עם הכינויים.

שורה תחתונה [2] – קריאות היא המפתח להצלחה ב-SQL. אחד הדברים הראשונים שאנחנו מלמדים את התלמידים שלנו הוא לקחת את הזמן ולא למהר לסיים את כל התרגילים. מבחינתנו, אם בהתחלה ייקח שעה לכתוב שאילתה אחת, זה לגמרי בסדר – כל עוד מקפידים על כתיבה מסודרת וברורה. המהירות תגיע עם הזמן, אבל הבסיס הוא קריאות. שאילתה מעולה היא שאילתה שקל לתחזק, לשפר ולהעביר הלאה.

2. שימוש שגוי בכינויים לעמודות

באופן עקרוני – כאשר מעניקים כינויים (aliases) לעמודות במסגרת שאילתת SELECT, חשוב להבין שהכינויים האלה זמינים לשימוש רק בסעיף ORDER BY, ולא בסעיפים אחרים כמו WHERE או אפילו ב-SELECT עצמו (כל עוד לא משתמשים ב-CTEs, או תתי שאילתות).
הסיבה לכך נובעת מהסדר שבו השאילתה מבוצעת בפועל, אשר שונה מהסדר שבו היא נכתבת.

3. שימוש שגוי ב HAVING במקום WHERE (ולהפך)

פקודת WHERE ו-HAVING משרתות מטרות שונות בתהליך הסינון של הנתונים. פקודת WHERE נועדה לסנן שורות בודדות מהטבלה המקורית לפני שהן עוברות פעולות קיבוץ (GROUP BY), והיא מיושמת עוד בשלב שבו הנתונים הגולמיים מטופלים. לעומת זאת, פקודת HAVING משמשת לסינון קבוצות שנוצרו לאחר פעולת הקיבוץ, כאשר כבר נוצרו קבוצות של נתונים בהתבסס על ערכים משותפים.

4. שימוש ב-LIMIT במקום ב-RANK

לעיתים קרובות יוצא לי לראות בלבול בין פעולות LIMIT ו-RANK

  • הפקודה LIMIT (או TOP בסביבות מסוימות) נועדה להחזיר מספר מוגבל של שורות מתוך תוצאה, הפקודה לא מתחשבת בדירוג מסוים של ערכים אלא רק בגבול עליון \ תחתחון שנקבע מראש עבור מספר השורות שיוחזרו. 
  • לעומת זאת, הפקודה RANK נועדה לסדר שורות לפי דירוג מסוים ולהחזיר את כל השורות שעונות על קריטריון הדירוג המבוקש, ללא הגבלה על כמות השורות. שימושי במיוחד במצבים שבהם ייתכן שיותר מאשר שורה אחת תשתייך לדירוג מסוים.

 

לדוגמה, אם נדרש להציג את חמשת העובדים בעלי המשכורות הגבוהות ביותר, פקודת LIMIT תחזיר פשוט את חמש השורות הראשונות על פי סדר המשכורות, גם אם יש יותר מחמישה עובדים עם משכורות זהות הנמצאות בקטגוריה זו. לעומת זאת, פקודת RANK תחזיר את כל העובדים שמדורגים בחמשת המקומות הראשונים מבחינת משכורת, גם אם מדובר ביותר מחמישה אנשים בפועל.

5. שימוש ב-COUNT (עמודה) במקום COUNT (כוכבית)

שימוש בפונקציית COUNT על עמודות לעומת שימוש ב-COUNT על כל השורות (COUNT) היא עוד נקודה מבלבלת למדי עבור רבים. 

  • COUNT(עמודה): כאשר משתמשים ב-COUNT על עמודה מסוימת, הפונקציה סופרת רק את הערכים שאינם NULL בעמודה זו. כלומר, אם יש ערכי NULL בעמודה, הם לא ייספרו בתוצאה. כתוצאה מכך, ייתכן שהמספר המוחזר לא ישקף את סך השורות בטבלה אלא רק את הערכים שאינם ריקים בעמודה הספציפית.
  • COUNT(*): לעומת זאת, שימוש ב-COUNT(*) סופר את כל השורות בטבלה ללא קשר לתוכן שלהן או לערכי NULL. הפונקציה תספור כל שורה בטבלה, גם אם בעמודות מסוימות יש ערכים חסרים (NULL).

 

אז מה עדיף? תלוי מה השאלה העסקית. כמה מוצרים יש לנו בטבלה, או כמה מוצרים ללא ערכי NULL בעמודת שם מוצר, למשל.

6. COUNT עמודה, לעומת COUNT DISTINCT

כאשר משתמשים ב-COUNT על עמודה, כפי שכבר הוזכר, הפונקציה סופרת את כל הערכים בעמודה, כולל ערכים כפולים (אך לא את ערכי ה-NULL). עם זאת, לעיתים נרצה לספור רק את הערכים הייחודיים (Distinct) בעמודה, כלומר להתעלם מכל מופע כפול של ערכים. במקרים כאלה, יש להשתמש ב-COUNT DISTINCT.

7. לא להבין את ההבדל בין NOT IN לבין IN

ההבדל בין השימוש ב-IN לשימוש ב-NOT IN חשוב להבנה כדי להימנע מתוצאות בלתי צפויות בשאילתות.

  • IN: השימוש ב-IN שקול לפעולת OR. הוא בודק אם ערך מסוים נמצא בתוך רשימה של ערכים שהוגדרו. לדוגמה, אם נרצה להחזיר את כל המוצרים שמספרם הוא 1, 2, 3 או 4, נשתמש ב-IN, והשאילתה תחזיר את כל המוצרים שמספרם נמצא ברשימה זו.
  • NOT IN: לעומת זאת, השימוש ב-NOT IN שקול לפעולת AND, והוא בודק אם ערך אינו נמצא ברשימה מוגדרת של ערכים. לדוגמה, אם נרצה להציג את כל המוצרים שמספרם אינו 1, 2, 3 או 4, נשתמש ב-NOT IN. כאן הטעות הנפוצה נובעת מהניסוח של השאלה: המילה "או" עלולה להטעות ולגרום לחשוב שהשימוש ב-OR מתאים, בעוד שהמשמעות האמיתית היא שכל הערכים אינם חלק מהרשימה. למה זה מעניין אותנו? 

כאשר השימוש ב-NOT IN כולל תוצאה שמוחזרת משאילתה אחרת, יש להיזהר מערכי NULL. אם אחת מהתוצאות המוחזרות מהשאילתה היא NULL, אזי הפונקציה NOT IN תיכשל ולא תדע איך להתמודד עם הערך הזה, ולכן השאילתה עלולה להחזיר תוצאה ריקה או שגויה. לכן, בעת שימוש ב-NOT IN עם תוצאה משאילתה, חשוב לוודא שאין ערכי NULL ברשימה המוחזרת או להשתמש באופרטורים כמו NOT EXIST.

8. חישובי הפרשים ו-DATEDIFF

כאשר משתמשים ב-DATEDIFF לחישוב הפרשים, צריך להבין ש-DATEDIFF מחזיר את ההפרש בין שני תאריכים בהתאם ליחידת הזמן שנבחרה (כגון שנים, חודשים, ימים), מבלי להתחשב במרכיבים מדויקים יותר של התאריך, כמו היום והחודש.

DATEDIFF(YEAR, bday, current_date)

למשל, ילד שנולד ב-30/12/2023 ייחשב כבן שנה כבר בתחילת 2024. במצבים כאלו כדי לקבל תוצאה מדוייקת – כדאי להפעיל חישוב המביא בחשבון גם את הרמה הגבוהה יותר (חודש במקרה שלנו)

9. סינון שגוי ב-LEFT JOIN

כשמבצעים LEFT JOIN בין שתי טבלאות במטרה להחזיר את כל הרשומות מהטבלה השמאלית, גם אם אין להן התאמה בטבלה הימנית, חשוב להיזהר מהשמת תנאי סינון על הטבלה הימנית בתוך ה-WHERE.

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

				
					<xmp>USE Northwind
GO

SELECT C.CustomerID, C.CompanyName, O.OrderID
FROM Customers C
LEFT JOIN Orders O ON C.CustomerID = O.CustomerID
WHERE O.OrderDate = '1997-08-25';</xmp>
				
			

בשאילתה העליונה, כל הלקוחות שאין להם הזמנה בתאריך ה-25 באוגוסט 1997 לא ייכללו בתוצאה, אפילו שהשאילתה אמורה להחזיר את כל הלקוחות (כחלק משימוש ב-LEFT JOIN). הרשומות שאינן תואמות את התנאי מסוננות בגלל ה-WHERE, כך שהתוצאה תהיה זהה לשימוש ב-INNER JOIN.

				
					<xmp>SELECT C.CustomerID, C.CompanyName, O.OrderID
FROM Customers C
LEFT JOIN Orders O ON C.CustomerID = O.CustomerID 
AND O.OrderDate = '1997-08-25';</xmp>
				
			

כדי לשמור את הרשומות מהטבלה Customers, גם אם אין להן הזמנות בתאריך זה, יש להעביר את תנאי הסינון לחלק ה-ON של ה-JOIN. כעת, השאילתה תחזיר את כל הלקוחות, כולל אלו שאין להם הזמנות בתאריך זה, אשר עבורם ערך ה-OrderID יהיה NULL.

10. שימוש ב UPDATE בלי WHERE

ולסיום, מנסיון אישי, אף דאבל-אספרסו לא מעלה את הדופק על הבוקר כמו לעשות UPDATE בלי WHERE על טבלה בסביבת ייצור..

כשאתם מריצים פקודת UPDATE על טבלה מבלי להגדיר תנאי WHERE, כל השורות בטבלה מתעדכנות – ולא רק השורות הרצויות. זה עלול לגרום לשינוי גורף של הנתונים ולתוצאות לא צפויות. לכן, חשוב מאוד לוודא שתנאי ה-WHERE מגדיר בדיוק אילו שורות אמורות להתעדכן. מומלץ להתחיל עם שאילתת SELECT ולוודא שהשורות שאתם עומדים לעדכן הן אכן השורות הנכונות לפני הרצת ה-UPDATE.

המדריך לדאטה-אנליסט

בואו לבקר במאגר המידע הגדול ביותר בעברית! גלו הרצאות וסשנים מרתקים של מומחים מהתעשייה, וצללו לעולם שלם של פוסטים מעמיקים בנושאים שונים.

דברו איתנו