# SQL בסיסי — טבלאות, SELECT, INSERT, UPDATE, DELETE📖 שיעור

## מה זה?

עד עכשיו, בכל התרגילים שכתבנו, הנתונים חיו ב-`List<T>` בתוך התוכנית — ברגע שהתוכנית נסגרת, הכול נעלם. **SQL** (Structured Query Language) הוא לא כלי או תוכנה — הוא **שפה**: אוסף קבוע של מילים ותחביר, שבאמצעותם מדברים עם מסד נתונים ומבקשים ממנו לשמור, לקרוא, לעדכן או למחוק מידע, באופן שנשאר קיים גם אחרי שהתוכנית נסגרה ונפתחה מחדש. השיעור הזה מלמד את חמש הפקודות הבסיסיות של השפה הזו — לפני שנכנסים, בשיעורים הבאים, לאיך מתכננים טבלאות טוב ואיך מדברים איתן מתוך קוד C#.

## מילות מפתח שחשוב לזכור

• Database (מסד נתונים) — מקום מאורגן ששומר מידע לצמיתות, גם אחרי שהתוכנית שיצרה אותו נסגרה

• Table (טבלה) — אוסף מידע מסוג אחד, בנוי כמו גיליון אקסל: עמודות קבועות, ושורות שמצטברות

• Row (שורה) — רשומה בודדת בטבלה — למשל תלמיד אחד

• Column (עמודה) — שדה מידע קבוע שקיים לכל שורה בטבלה — למשל "שם" או "גיל"

• Query (שאילתה) — פקודת SQL בודדת ששולחים למסד הנתונים, כדי לבקש ממנו לעשות משהו

## בונים שאילתות SQL שלב אחר שלב

### שלב 1: איך נראית טבלה בכלל

לפני שכותבים שורת SQL אחת, שווה לראות בעין את מה שהולכים לבנות. טבלה בשם `Students`, עם שלוש עמודות:

| Id | Name  | Age |
| -- | ----- | --- |
| 1  | Dana  | 25  |
| 2  | Avi   | 19  |

`Id`, `Name`, `Age` הן ה-**עמודות** — קבועות, מוגדרות מראש, וקיימות לכל שורה. כל שורה (`1, Dana, 25`) היא רשומה אחת. זה בדיוק אותו רעיון כמו `record Student(int Id, string Name, int Age)` שכתבנו ב-C# — רק שכאן זה נשמר במסד נתונים, לא בזיכרון של תוכנית ש"שוכחת" הכול כשהיא נסגרת.

### שלב 2: בונים את הטבלה — CREATE TABLE

```sql
CREATE TABLE Students (
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(100),
    Age INT
);
```

- `CREATE TABLE Students` — הפקודה שיוצרת טבלה חדשה בשם `Students`.
- בתוך הסוגריים, כל שורה מגדירה **עמודה אחת**: שם העמודה, ואחריו הטיפוס שלה. `INT` הוא מספר שלם, `NVARCHAR(100)` הוא טקסט (עד 100 תווים).
- `PRIMARY KEY` על `Id` אומר "העמודה הזו מזהה כל שורה בצורה ייחודית — לעולם לא יהיו שני `Id` זהים בטבלה". `IDENTITY` אומר למסד הנתונים "תמלא את `Id` בעצמך אוטומטית, 1, 2, 3... — אל תבקשו מהמשתמש להזין אותו". (נעמיק הרבה יותר ב-Primary Key, וגם ב-Foreign Key שמקשר בין טבלאות, בשיעור הבא — כרגע מספיק לדעת שהעמודה הזו היא "מספר הזיהוי" הייחודי של כל תלמיד.)

זהו — יש טבלה ריקה, מוכנה לקבל נתונים.

### שלב 3: מכניסים שורה — INSERT INTO

```sql
INSERT INTO Students (Name, Age)
VALUES ('Dana', 25);
```

`INSERT INTO Students (Name, Age)` אומר "אני רוצה להוסיף שורה לטבלה `Students`, וממלא בה את העמודות `Name` ו-`Age`" (את `Id` לא מציינים — הוא `IDENTITY`, מסד הנתונים ממלא אותו לבד). `VALUES ('Dana', 25)` הם הערכים בפועל, בדיוק באותו סדר של העמודות שנקבעו לפניו. שימו לב: מחרוזות (`'Dana'`) עוטפים בגרשיים בודדים ב-SQL — לא גרשיים כפולים כמו ב-C#.

### שלב 4: קוראים נתונים — SELECT

```sql
SELECT * FROM Students;
```

`SELECT` היא הפקודה שמבקשת "תראה לי נתונים" (היא **לא** משנה שום דבר בטבלה, רק קוראת). ה-`*` אומר "כל העמודות". `FROM Students` אומר מאיזו טבלה. התוצאה: כל השורות שנמצאות כרגע ב-`Students`, עם כל העמודות שלהן.

אם רוצים רק עמודות מסוימות, לא את כולן:

```sql
SELECT Name, Age FROM Students;
```

עכשיו כל שורה בתוצאה מכילה רק `Name` ו-`Age`, בלי `Id`.

### שלב 5: מסננים — WHERE

מה אם רוצים רק תלמידים מעל גיל 20, לא את כולם?

```sql
SELECT * FROM Students
WHERE Age > 20;
```

`WHERE` מוסיף תנאי: רק שורות שעונות עליו נכללות בתוצאה. `Age > 20` הוא בדיוק אותו תנאי שהיינו כותבים ב-C# בתוך `if` — רק שכאן ה-**מסד נתונים** עצמו מסנן, לא הקוד שלכם אחרי שכבר קיבל את כל הנתונים. אפשר גם `=` (שווה), `<` (קטן מ-), `!=` (שונה מ-), ולשלב כמה תנאים עם `AND`/`OR`, בדיוק כמו `&&`/`||` ב-C#.

### שלב 6: ממיינים — ORDER BY

```sql
SELECT * FROM Students
ORDER BY Age DESC;
```

`ORDER BY Age` ממיין את התוצאה לפי עמודת הגיל. `DESC` (descending) אומר "מהגבוה לנמוך"; ברירת המחדל בלעדיו היא `ASC` (ascending) — מהנמוך לגבוה.

### שלב 7: מעדכנים שורה קיימת — UPDATE

```sql
UPDATE Students
SET Age = 26
WHERE Id = 1;
```

`UPDATE Students` אומר "אני עומד לשנות שורות בטבלה `Students`". `SET Age = 26` אומר מה בדיוק לשנות. ה-`WHERE Id = 1` **חיוני** — הוא אומר "רק בשורה שבה `Id` שווה 1", לא בכל הטבלה. בלי `WHERE`, `UPDATE` משנה את **כל** השורות בטבלה — טעות שקל לעשות ויקרה לפרק "טעות נפוצה" למטה.

### שלב 8: מוחקים שורה — DELETE

```sql
DELETE FROM Students
WHERE Id = 2;
```

בדיוק כמו `UPDATE`, גם `DELETE` בלי `WHERE` מוחק את **כל** השורות בטבלה, לא רק אחת. `WHERE Id = 2` מגביל את המחיקה לשורה אחת ספציפית.

### טעות נפוצה: UPDATE או DELETE בלי WHERE

```sql
UPDATE Students
SET Age = 0;
```

הקוד הזה **תקין תחבירית לגמרי** — SQL לא מתלונן ולא מתריע. הוא פשוט עושה בדיוק מה שהתבקש: "בעמודת `Age`, בכל השורות בטבלה, שים `0`". אין כאן `WHERE`, אז אין "רק שורה אחת" — התנאי חסר, לא שקוראים בטעות "כל השורות" — פשוט אין מגבלה בכלל. זו הסיבה ש-`UPDATE`/`DELETE` הן הפקודות הכי מסוכנות ב-SQL: לפני שמריצים אותן במסד נתונים אמיתי, בודקים פעמיים שיש `WHERE` מתאים (ולפעמים מריצים קודם `SELECT` עם אותו `WHERE`, כדי לראות בדיוק אילו שורות ייפגעו, לפני שבאמת מוחקים/מעדכנים).

## הסבר עיקרי

למה `SELECT`/`WHERE` חשובים כל כך מעבר לתחביר — SQL הוא **הצהרתי** (Declarative): אתם אומרים **מה** אתם רוצים ("תלמידים מעל גיל 20, ממוינים לפי גיל"), לא **איך** למצוא אותם. מסד הנתונים בעצמו מחליט את הדרך היעילה ביותר לבצע את זה. זה שונה מקוד C# רגיל, שבו הייתם צריכים לכתוב לולאה שעוברת על כל התלמידים ובודקת תנאי בעצמכם.

`INSERT`/`UPDATE`/`DELETE` מול `SELECT` — שלוש הראשונות **משנות** את הנתונים בטבלה, `SELECT` רק **קוראת** אותם ולא נוגעת בהם. ההבחנה הזו תחזור שוב ושוב ככל שמתקדמים — היא בדיוק מה שמפריד בין GET (קריאה בלבד) ל-POST/PUT/DELETE (שינוי) ב-HTTP, שכבר נלמד ביחידת ASP.NET Core.

## יתרונות

SQL הוא תקן שכמעט כל מסד נתונים רלציוני מבין (עם הבדלים קטנים בין ספקים); שאילתות הצהרתיות קריאות וקרובות לשפה טבעית ("תן לי תלמידים מעל גיל 20"); `WHERE`/`ORDER BY` מבצעים סינון ומיון בתוך מסד הנתונים עצמו, יעיל בהרבה מלמשוך את כל הנתונים ולסנן בקוד.

## חסרונות

תחביר SQL שונה במקצת בין ספקי מסדי נתונים (SQL Server, PostgreSQL, MySQL); `UPDATE`/`DELETE` בלי `WHERE` הם טעות נפוצה ומסוכנת, בלי שום אזהרה מובנית; שאילתות מורכבות (הרבה טבלאות יחד) יכולות להיות קשות לקריאה בלי תכנון טוב.

## נקודות חשובות

• `CREATE TABLE` מגדיר עמודות וטיפוסים; `PRIMARY KEY` מזהה כל שורה בייחודיות

• `SELECT` קורא נתונים (לא משנה כלום); `INSERT`/`UPDATE`/`DELETE` משנים אותם

• `WHERE` מסנן שורות; בלעדיו, `UPDATE`/`DELETE` פועלים על **כל** הטבלה

• `ORDER BY ... DESC/ASC` קובע סדר מיון של התוצאה

## טעויות נפוצות

• להריץ `UPDATE`/`DELETE` בלי `WHERE` ולפגוע בכל הטבלה בטעות (ראו הסבר מלא למעלה)

• לשכוח גרשיים בודדים סביב מחרוזת (`'Dana'`, לא `"Dana"`) — שגיאת תחביר

• לבלבל בין `=` (השוואה בתוך `WHERE`) לבין `SET Age = 26` (השמת ערך חדש) — אותו סימן, שני שימושים שונים לגמרי

## סיכום

SQL היא שפה לדיבור עם מסד נתונים רלציוני: `CREATE TABLE` בונה טבלה עם עמודות מוגדרות, `INSERT INTO` מוסיף שורה, `SELECT` קורא נתונים (עם `WHERE` לסינון ו-`ORDER BY` למיון), ו-`UPDATE`/`DELETE` משנים או מוחקים שורות קיימות — תמיד עם `WHERE` כדי להגביל לשורה הנכונה. אלה חמש הפקודות שכל שאר היחידה (עיצוב טבלאות, ואז EF Core מ-C#) בנויה עליהן.

## דוקומנטציה רשמית

[Microsoft Learn — Transact-SQL (T-SQL) reference](https://learn.microsoft.com/sql/t-sql/language-reference)

[W3Schools — SQL Tutorial](https://www.w3schools.com/sql/)

---

## תרגילים

### תרגיל 1 — טבלה ראשונה

**המשימה:** כתבו `CREATE TABLE Products` עם עמודות `Id` (מזהה ייחודי אוטומטי), `Name` (טקסט) ו-`Price` (מספר). הוסיפו 3 שורות עם `INSERT INTO`.

**בדיקה:** `SELECT * FROM Products;` מחזיר את שלוש השורות שהוספתם, עם `Id` שמולא אוטומטית.

### תרגיל 2 — סינון ומיון

**המשימה:** על הטבלה מתרגיל 1, כתבו שאילתה שמחזירה רק מוצרים עם `Price` מעל 50, ממוינים מהיקר לזול.

**בדיקה:** התוצאה מכילה רק מוצרים עם `Price > 50`, בסדר יורד לפי מחיר.

---

## פרויקט מסכם

**המשימה:** בנו טבלת `Tasks` (משימות) עם עמודות `Id`, `Title` (טקסט) ו-`IsDone` (`BIT` — 0 או 1).

**דרישות:**
1. `CREATE TABLE` עם שלוש העמודות, `Id` כ-Primary Key עם `IDENTITY`
2. הוספת 5 משימות לפחות עם `INSERT`, חלקן עם `IsDone = 1` וחלקן עם `IsDone = 0`
3. שאילתת `SELECT` שמחזירה רק משימות פתוחות (`IsDone = 0`)
4. `UPDATE` שמסמן משימה ספציפית (לפי `Id`) כמושלמת
5. `DELETE` שמוחק משימה ספציפית לפי `Id`

**בדיקה:** לפני ה-`UPDATE`, השאילתה מהשלב 3 מחזירה את המשימה שסומנה; אחרי ה-`UPDATE` היא כבר לא מופיעה בתוצאה (כי היא כבר לא `IsDone = 0`); אחרי ה-`DELETE`, `SELECT * FROM Tasks` לא כולל יותר את השורה שנמחקה.

## מה בפרק הבא

בפרק הבא — **עיצוב בסיס נתונים**: איך בונים כמה טבלאות שמתקשרות נכון ביניהן (Primary Key ו-Foreign Key לעומק), ואיך נמנעים מכפילות נתונים שיוצרת סתירות (Normalization) — לפני שממשיכים ל-EF Core וממפים את כל זה למחלקות C#.
