שגיאות נפוצות ב-SQL, חלק א
בשפת SQL, כמו בכל שפה אחרת, יש טעויות נפוצות שמתחילים ומנוסים כאחד עלולים ליפול בהן כאחד (אני, כותב שורות אלה, נפלתי בכולן בשלב כזה או אחר בחיי המקצועיים:)).
בפוסט זה נצלול אל כמה מהמכשולים הנפוצים יותר, ונדבר על דרכים בהן ניתן להמנע מהם.
1. הבנה עסקית לפני טכניקה
נושא נדוש, אני יודע, ועדיין הסיבה הכי גדולה לטעויות SQL. אנחנו חופרים על זה המון בקורסים שלנו.
רגע לפני שמתחילים לכתוב, כדאי להבין בדיוק מהו הצורך העסקי.
לדוגמא, טעות אחת נפוצה בהקשר הזה באה לידי ביטוי בשימוש בפקודת ה-JOIN. התבקשתם להציג את כל לקוחות החברה (טבלה אחת) והמנויים שלהם (טבלה שניה). איזה סוג של ג'וין מתאים לסיטואציה?
אני לא יודע את התשובה, אתם יודעים?
לפני שמתקדמים, יש הרבה שאלות שדורשות מענה. מה אנחנו רוצים לראות? רק את הלקוחות עם מנוי, את כל הלקוחות גם אלו ללא מנוי, את כל המנויים גם אלו ללא לקוחות משוייכים, הכל מהכל? אולי בכלל יש פה איזו הקבצה שמבקשים, אולי צריך להפעיל סינון. הרבה פעמים הדרישה העסקית מכם תהיה מעורפלת, איש ביזנס לא תמיד מבין את טכניקות ה-SQL והמשמעויות שלהן. אתם אלו שצריכים להבין בדיוק מה רוצים מכם, ולחדד את השאלה עוד לפני שבכלל יוצאים לדרך. זה ישתלם לכם בהמשך.
2. מפורש תמיד עדיף על מרומז
ישנם ב-SQL קיצורים שונים שעוזרים לכתוב שאילתות בצורה יותר מהירה ואפקטיבית, מבלי לשבור את הראש על הסינטקס. דוגמא אחת היא פקודת ה GROUP BY ALL שעושה בדיוק את זה – GROUP BY בלי לשבור את הראש על איזה עמודות שמים בפנים. דוגמא נוספת זו פקודת ה-ORDER BY שמאפשרת לכתוב מספרים במקום שמות של עמודות.
אני נגד הקיצורים הללו מכמה סיבות
- למתחילים – זהו אחד הקונספטים המבלבלים ביותר ב-SQL, הקשיחות של השפה שמכריחה לכתוב את העמודות ב-GROUP BY בצורה הנכונה, מלמדת המון וגורמת להבין את הפקודה בצורה טובה יותר.
- גם ליותר מנוסים, זה עלול לגרום לכם לפספס דברים. דברים שאולי לא היתם מפספסים אילו היתם כותבים לאט ומסודר את כל העמודות.
- זה פשוט גורם לשאילתה להיות פחות ברורה. לכם – כשתחזרו אליה בעוד כמה חודשים, ולאנשים אחרים שיצטרכו לתחזק אותה.
אין בעיה לכתוב quick and dirty כשמדובר בשאילתות שאתם עושים בשביל עצמכם. שאילתות שאתם כותבים לסביבות ייצור, בתוך סקריפטים שאחרים גם יצטרכו לעבור עליהן ולתחזק – אני הייתי מציע לכתוב by the book.
3. תקני לעומת לא תקני
לא כל פקודת SQL בסביבה מסויימת תעבוד גם בסביבה אחרת. למשל פקודת ה-IIF המצויינת, שמהווה סוג של חלופה ל-CASE לא נתמכת בכל פלטפורמה.
האם כדאי להשתמש ב-SQL לא תקני? תלוי עד כמה אותה שאילתה צריכה לשרת גם דאטהבייסים אחרים, תלוי האם ישנה אפשרות שאתם תעברו יום אחד לסביבה אחרת שלא תומכת בהכרח באותם דברים שכתבתם.
אני לא אומר לכתוב רק תקני, או רק לא תקני. אין פה נכון או לא נכון, כן כדאי לפתח מודעות לגבי העניין.
אני אישית תמיד מעדיף לעבוד עם SQL תקני ככל שמתאפשר לי, והשימוש בו הגיוני (לפעמים כתיבה תקנית מאריכה ומסבכת בצורה לא פרופורציונאלית את השאילתה)
4. סדר בתוצאות השאילתה
לפני כמעט 20 שנה קראתי פוסט מעולה שמהדהד לי בראש עד היום. את הפוסט כתב טום קייט, ה-גורו של Oracle. והשורה התחתונה שלו היא – אין סדר מובטח ב-SQL אלא אם כן השתמשתם ב-ORDER BY.
כן, זה כולל גם את המקרים שבהם הנתונים נראים כאילו הם ממוינים בצורה מסוימת. תמיד, אבל תמיד, כשאתם צריכים סדר מסוים בתוצאות השאילתה שלכם, השתמשו ב-ORDER BY. רק זה יבטיח שהנתונים יוצגו בסדר בו אתם מעוניינים.
5. קינון תתי שאילתות ב-SQL
קינון יתר של תתי שאילתות, כלומר שאילתה בתוך שאילתה בתוך שאילתה, יכול להפוך את הקוד שלנו לבלתי קריא ומסובך לתחזוקה.
במקרים רבים, Common Table Expressions יכולים להוות אלטרנטיבה מצוינת לתתי שאילתות מקוננות. CTE מאפשר לנו להגדיר שאילתה זמנית שאפשר להתייחס אליה בשאילתה הראשית. זה הופך את הקוד שלנו למובנה יותר, קל יותר לקריאה ולתחזוקה.
6. שימוש ב- IN או ב =
למה לא לכתוב את השאילתה הזו
<xmp>SELECT * FROM table WHERE column = 1
</xmp>
באופן הבא:
<xmp>SELECT * FROM table WHERE column IN (1)
</xmp>
האם ישנו הבדל בתוצאות? ואם אין, למה בכלל להשתמש ב = כש-IN עושה את אותה העבודה, וגם נותן לנו מקום ל"הרחבות עתידית".
התשובה היא קריאות והצהרת כוונות. כשאתם כותבים IN כאשר התכוונתם לעשות השוואה מול ערך בודד – כמו בומרנג, זו רק שאלה של זמן עד שמישהו אחר יקרא את מה שעשיתם ויבוא אליכם בשאלות "למה".
7. שימוש ב-LIKE במקום =
אותו הקונספט כמו הסעיף הקודם. הצהרת הכוונות שלכם שונה בכל אחת מהשאילתות הבאות. אם אתם רוצים לבצע חיפוש תבניתי – LIKE היא הפתרון, אם אתם רוצים לחפש ערך מדוייק, אופרטור ה = הוא הפתרון הנכון.
<xmp>SELECT * FROM table WHERE column LIKE 'some text'
SELECT * FROM table WHERE column = 'some text'
</xmp>
8. שימוש ב Distinct במקום Group by ולהפך
אלו שתי פעולות שנועדו למטרות שונות, אך משום שהתוצאה הסופית שלהן לעיתים קרובות זהה, אנשים נוטים להשתמש באחת במקום השניה. במורד הדרך, כשדברים הופכים להיות קצת יותר מורכבים, השימוש בכל אחת מהן עלול להחזיר תוצאה שונה. לדוגמא –
<xmp>USE Northwind
GO
SELECT DISTINCT categoryID, COUNT(categoryID) OVER ()
FROM products
categoryID CNT
1 77
2 77
3 77
4 77
5 77
6 77
7 77
8 77</xmp>
במקרה הראשון, ה-COUNT מחזיר 77, מספר השורות בטבלה, עבור כל שורה ושורה
<xmp>USE Northwind
GO
SELECT categoryID, COUNT(categoryID) OVER () AS 'CNT'
FROM products
GROUP BY categoryID
categoryID CNT
1 8
2 8
3 8
4 8
5 8
6 8
7 8
8 8</xmp>
במקרה השני ה-COUNT מחזיר 8, מספר השורות שחזרו כתוצאה מה GROUP BY עבור כל שורה ושורה.
שורה תחתונה – ב GROUP BY משתמשים כדי לבצע הקבצות, ב-DISTINCT משתמשים כדי להחזיר תוצאות ייחודיות.
9. שימוש בסוגריים במהלך פעולות OR ו-AND
פעולות ה-AND וה-OR שקולות לפעולות הכפל והחיבור, מבחינת סדר ביצועים. כאשר ישנם מספר תנאים המשלבים בתוכם גם AND וגם OR, ורוצים שחלק מסויים מהתנאי יתבצע לפני החלק השני – שימוש בסוגרים הוא בגדר חובה
למשל
<xmp>SELECT * FROM table WHERE c1 OR c2 AND c3 </xmp>
שונה מ-
<xmp>SELECT * FROM table WHERE (c1 OR c2) AND c3
</xmp>
10. לא להשתמש בכינויים משמעותיים לטבלאות
לא אחת יוצא לי לראות פעולות JOIN בהן הכינויים לטבלאות ניתנים לפי סדר האותיות a,b,c או כל צורה שרירותית אחרת, מבלי באמת לייחס איזו משמעות לטבלה ממנה הנתונים נלקחים.
שזה מחזיר אותנו לנקודה בה נגענו כבר מס' פעמים במהלך הפוסט – קריאות. כשעובדים על שאילתה שמחברת בין 2 טבלאות ומחזירה 5-6 עמודות, הכינויים לטבלה אולי לא ישפיעו בצורה משמעותית על הקריאות, אבל כשמחברים 10 טבלאות, ומחזירים החוצה 10-20 עמודות או יותר, שם כבר לקריאות יש תפקיד קריטי.
תתרגלו לעבוד נכון בטבלאות קטנות ופשוטות, וזה יבוא לכם על אוטומט בשאילתות מורכבות וגדולות יותר.