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
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
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
SELECT * FROM Students;SELECT היא הפקודה שמבקשת “תראה לי נתונים” (היא לא משנה שום דבר בטבלה, רק קוראת). ה-* אומר “כל העמודות”. FROM Students אומר מאיזו טבלה. התוצאה: כל השורות שנמצאות כרגע ב-Students, עם כל העמודות שלהן.
אם רוצים רק עמודות מסוימות, לא את כולן:
SELECT Name, Age FROM Students;עכשיו כל שורה בתוצאה מכילה רק Name ו-Age, בלי Id.
שלב 5: מסננים — WHERE
מה אם רוצים רק תלמידים מעל גיל 20, לא את כולם?
SELECT * FROM Students
WHERE Age > 20;WHERE מוסיף תנאי: רק שורות שעונות עליו נכללות בתוצאה. Age > 20 הוא בדיוק אותו תנאי שהיינו כותבים ב-C# בתוך if — רק שכאן ה-מסד נתונים עצמו מסנן, לא הקוד שלכם אחרי שכבר קיבל את כל הנתונים. אפשר גם = (שווה), < (קטן מ-), != (שונה מ-), ולשלב כמה תנאים עם AND/OR, בדיוק כמו &&/|| ב-C#.
שלב 6: ממיינים — ORDER BY
SELECT * FROM Students
ORDER BY Age DESC;ORDER BY Age ממיין את התוצאה לפי עמודת הגיל. DESC (descending) אומר “מהגבוה לנמוך”; ברירת המחדל בלעדיו היא ASC (ascending) — מהנמוך לגבוה.
שלב 7: מעדכנים שורה קיימת — UPDATE
UPDATE Students
SET Age = 26
WHERE Id = 1;UPDATE Students אומר “אני עומד לשנות שורות בטבלה Students”. SET Age = 26 אומר מה בדיוק לשנות. ה-WHERE Id = 1 חיוני — הוא אומר “רק בשורה שבה Id שווה 1”, לא בכל הטבלה. בלי WHERE, UPDATE משנה את כל השורות בטבלה — טעות שקל לעשות ויקרה לפרק “טעות נפוצה” למטה.
שלב 8: מוחקים שורה — DELETE
DELETE FROM Students
WHERE Id = 2;בדיוק כמו UPDATE, גם DELETE בלי WHERE מוחק את כל השורות בטבלה, לא רק אחת. WHERE Id = 2 מגביל את המחיקה לשורה אחת ספציפית.
טעות נפוצה: UPDATE או DELETE בלי WHERE
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
תרגילים
תרגיל 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).
דרישות:
CREATE TABLEעם שלוש העמודות,Idכ-Primary Key עםIDENTITY- הוספת 5 משימות לפחות עם
INSERT, חלקן עםIsDone = 1וחלקן עםIsDone = 0 - שאילתת
SELECTשמחזירה רק משימות פתוחות (IsDone = 0) UPDATEשמסמן משימה ספציפית (לפיId) כמושלמתDELETEשמוחק משימה ספציפית לפיId
בדיקה: לפני ה-UPDATE, השאילתה מהשלב 3 מחזירה את המשימה שסומנה; אחרי ה-UPDATE היא כבר לא מופיעה בתוצאה (כי היא כבר לא IsDone = 0); אחרי ה-DELETE, SELECT * FROM Tasks לא כולל יותר את השורה שנמחקה.
מה בפרק הבא
בפרק הבא — עיצוב בסיס נתונים: איך בונים כמה טבלאות שמתקשרות נכון ביניהן (Primary Key ו-Foreign Key לעומק), ואיך נמנעים מכפילות נתונים שיוצרת סתירות (Normalization) — לפני שממשיכים ל-EF Core וממפים את כל זה למחלקות C#.