# מערך שיעור: Window Functions📋 מערך מרצה

משך: 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_NUMBER | 20 דקות | דירוג בתוך כיתה, השוו ל-GROUP BY |
| מריצים דרך FromSqlRaw | 28 דקות | מחלקת תוצאה + `.HasNoKey()` + endpoint — על Grades/StudentId האמיתיים שלנו |
| RANK מול ROW_NUMBER | 15 דקות | תיקו — הדגימו את ההבדל בפועל |
| LAG/LEAD | 15 דקות | השוואה לחודש קודם, בלי 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` (כל עובד עם דירוגו) — ההבדל במספר השורות ובמידע שמוצג הוא כל הלקח.
