משך: 2.5 שעות אקדמיות (113 דקות).

מטרת השיעור: הסטודנטים יבינו את ההבדל בין PARTITION BY ל-GROUP BY, ישתמשו ב-ROW_NUMBER/RANK/LAG/LEAD בביטחון, ויריצו Window Function אחת לפחות דרך ה-DbContext שלהם, לא רק ב-SQL בבידוד.

בסוף השיעור הסטודנטים צריכים: לכתוב שאילתת Window Function עם PARTITION BY+ORDER BY, להסביר את ההבדל בין ROW_NUMBER ל-RANK, להשתמש ב-LAG להשוואת שורה לקודמתה, ולהריץ שאילתה כזו דרך FromSqlRaw + endpoint אמיתי.

דגש קריטי: התחילו עם GROUP BY מוכר, והראו במפורש למה הוא לא מספיק לדרישה של “לשמור פירוט + לדעת דירוג” בו-זמנית. חדש בשיעור הזה: LINQ לא מתרגם Window Functions — זו הפעם הראשונה שסטודנטים כותבים SQL גולמי דרך FromSqlRaw, כולל .HasNoKey() למחלקת תוצאה שאינה טבלה.


מהלך השיעור

חלק בשיעורמשךהערות ודגשים
פתיחה וחיבור5 דקותשאלו: “איך תדרגו תלמידים בתוך כל כיתה, בלי לאבד את הפרטים שלהם?”
הבעיה: GROUP BY מאבד פירוט10 דקותהראו GROUP BY שנותן רק סיכום, לא דירוג פר-תלמיד
הרעיון: PARTITION BY + ROW_NUMBER20 דקותדירוג בתוך כיתה, השוו ל-GROUP BY
מריצים דרך FromSqlRaw28 דקותמחלקת תוצאה + .HasNoKey() + endpoint — על Grades/StudentId האמיתיים שלנו
RANK מול ROW_NUMBER15 דקותתיקו — הדגימו את ההבדל בפועל
LAG/LEAD15 דקותהשוואה לחודש קודם, בלי self-join
תרגול מודרך20 דקותתרגיל 1+2

דגשים להעברת השיעור

  • ההבחנה PARTITION BY מול GROUP BY היא לב השיעור — הריצו שתי השאילתות זו לצד זו על אותם נתונים כדי שהסטודנטים יראו בעיניים את ההבדל בפלט (מספר שורות שונה!).
  • FromSqlRaw הוא הפעם הראשונה שסטודנטים “בורחים” מ-LINQ — הדגישו שזה לא “חזרה אחורה” לקוד גרוע, אלא פתח-מילוט מכוון למקרים שה-Framework לא תומך בהם ישירות. .HasNoKey() שווה הסבר נפרד: בלעדיו EF Core מצפה ל-Primary Key ונכשל.
  • דוגמת ה-RANK עם תיקו (1, 1, 3) היא מלכודת קלאסית — הדגישו אותה ותנו לסטודנטים לחזות מה יקרה לפני שאתם מריצים.
  • LAG/LEAD שווה להשוות בקצרה ל-self-join שהיה נדרש בלעדיהם — לא צריך להראות את ה-self-join המלא, רק לתאר בקצרה כמה זה היה מסובך יותר.

מושגים

Window Function — מחשבת ערך יחסית לחלון שורות, שומרת על פירוט. PARTITION BY — מחלק לקבוצות בלי לאחד שורות. ROW_NUMBER/RANK — מספור/דירוג בתוך החלון. LAG/LEAD — גישה לשורה קודמת/הבאה. FromSqlRaw — מריץ SQL גולמי דרך DbContext, למה ש-LINQ לא מתרגם. Keyless Entity (.HasNoKey()) — מחלקת תוצאה שלא מגיעה מטבלה אמיתית, לכן אין לה Primary Key.


תרגיל חשיבה

שאלת פתיחה: “אתם רוצים דוח שמראה לכל עובד גם את המשכורת שלו וגם את הדירוג שלו בתוך המחלקה — למה GROUP BY Department לבד לא יכול לתת לכם את זה?”

מהלך: הובילו לתובנה: GROUP BY מייצר שורה אחת לכל מחלקה (סיכום), לא שורה לכל עובד. כדי לראות גם את הפרט (כל עובד) וגם את ההקשר הקבוצתי (הדירוג בתוך המחלקה) באותה שורה, צריך PARTITION BY — זה בדיוק מה ש-Window Functions נועדו לפתור.

מה המרצה מראה בסוף: הריצו SELECT Department, AVG(Salary) FROM Employees GROUP BY Department (סיכום בלבד, שורה למחלקה) לצד SELECT Name, Department, Salary, RANK() OVER (PARTITION BY Department ORDER BY Salary DESC) FROM Employees (כל עובד עם דירוגו) — ההבדל במספר השורות ובמידע שמוצג הוא כל הלקח.