# עדכון ומחיקה: UPDATE ו-DELETE לפי id — מ-SQL ומ-Python<no value>

## מה בשיעור

השיעור הזה בנוי משני פרקים, באותו סדר כמו במצגת:

1. **פרק ראשון** — UPDATE לפי id: עדכון רשומה מ-SQL ומ-Python
2. **פרק שני** — DELETE לפי id: מחיקת רשומה מ-SQL ומ-Python

## פרק ראשון: UPDATE לפי id — עדכון רשומה מ-SQL ומ-Python

### מה נלמד?

**`UPDATE`** = ה-**U** של CRUD: שינוי של רשומה **קיימת**.

- התחביר `UPDATE ... SET ... WHERE ...`.
- עדכון של עמודה אחת, ושל כמה עמודות.
- **למה `WHERE` הוא חובה.**
- עדכון מ-Python, ו-**`rowcount`**.

### למה צריך את זה?

כל מערכת צריכה לעדכן: סטודנט שינה אימייל, מחיר מוצר השתנה, הזמנה עברה ל"נשלחה". ב-API של נושא Students API – שרת עם מסד נתונים, `PUT /students/7` = `UPDATE students SET ... WHERE id = 7`. ו-`UPDATE` הוא גם הפקודה שהכי קל **להרוס** איתה נתונים, אם שוכחים פרט אחד.

### הסבר מהבסיס

#### התחביר

```sql
UPDATE students
SET age = 25
WHERE id = 1;
```

- **`UPDATE students`** — איזו טבלה.
- **`SET age = 25`** — איזו עמודה מקבלת איזה ערך חדש. (כאן `=` הוא **השמה**, ובתוך `WHERE` הוא **השוואה**. אותו סימן, שני תפקידים.)
- **`WHERE id = 1`** — **איזו שורה** (או אילו שורות) מעדכנים.

**כמה עמודות** — מופרדות בפסיקים:

```sql
UPDATE students
SET name = 'Dana Cohen', age = 25, email = 'dana.cohen@mail.com'
WHERE id = 1;
```

**ערך שמחושב מהערך הקיים:**

```sql
UPDATE products SET stock = stock - 1 WHERE id = 3;
```

"המלאי החדש = המלאי הנוכחי פחות 1". (כמו `x = x - 1` ב-Python.)

#### WHERE — חובה!

```sql
UPDATE students SET age = 25;
```

**בלי `WHERE`**, התנאי חל על **כל השורות**: **כל** הסטודנטים עכשיו בני 25. אין "אתה בטוח?", ואין Undo. (אחרי commit, הנתונים הישנים אבדו.)

{{< box "warn" "כלל: לפני UPDATE או DELETE — SELECT" >}}
הרגל של מקצוענים: לפני הרצה ידנית של `UPDATE` או `DELETE`, מריצים **`SELECT` עם אותו `WHERE` בדיוק**, ובודקים אילו שורות יושפעו. רק אחר כך מחליפים את `SELECT` ב-`UPDATE`.
{{< /box >}}

{{< box "analogy" "אנלוגיה: מחיקון על טופס" >}}
`UPDATE ... WHERE id = 1` = לוקחים את **התיק מספר 1** מהארון, מוחקים את הגיל וכותבים גיל חדש. `UPDATE` בלי `WHERE` = לעבור על **כל** התיקים בארון ולשנות את הגיל בכולם, כי שכחתם להגיד באיזה תיק מדובר.
{{< /box >}}

#### `rowcount` — כמה שורות השתנו?

אחרי `UPDATE` מ-Python, ל-Cursor יש **`cursor.rowcount`** = מספר השורות שעודכנו בפועל:

| rowcount | משמעות |
|---|---|
| `1` | נמצאה השורה, והערכים בה **השתנו** ✓ |
| `0` | **אף שורה לא השתנתה**: אין סטודנט עם ה-id הזה — **או** שהערכים החדשים זהים לישנים |

**UPDATE על id שלא קיים הוא לא שגיאה ב-SQL.** הפקודה "הצליחה" ועדכנה 0 שורות.

שימו לב לשורה השנייה בטבלה: ב-MySQL, `rowcount` סופר רק שורות ש**באמת השתנו**. אם מעדכנים את הגיל של אבי ל-31, והוא כבר בן 31 — `rowcount` הוא 0, למרות שאבי קיים. לכן, כדי לדעת אם רשומה **קיימת** (בשביל ה-**404** של `PUT`), הדרך הבטוחה היא **לשלוף אותה** (`SELECT ... WHERE id = %s`) ולבדוק אם חזר `None`.

### תחביר

```sql
UPDATE table_name
SET column_a = value_a, column_b = value_b
WHERE id = 7;
```

```python
cursor = connection.cursor()
cursor.execute(
    "UPDATE students SET name = %s, age = %s WHERE id = %s",
    (new_name, new_age, student_id),
)
connection.commit()
cursor.rowcount        # 1 = changed, 0 = nothing changed
```

**שימו לב לסדר הפרמטרים:** הם הולכים לפי הסדר של ה-`%s` בשאילתה כולה: קודם ה-`SET`, ו**בסוף** ה-`id` של ה-`WHERE`.

### דוגמת קוד ראשונה

הטבלה לפני:

```text
id | name | age | email
 1 | Dana |  24 | dana@mail.com
 2 | Avi  |  31 | avi@mail.com
 3 | Noa  |  22 | noa@mail.com
```

```sql
SELECT id, name, age FROM students WHERE id = 2;
UPDATE students SET age = 32 WHERE id = 2;
SELECT id, name, age FROM students WHERE id = 2;
```

**תוצאה:**

```text
id | name | age
 2 | Avi  |  31

id | name | age
 2 | Avi  |  32
```

### פירוק הקוד שורה אחר שורה

| שאילתה | מה קרה |
|---|---|
| `SELECT ... WHERE id = 2` | **לפני**: בודקים מה יש, ואיזו שורה תושפע. |
| `UPDATE students SET age = 32 WHERE id = 2` | רק השורה עם id 2 השתנתה. רק `age`. השם והאימייל נשארו. |
| `SELECT ... WHERE id = 2` | **אחרי**: מאמתים. |

### דוגמה נוספת

**עדכון מ-Python, עם rowcount, ושורה שלא קיימת.** `update_demo.py`:

```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
try:
    cursor = connection.cursor()
    cursor.execute("DROP TABLE IF EXISTS students")
    cursor.execute(
        "CREATE TABLE students (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, "
        "age INT NOT NULL, email VARCHAR(100) NOT NULL UNIQUE)"
    )
    cursor.executemany(
        "INSERT INTO students (name, age, email) VALUES (%s, %s, %s)",
        [("Dana", 24, "dana@mail.com"), ("Avi", 31, "avi@mail.com"), ("Noa", 22, "noa@mail.com")],
    )
    connection.commit()

    cursor.execute(
        "UPDATE students SET name = %s, age = %s, email = %s WHERE id = %s",
        ("Dana Cohen", 25, "dana.cohen@mail.com", 1),
    )
    connection.commit()
    print("Updated rows for id 1:", cursor.rowcount)

    cursor.execute("UPDATE students SET age = %s WHERE id = %s", (40, 99))
    connection.commit()
    print("Updated rows for id 99:", cursor.rowcount)

    cursor.execute("UPDATE students SET age = %s WHERE id = %s", (31, 2))
    connection.commit()
    print("Updated rows for id 2, same age:", cursor.rowcount)

    cursor.execute("UPDATE students SET age = age + 1 WHERE age < %s", (30,))
    connection.commit()
    print("Birthday for everyone under 30:", cursor.rowcount, "rows")

    cursor = connection.cursor(dictionary=True)
    cursor.execute("SELECT id, name, age, email FROM students ORDER BY id")
    for row in cursor.fetchall():
        print(row)
finally:
    connection.close()
```

**פלט:**

```text
Updated rows for id 1: 1
Updated rows for id 99: 0
Updated rows for id 2, same age: 0
Birthday for everyone under 30: 2 rows
{'id': 1, 'name': 'Dana Cohen', 'age': 26, 'email': 'dana.cohen@mail.com'}
{'id': 2, 'name': 'Avi', 'age': 31, 'email': 'avi@mail.com'}
{'id': 3, 'name': 'Noa', 'age': 23, 'email': 'noa@mail.com'}
```

**פירוק:**

- **`DROP TABLE IF EXISTS`** — כל הרצה מתחילה מטבלה ריקה (רק להדגמה!).
- **`executemany(sql, list_of_tuples)`** — מריצה את אותה שאילתה לכל tuple ברשימה. נוח להכנסת כמה שורות.
- **עדכון של 3 עמודות** — 4 Placeholders: שלושה ל-`SET` ואחד ל-`WHERE`, באותו סדר ב-tuple.
- **id 99** — `rowcount = 0`. **בלי שגיאה.**
- **id 2 עם אותו גיל** — גם `rowcount = 0`, למרות שאבי קיים! שום דבר לא השתנה. לכן "לא נמצא" בודקים עם `SELECT`, לא עם `rowcount`.
- **`age = age + 1 WHERE age < 30`** — `WHERE` יכול לבחור **כמה** שורות. דנה (25, אחרי העדכון) ונועה (22) מתחת ל-30, ולכן `rowcount = 2`. אבי לא השתנה.
- **`connection.cursor(dictionary=True)`** — Cursor חדש, כדי לקבל את השורות בסוף כמילונים.

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

**הטעות 1 (הגדולה): UPDATE בלי WHERE.**

```python
cursor.execute("UPDATE students SET email = %s", (new_email,))
```

**כל** הסטודנטים מקבלים את אותו אימייל. (כאן, בזכות `UNIQUE`, המסד דווקא יסרב, כי שני סטודנטים לא יכולים לקבל אותו אימייל. אילוצים מצילים! אבל על `age` לא היה אילוץ שיעצור.) **לבדוק כל UPDATE: איפה ה-WHERE?**

**הטעות 2: סדר פרמטרים לא נכון.**

```python
cursor.execute("UPDATE students SET age = %s WHERE id = %s", (student_id, new_age))
```

עכשיו מעדכנים את **הסטודנט עם ה-id שווה לגיל**, ל**גיל שווה ל-id**. אין שגיאה, וזה Logical Error קשה מאוד למציאה. **הסדר ב-tuple = הסדר של ה-`%s` בשאילתה, משמאל לימין.**

**הטעות 3: לחשוב ש-UPDATE על id לא קיים זורק שגיאה.** הוא לא. קוד שלא בודק יחזיר "עודכן בהצלחה" על סטודנט שלא קיים. ולהפך: `rowcount == 0` לא אומר בהכרח "לא קיים" — אולי פשוט שום ערך לא השתנה. **בודקים קיום עם `SELECT`.**

**הטעות 4: לשכוח commit** (כמו ב-INSERT). העדכון נראה בתוך אותו חיבור, ונעלם אחרי הסגירה.

### סיכום

- **`UPDATE table SET col = %s, col2 = %s WHERE id = %s`** — עדכון רשומה קיימת. ב-`SET`, `=` הוא השמה.
- **בלי `WHERE`, כל השורות מתעדכנות.** הרגל: `SELECT` עם אותו `WHERE` לפני הרצה ידנית.
- ערך חדש יכול להיות מחושב: `stock = stock - 1`.
- מ-Python: פרמטרים לפי סדר ה-`%s` (SET ואחר כך WHERE), ו-`commit`.
- **`cursor.rowcount`** = כמה שורות **השתנו**. 0 = לא קיימת, **או** שום ערך לא השתנה. את ה-404 של PUT קובעים עם `SELECT` שמחזיר `None`.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

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

- [MySQL — UPDATE](https://dev.mysql.com/doc/refman/8.4/en/update.html) — עדכון לפי WHERE
- [MySQLConnection.commit()](https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlconnection-commit.html) — שמירת השינוי

<!-- docs:end -->

## פרק שני: DELETE לפי id — מחיקת רשומה מ-SQL ומ-Python

### מה נלמד?

**`DELETE`** = ה-**D** של CRUD: מחיקת רשומות.

- התחביר `DELETE FROM ... WHERE ...`.
- **DELETE בלי WHERE** — מה הוא עושה, ואיך נמנעים ממנו.
- מחיקה מ-Python, ו-**`rowcount`**.
- מה קורה ל-**id** אחרי מחיקה.

### למה צריך את זה?

משתמש מוחק חשבון, מנהל מוחק מוצר שהפסיק להימכר, סטודנט עוזב את הקורס. ב-API: `DELETE /students/7` = `DELETE FROM students WHERE id = 7`. ומחיקה היא **הפעולה היחידה ב-CRUD שאין ממנה חזרה**: אחרי commit, הנתון לא קיים יותר.

### הסבר מהבסיס

#### התחביר

```sql
DELETE FROM students WHERE id = 3;
```

- **`DELETE FROM students`** — מאיזו טבלה.
- **`WHERE id = 3`** — **אילו שורות** למחוק.
- אין רשימת עמודות: **מוחקים שורות שלמות**, לא עמודות. (כדי "לרוקן" שדה, משתמשים ב-`UPDATE ... SET field = NULL`.)

#### DELETE בלי WHERE

```sql
DELETE FROM students;
```

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

#### rowcount — שוב

| rowcount אחרי DELETE | משמעות |
|---|---|
| `1` | נמחקה רשומה ✓ |
| `0` | אין רשומה עם ה-id הזה. **לא שגיאה ב-SQL.** ב-API → 404 |

#### מה קורה ל-id?

עם `AUTO_INCREMENT`, **MySQL לא ממחזר id**. אם מחקתם את סטודנט 3, והסטודנט הבא שנוסף מקבל 4 (ולא 3). למה זה טוב? כי אם id 3 היה עובר למישהו אחר, כל מה שהצביע על "סטודנט 3" (קישור, הזמנה, לוג) היה מצביע פתאום על **אדם אחר**.

{{< box "analogy" "אנלוגיה: מספרי תעודות זהות" >}}
כשאדם נפטר, מספר תעודת הזהות שלו לא עובר לתינוק שנולד. מספר שנתפס פעם, נשאר שייך לאותה רשומה לתמיד. "חורים" במספור (1, 2, 4, 5) הם **תקינים** ואפילו רצויים.
{{< /box >}}

#### מחיקה רכה (Soft Delete) — ברמת היכרות

במערכות רבות **לא מוחקים באמת**. מוסיפים לטבלה עמודה כמו `is_active` — 1 = פעיל, 0 = "נמחק" — ו"מחיקה" היא בעצם `UPDATE`. כך אפשר לשחזר, ולשמור היסטוריה (למשל, הזמנות ישנות של לקוח שעזב נשארות תקינות).

| | מחיקה רגילה (Hard Delete) | מחיקה רכה (Soft Delete) |
|---|---|---|
| הפקודה | `DELETE` | `UPDATE ... SET is_active = 0` |
| השורה | נעלמת מהטבלה | נשארת, רק מסומנת |
| שחזור | אי אפשר | `UPDATE ... SET is_active = 1` |
| קריאה | `SELECT` רגיל | כל `SELECT` של רשימה מוסיף `WHERE is_active = 1` |

**ב-SQL:**

```sql
-- the table has: is_active INT NOT NULL DEFAULT 1
-- "delete" = mark the row, do not remove it
UPDATE students SET is_active = 0 WHERE id = 3;

-- every read shows only active students
SELECT id, name FROM students WHERE is_active = 1;
-- 1 | Dana
-- 2 | Avi

-- restore = one more UPDATE
UPDATE students SET is_active = 1 WHERE id = 3;
```

**מ-Python** — בדיוק כמו `delete_student`, רק עם `UPDATE` במקום `DELETE`:

```python
def soft_delete_student(student_id):
    connection = get_connection()
    try:
        cursor = connection.cursor()
        cursor.execute(
            "UPDATE students SET is_active = 0 "
            "WHERE id = %s", (student_id,))
        connection.commit()
        return cursor.rowcount > 0
    finally:
        connection.close()
```

`DEFAULT 1` אומר שכל סטודנט חדש נוצר פעיל, בלי שצריך לכתוב את זה ב-`INSERT`. המחיר של מחיקה רכה: **כל** שאילתה שמציגה רשימה חייבת לזכור את `WHERE is_active = 1`, אחרת סטודנטים "מחוקים" יופיעו שוב. השבוע נשתמש ב-DELETE אמיתי, אבל טוב לדעת שהאפשרות קיימת.

### תחביר

```sql
DELETE FROM table_name WHERE id = 7;
```

```python
cursor = connection.cursor()
cursor.execute("DELETE FROM students WHERE id = %s", (student_id,))
connection.commit()
cursor.rowcount        # 1 = deleted, 0 = no such row
```

### דוגמת קוד ראשונה

```sql
SELECT id, name FROM students WHERE id = 3;
DELETE FROM students WHERE id = 3;
SELECT id, name FROM students;
```

**תוצאה:**

```text
id | name
 3 | Noa

id | name
 1 | Dana
 2 | Avi
```

### פירוק הקוד שורה אחר שורה

| שאילתה | מה קרה |
|---|---|
| `SELECT ... WHERE id = 3` | **בדיקה מראש**: מה עומד להימחק? נועה. |
| `DELETE FROM students WHERE id = 3` | השורה של נועה נמחקה, כולה. |
| `SELECT id, name FROM students` | נשארו דנה ואבי. |

### דוגמה נוספת

**מחיקה מ-Python: rowcount, מחיקה כפולה, ו-id שלא ממוחזר.** `delete_demo.py`:

```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
try:
    cursor = connection.cursor()
    cursor.execute("DROP TABLE IF EXISTS students")
    cursor.execute(
        "CREATE TABLE students (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL)"
    )
    cursor.executemany("INSERT INTO students (name) VALUES (%s)", [("Dana",), ("Avi",), ("Noa",)])
    connection.commit()

    cursor.execute("DELETE FROM students WHERE id = %s", (3,))
    connection.commit()
    print("Deleted id 3:", cursor.rowcount, "row(s)")

    cursor.execute("DELETE FROM students WHERE id = %s", (3,))
    connection.commit()
    print("Deleted id 3 again:", cursor.rowcount, "row(s)")

    cursor.execute("INSERT INTO students (name) VALUES (%s)", ("Tom",))
    connection.commit()
    print("Tom got id:", cursor.lastrowid)

    cursor.execute("SELECT id, name FROM students ORDER BY id")
    print("Rows now:", cursor.fetchall())
finally:
    connection.close()
```

**פלט:**

```text
Deleted id 3: 1 row(s)
Deleted id 3 again: 0 row(s)
Tom got id: 4
Rows now: [(1, 'Dana'), (2, 'Avi'), (4, 'Tom')]
```

**פירוק:**

- `DROP TABLE IF EXISTS` — כל הרצה מתחילה מטבלה ריקה (רק להדגמה!).
- `[("Dana",), ("Avi",), ("Noa",)]` — לכל שורה tuple של **ערך אחד**, ולכן פסיק אחרי כל שם.
- **מחיקה ראשונה** ‏←‏ `rowcount = 1`.
- **מחיקה שנייה של אותו id** → `rowcount = 0`, **בלי שגיאה**. ב-API: הבקשה הראשונה מחזירה **204**, והשנייה **404**.
- **Tom קיבל 4, ולא 3.** `AUTO_INCREMENT` לא ממחזר. יש "חור" במספור, וזה תקין.

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

**הטעות 1: DELETE בלי WHERE.**

```python
cursor.execute("DELETE FROM students")
connection.commit()
```

כל הסטודנטים נמחקו. **בכל קוד שמכיל DELETE: איפה ה-WHERE, ומה הפרמטר שלו?**

**הטעות 2: WHERE שתופס יותר ממה שחשבתם.**

```sql
DELETE FROM students WHERE name = 'Dana';
```

אם יש **שתי** דנה, שתיהן נמחקות. **מוחקים לפי Primary Key** (`id`), שמובטח שהוא ייחודי. השם הוא לא מזהה.

**הטעות 3: לשכוח commit.** המחיקה "עובדת" בתוך החיבור, ונעלמת אחרי הסגירה. במקרה של מחיקה, זה דווקא "באג מציל", אבל עדיין באג.

**הטעות 4: לקרוא לרשומה אחרי שמחקתם אותה.** `get_student(id)` אחרי מחיקה מחזיר `None`. אם הקוד מניח שהוא קיים (`student["name"]`), הוא יקרוס. לכן `delete_student` תחזיר **`True`/`False`** ולא את הסטודנט שנמחק.

### סיכום

- **`DELETE FROM table WHERE id = %s`** — מוחק שורות שלמות. אחרי commit, אין חזרה.
- **בלי WHERE נמחקות כל השורות.** מוחקים לפי **Primary Key**, ובודקים עם SELECT לפני כן.
- `cursor.rowcount`: **1** = נמחק, **0** = לא היה קיים, בלי שגיאה. ב-API: 204 / 404.
- **`AUTO_INCREMENT` לא ממחזר id.** "חורים" במספור הם תקינים.
- Soft Delete (`is_deleted`) היא חלופה נפוצה במערכות אמיתיות.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

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

- [MySQL — DELETE](https://dev.mysql.com/doc/refman/8.4/en/delete.html) — מחיקה לפי WHERE
- [MySQLCursor.rowcount](https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-rowcount.html) — כמה שורות נמחקו

<!-- docs:end -->

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## תרגול

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

### תרגיל 1: UPDATE ב-SQL

כתבו פקודת SQL שמשנה את הגיל של הסטודנט ש-`id` שלו 2 ל-32. מה יקרה אם תשכחו את `WHERE`?

{{< solution >}}
```sql
UPDATE students SET age = 32 WHERE id = 2;
```

בלי `WHERE`, **כל** הסטודנטים בטבלה יקבלו גיל 32. `WHERE` הוא החלק הקריטי בפקודה — הוא קובע **אילו** שורות מתעדכנות.
{{< /solution >}}

### תרגיל 2: UPDATE מ-Python ו-rowcount

בטבלה יש סטודנטית אחת — דנה (id 1, בת 24). צרו אותה כך:

```py
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
cursor = connection.cursor()
cursor.execute("DROP TABLE IF EXISTS students")   # start clean, so ids start from 1
cursor.execute("CREATE TABLE students (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), age INT)")
cursor.execute("INSERT INTO students (name, age) VALUES ('Dana', 24)")
connection.commit()
connection.close()
```

כתבו `update_age(student_id, new_age)` שמעדכנת עם פרמטרים, ומחזירה כמה שורות עודכנו (`rowcount`).

```python
print("Updated rows for id 1:", update_age(1, 25))
print("Updated rows for id 99:", update_age(99, 40))
```

**פלט צפוי:**

```text
Updated rows for id 1: 1
Updated rows for id 99: 0
```

{{< solution >}}
```python
import mysql.connector


def get_connection():
    return mysql.connector.connect(
        host="localhost", user="root", password="kodkod", database="school")


def update_age(student_id, new_age):
    connection = get_connection()
    try:
        cursor = connection.cursor()
        cursor.execute(
            "UPDATE students SET age = %s WHERE id = %s", (new_age, student_id))
        connection.commit()
        return cursor.rowcount
    finally:
        connection.close()


setup = get_connection()
cursor = setup.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS students "
               "(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), age INT)")
cursor.execute("INSERT INTO students (name, age) VALUES ('Dana', 24)")
setup.commit()
setup.close()

print("Updated rows for id 1:", update_age(1, 25))
print("Updated rows for id 99:", update_age(99, 40))
```

```text
Updated rows for id 1: 1
Updated rows for id 99: 0
```

`rowcount` הוא 0 כשאין שורה כזו — **זו לא שגיאה**. הקוד שקורא מחליט מה לעשות עם זה (למשל, 404). שימו לב: ב-MySQL, `rowcount` סופר רק שורות ש**באמת השתנו** — עדכון לאותו ערך בדיוק מחזיר גם הוא 0.
{{< /solution >}}

### תרגיל 3: update_student

התחילו שוב מטבלה עם דנה בלבד (id 1, בת 24), כמו בתרגיל 2. כתבו `update_student(student_id, name, age)` שמעדכנת את שני השדות ומחזירה את הסטודנט המעודכן כמילון, או `None` אם הוא לא קיים.

```python
print(update_student(1, "Dana Cohen", 26))
print(update_student(99, "Nobody", 1))
```

**פלט צפוי:**

```text
{'id': 1, 'name': 'Dana Cohen', 'age': 26}
None
```

{{< solution >}}
```python
import mysql.connector


def get_connection():
    return mysql.connector.connect(
        host="localhost", user="root", password="kodkod", database="school")


def update_student(student_id, name, age):
    connection = get_connection()
    try:
        cursor = connection.cursor(dictionary=True)
        cursor.execute(
            "UPDATE students SET name = %s, age = %s WHERE id = %s",
            (name, age, student_id))
        connection.commit()
        cursor.execute("SELECT * FROM students WHERE id = %s", (student_id,))
        return cursor.fetchone()
    finally:
        connection.close()


setup = get_connection()
cursor = setup.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS students "
               "(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), age INT)")
cursor.execute("INSERT INTO students (name, age) VALUES ('Dana', 24)")
setup.commit()
setup.close()

print(update_student(1, "Dana Cohen", 26))
print(update_student(99, "Nobody", 1))
```

```text
{'id': 1, 'name': 'Dana Cohen', 'age': 26}
None
```

אחרי העדכון שולפים את השורה מחדש: אם אין סטודנט כזה — `fetchone()` מחזיר `None`, באותה הסכמה כמו `get_student`. (לא מסתמכים כאן על `rowcount`, כי עדכון לאותם ערכים בדיוק מחזיר 0 — למרות שהסטודנט קיים.)
{{< /solution >}}

### תרגיל 4: delete_student

בטבלה יש שלושה סטודנטים — דנה (1), אבי (2) ונועה (3). צרו אותה כך:

```py
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
cursor = connection.cursor()
cursor.execute("DROP TABLE IF EXISTS students")   # start clean, so ids start from 1
cursor.execute("CREATE TABLE students (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100))")
cursor.execute("INSERT INTO students (name) VALUES ('Dana'), ('Avi'), ('Noa')")
connection.commit()
connection.close()
```

כתבו `delete_student(student_id)` שמחזירה `True` אם נמחקה שורה ו-`False` אם לא. מחקו את אותו סטודנט (נועה, id 3) פעמיים.

```python
print(delete_student(3))
print(delete_student(3))
```

**פלט צפוי:**

```text
True
False
```

{{< solution >}}
```python
import mysql.connector


def get_connection():
    return mysql.connector.connect(
        host="localhost", user="root", password="kodkod", database="school")


def delete_student(student_id):
    connection = get_connection()
    try:
        cursor = connection.cursor()
        cursor.execute("DELETE FROM students WHERE id = %s", (student_id,))
        connection.commit()
        return cursor.rowcount > 0
    finally:
        connection.close()


setup = get_connection()
cursor = setup.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS students "
               "(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100))")
cursor.execute("INSERT INTO students (name) VALUES ('Dana'), ('Avi'), ('Noa')")
setup.commit()
setup.close()

print(delete_student(3))
print(delete_student(3))
```

```text
True
False
```

המחיקה השנייה לא נכשלת — היא פשוט לא מוצאת שורה (`rowcount` = 0).
{{< /solution >}}

### תרגיל 5: מה קורה ל-id שנמחק?

עם שלושה סטודנטים (1, 2, 3): מחקו את 3, הוסיפו את טום, והדפיסו את ה-id שלו ואת כל השורות.

**פלט צפוי:**

```text
Tom got id: 4
[(1, 'Dana'), (2, 'Avi'), (4, 'Tom')]
```

{{< solution >}}
```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
cursor = connection.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS students "
               "(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100))")
cursor.execute("INSERT INTO students (name) VALUES ('Dana'), ('Avi'), ('Noa')")
connection.commit()

cursor.execute("DELETE FROM students WHERE id = %s", (3,))
cursor.execute("INSERT INTO students (name) VALUES (%s)", ("Tom",))
connection.commit()
print("Tom got id:", cursor.lastrowid)
cursor.execute("SELECT id, name FROM students ORDER BY id")
print(cursor.fetchall())
connection.close()
```

```text
Tom got id: 4
[(1, 'Dana'), (2, 'Avi'), (4, 'Tom')]
```

עם `AUTO_INCREMENT`, **id לא חוזר לשימוש**. זה חשוב: אם משהו אחר עדיין מצביע על id 3 (למשל הזמנה ישנה), הוא לא יצביע בטעות על טום.
{{< /solution >}}

<!-- exercises:end -->
