טריגר מריץ SQL אוטומטית
טריגר הוא בלוק SQL שמור שמופעל בכל פעם שאירוע מסוים קורה בטבלה מסוימת. כותבים אותו פעם אחת. SQLite דואג ל"מתי" בשבילכם.
הצורה:
אף פעם לא כתבנו INSERT מפורש ל-price_history. הטריגר עשה את זה. כל עדכון מחיר עתידי יירשם באותה דרך, בין אם הוא מגיע מה-CLI, מסקריפט או מאפליקציה.
האנטומיה של CREATE TRIGGER
קראו את התחביר חלק אחר חלק:
CREATE TRIGGER trigger_name
{ BEFORE | AFTER | INSTEAD OF } { INSERT | UPDATE [ OF column_list ] | DELETE }
ON table_name
[ FOR EACH ROW ]
[ WHEN condition ]
BEGIN
-- one or more statements
END;
- תזמון:
BEFOREרץ לפני השינוי,AFTERרץ אחריו,INSTEAD OFמחליף אותו (רק ב-views). - אירוע: איזו פעולה מפעילה אותו.
UPDATE OF col1, col2מצמצם עדכונים לעמודות מסוימות. - טבלה: הטבלה שנמצאת במעקב.
FOR EACH ROW: SQLite תומך רק בטריגרים ברמת השורה, אז זה מובלע. אפשר לכתוב את זה לשם הבהירות, זה לא משנה כלום.WHEN: תנאי אופציונלי. גוף הטריגר רץ רק אם הוא אמת.- גוף: פקודה אחת או יותר בין
BEGINל-END. כל אחת חייבת להסתיים בנקודה פסיק.
זה כל הדקדוק. רוב הטריגרים האמיתיים הם באורך חמש עד עשר שורות.
OLD ו-NEW: השורה שמשתנה
בתוך הגוף, שתי פסאודו שורות מאפשרות לראות את הנתונים:
NEW: השורה הנכנסת. זמינה בטריגרים שלINSERTושלUPDATE.OLD: השורה הקיימת. זמינה בטריגרים שלUPDATEושלDELETE.
לטריגר של DELETE יש רק OLD. לטריגר של INSERT יש רק NEW. לטריגר של UPDATE יש את שניהם.
השורה שנמחקה כבר לא נמצאת ב-accounts, אבל הנתונים שלה נשמרו ב-deletions לפני שהיא נעלמה.
BEFORE: אימות או תיקון של השורה
טריגרים של BEFORE רצים לפני שהשינוי בשורה מגיע לדיסק. הם נוחים להעלאת שגיאה או לנרמול נתונים:
ה-INSERT השני נעצר לפני שנכתבת שורה כלשהי. RAISE(ABORT, '...') מבטל את הפקודה הנוכחית ומחזיר לתחילתה. RAISE(FAIL, ...), RAISE(ROLLBACK, ...) ו-RAISE(IGNORE) נותנים שליטה עדינה יותר במה שקורה.
לאימות נתונים טהור, העדיפו אילוצי CHECK: הם הצהרתיים והאופטימייזר מכיר אותם. פנו לטריגר BEFORE כשהכלל צריך להסתכל על טבלאות אחרות או לעשות משהו ש-CHECK לא יכול לבטא.
WHEN: טריגרים מותנים
פסוקית WHEN מסננת אילו שינויים בשורות באמת מפעילים את הגוף. היא מחושבת לכל שורה, אחרי ש-OLD ו-NEW כבר קשורים:
ההזמנה הראשונה לא עוברת את הסף. השתיים האחרות כן. בלי פסוקית ה-WHEN, כל הכנסה הייתה נכתבת ל-big_orders והייתם צריכים לסנן בצד הקריאה.
INSTEAD OF: לאפשר כתיבה ל-view
Views הם לקריאה בלבד כברירת מחדל. טריגר INSTEAD OF מיירט כתיבה ל-view ומריץ את ה-SQL שלכם במקומה, בדרך כלל על ידי תרגום שלה לכתיבות לטבלה או לטבלאות שמתחת:
האפליקציה מדברת עם ה-view כאילו הוא טבלה. הטריגר מטפל מאחורי הקלעים בפיצול ל-first_name ול-last_name.
הצגה ומחיקה של טריגרים
טריגרים נשמרים ב-sqlite_master לצד טבלאות ואינדקסים:
DROP TRIGGER IF EXISTS name; היא הצורה הבטוחה. מחיקת הטבלה שהטריגר שייך לה מוחקת את הטריגר אוטומטית: לא צריך לנקות קודם.
מלכודות שכדאי להכיר
כמה דברים נושכים אנשים בפעם הראשונה:
- טריגרים מופעלים לכל שורה, לא לכל פקודה.
UPDATEשנוגע ב-1,000 שורות מפעיל את הטריגר 1,000 פעמים. אם הגוף עצמו יקר, זה מצטבר מהר. - טריגרים רצים בתוך הטרנזקציה שעוטפת אותם. אם הפקודה החיצונית מבוטלת, גם הכתיבות של הטריגר מבוטלות. זה בדרך כלל מה שרוצים, אבל זה אומר שטריגר הוא לא פתח מילוט ל"תרשום את זה לא משנה מה".
- טריגרים רקורסיביים כבויים כברירת מחדל. טריגר שמשנה את אותה טבלה לא יפעיל את עצמו שוב, אלא אם תגדירו
PRAGMA recursive_triggers = ON;. השאירו את זה כבוי אלא אם יש לכם סיבה מסוימת. - כתיבות מצד האפליקציה יכולות לעקוף אותם, אבל רק אם מדלגים על מסד הנתונים. כל עוד כל כתיבה עוברת דרך SQLite, הטריגר ירוץ. גם ORM שמריץ אצוות דרך SQL גולמי מפעיל אותם.
- אל תפזרו לוגיקה עסקית על פני טריגרים רבים. הם בלתי נראים ממקום הקריאה: מי שמנסה להבין "למה השורה הזו הופיעה?" צריך לחפש ב-
sqlite_master. השתמשו בהם לנושאים רוחביים (יומני ביקורת, עמודות נגזרות, כתיבה ל-views) והשאירו את השאר בקוד האפליקציה.
דוגמה מציאותית ליומן ביקורת
מחברים את התבניות יחד: מעקב אחרי כל שינוי בטבלת posts:
טריגר אחד שומר על updated_at מדויק וכותב שורת ביקורת במקום אחד. קוד האפליקציה שמבצע את ה-UPDATE לא צריך לדעת שאף אחד מהדברים האלה קיים.
הבא בתור: תמיכה ב-JSON
טריגרים מטפלים באוטומציה סביב אירועים בשורות. החלק הבא של SQLite המתקדם הוא מה שאפשר לשמור בתוך שורה: JSON. ל-SQLite יש סט מלא של פונקציות JSON לשאילתות ולעדכון של נתונים מובנים בלי לצאת מ-SQL, וזה הדף הבא.
שאלות נפוצות
מה זה טריגר ב-SQLite?
טריגר הוא בלוק של SQL שרץ אוטומטית כשאירוע מסוים קורה בטבלה: INSERT, UPDATE או DELETE. מגדירים אותו פעם אחת עם CREATE TRIGGER, ו-SQLite מפעיל אותו בשבילכם בכל פעם שהאירוע מתרחש. כך שומרים יומני ביקורת, מתחזקים עמודות נגזרות או אוכפים כללים בלי לסמוך על כך שהאפליקציה תזכור.
מה ההבדל בין טריגרים מסוג BEFORE, AFTER ו-INSTEAD OF?
BEFORE רץ לפני שהשינוי בשורה מוחל, ושימושי לאימות או לתיקון השורה. AFTER רץ אחרי שהשינוי קרה, ושימושי לרישום ביומן או לסנכרון טבלאות אחרות. INSTEAD OF עובד רק על views ומחליף לגמרי את הפעולה שהייתה אמורה לקרות, וכך מאפשר לכתוב ל-view.
איך מתייחסים לשורה שמשתנה בתוך טריגר?
השתמשו ב-NEW.column לשורה הנכנסת ב-INSERT וב-UPDATE, וב-OLD.column לשורה הקיימת ב-UPDATE וב-DELETE. טריגרים של INSERT רואים רק את NEW, טריגרים של DELETE רואים רק את OLD, וטריגרים של UPDATE רואים את שניהם. ההפניות האלה מתייחסות לשורה שמעובדת כרגע.
איך מציגים או מוחקים טריגרים ב-SQLite?
טריגרים נשמרים ב-sqlite_master: SELECT name, tbl_name FROM sqlite_master WHERE type = 'trigger'; מציג את כולם. כדי למחוק אחד, DROP TRIGGER trigger_name;, או DROP TRIGGER IF EXISTS trigger_name; אם אתם לא בטוחים שהוא קיים. מחיקת טבלה מוחקת גם את הטריגרים שלה.