UNIQUE פירושו "אסורות כפילויות"
אילוץ UNIQUE אומר ל-SQLite שערכים בעמודה (או בקבוצת עמודות) אסור שיחזרו על עצמם בין שורות. כך אומרים "שני משתמשים לא יכולים לחלוק אימייל" או "קוד מוצר מופיע לכל היותר פעם אחת".
ההכנסה השלישית נכשלת עם UNIQUE constraint failed: users.email. SQLite בודק את האילוץ בכל כתיבה ודוחה כל דבר שהיה יוצר כפילות. שתי השורות הראשונות נשמרות. השלישית אף פעם לא נכנסת.
מאחורי הקלעים, UNIQUE ממומש כאינדקס ייחודי, אותו מבנה נתונים ש-SQLite משתמש בו לחיפושים מהירים, כך שהבדיקה זולה והעמודה מקבלת אינדקס אוטומטית.
תחביר ברמת העמודה מול ברמת הטבלה
אפשר לכתוב UNIQUE בשתי דרכים. בשורה לצד עמודה, או כפסוקית נפרדת בסוף הגדרת הטבלה:
לעמודה בודדת, שתי הדרכים שקולות: בחרו במה שנקרא טוב יותר. הצורה ברמת הטבלה הופכת להכרחית ברגע שצריך ייחודיות על פני יותר מעמודה אחת.
UNIQUE מורכב: כמה עמודות יחד
לפעמים עמודה בודדת לא ייחודית בפני עצמה, אבל שילוב צריך להיות. משתמש יכול להירשם לקורסים רבים, ולקורס יכולים להיות משתמשים רבים, אבל אותו זוג (user_id, course_id) לא צריך להופיע פעמיים:
האילוץ הוא על הזוג, לא על כל עמודה לבדה. משתמש 1 יכול להירשם לקורסים רבים, לקורס 100 יכולים להיות משתמשים רבים, אבל רק פעם אחת לכל שילוב.
זו התבנית הבסיסית לטבלאות קישור ביחסי רבים לרבים.
UNIQUE מול PRIMARY KEY
הם נשמעים דומים והם קשורים, אבל הם לא אותו דבר:
- לטבלה יש לכל היותר
PRIMARY KEYאחד. יכולים להיות לה אילוציUNIQUEרבים. PRIMARY KEYהוא הזהות של השורה: מה שמפתחות זרים מצביעים עליו, מה ש-rowidהוא כינוי שלו.UNIQUEהוא רק "הערך הזה (או השילוב הזה) לא חוזר".- בטבלה רגילה, עמודת
UNIQUEיכולה להכיל ערכיNULL.PRIMARY KEYלא יכול (עם חריג היסטורי אחד שנדלג עליו).
צורה נפוצה:
id הוא מה ששאר מסד הנתונים מתייחס אליו. email ו-username ייחודיים כי האפליקציה דורשת זאת, לא כי הם הזהות. אם משתמש משנה את האימייל שלו, ה-id נשאר זהה: זו בדיוק הסיבה להפריד ביניהם.
המוזרות של NULL
זה מכשיל כמעט את כולם בפעם הראשונה. עמודת UNIQUE ב-SQLite מקבלת כמה ערכי NULL שתרצו:
שלושה NULL, אין בעיה. שני 'ada@example.com', זו התנגשות.
הסיבה: SQL מתייחס ל-NULL כ"לא ידוע", ושני ערכים לא ידועים לא נחשבים שווים, ולכן בדיקת הייחודיות לא יכולה לקבוע שהם כפולים. אם צריך לכל היותר NULL אחד, התיקון הנקי ביותר הוא NOT NULL UNIQUE. אם NULL הוא ערך תקין אבל רק אחד לכל שילוב של עמודה אחרת, פנו לאינדקס חלקי (מוסבר בהמשך, בפרק על אינדקסים).
טיפול בהתנגשויות: ON CONFLICT
כברירת מחדל, הפרה של UNIQUE מבטלת את הפקודה. אבל לפעמים רוצים התנהגות אחרת: להחליף את השורה הקיימת, להתעלם מהחדשה, או לעדכן עמודות מסוימות. SQLite נותן שתי דרכים לבקש את זה.
הראשונה מובנית באילוץ עם ON CONFLICT:
בפעם השנייה ש-theme מוכנס, השורה הקיימת נמחקת והחדשה תופסת את מקומה. אפשרויות נוספות הן IGNORE (דילוג שקט), ABORT (ברירת המחדל), FAIL ו-ROLLBACK.
הדרך השנייה היא ברמת הפקודה, עם תחביר ה-upsert, ובדרך כלל גמישה יותר כי היא יכולה לעדכן עמודות מסוימות:
ההכנסה הראשונה יוצרת את השורה. שתי הבאות נתקלות באילוץ ה-UNIQUE ועוברות לענף ה-DO UPDATE, שמגדיל את count. זו תבנית ה-upsert של INSERT ... ON CONFLICT, ויש עליה דף ייעודי בהמשך.
אילוץ UNIQUE מול אינדקס UNIQUE
CREATE UNIQUE INDEX עושה את אותה עבודה כמו אילוץ UNIQUE. למעשה, אילוץ UNIQUE יוצר אינדקס ייחודי מאחורי הקלעים: זה כמעט אותו מנגנון עם כובעים שונים.
מתי להעדיף כל אחד:
- אילוץ כשהייחודיות היא חלק מהגדרת הטבלה. היא מתועדת ממש ליד העמודות.
- אינדקס ייחודי כשרוצים אינדקס חלקי (פסוקית
WHERE), צריכים שם מסוים, או רוצים להוסיף אותו לטבלה קיימת בלי לכתוב אותה מחדש.ALTER TABLEשל SQLite לא יכול להוסיף אילוץ, אבל תמיד אפשר להוסיף אינדקס.
ההתנהגות בכתיבה זהה. הבחירה היא בעיקר איפה אתם רוצים שהכלל יחיה בסכמה.
הוספת UNIQUE לטבלה קיימת
ALTER TABLE של SQLite מוגבל בכוונה: אין ALTER TABLE ... ADD CONSTRAINT. שתי האפשרויות המעשיות:
אפשרות 2, כשאתם באמת רוצים פסוקית UNIQUE אפויה בתוך הגדרת הטבלה, היא ריקוד כתיבת הטבלה מחדש: יוצרים טבלה חדשה עם האילוץ, מעתיקים אליה את הנתונים, מוחקים את הישנה ומשנים שם. זה מוסבר בדף הבא.
שימו לב: אם אתם מוסיפים ייחודיות לעמודה שכבר יש בה כפילויות, ה-CREATE UNIQUE INDEX ייכשל. נקו קודם את השורות הכפולות, ואז הוסיפו את האינדקס.
כש-UNIQUE נכשל: קריאת השגיאה
הודעת השגיאה אומרת בדיוק איזה אילוץ התפוצץ:
Error: UNIQUE constraint failed: users.email
Error: UNIQUE constraint failed: enrollments.user_id, enrollments.course_id
הצורה הראשונה היא אילוץ על עמודה בודדת, users.email. השנייה היא מורכבת: שתי העמודות מופיעות כי השילוב כבר קיים. כשאתם רואים את זה:
- זהו איזו שורה כבר מכילה את הערך המתנגש (
SELECT ... WHERE email = '...'). - החליטו אם אתם רוצים לעדכן את השורה הזו, לדלג על ההכנסה או להשתמש בערך אחר.
- אם כפילויות צפויות ואתם רוצים למזג אותן, עברו ל-
INSERT ... ON CONFLICT DO UPDATE.
השגיאה רועשת כי ברוב המקרים אתם באמת רוצים לדעת: כפילויות שקטות היו גרועות יותר מכתיבה שנכשלה.
הבא בתור: מחיקה ושינוי של טבלאות
אי אפשר להוסיף אילוצי UNIQUE לטבלה קיימת עם ALTER TABLE פשוט. המגבלה הזו היא הסיבה של-SQLite יש ריקוד מיוחד לשינויי סכמה, כתיבת הטבלה מחדש, וזה הנושא של הדף הבא, לצד היסודות של מחיקת טבלאות בצורה נקייה.
שאלות נפוצות
איך מוסיפים אילוץ UNIQUE ב-SQLite?
הוסיפו UNIQUE להגדרת עמודה (email TEXT UNIQUE) או כתבו פסוקית UNIQUE(col1, col2) ברמת הטבלה לייחודיות על פני כמה עמודות. SQLite אוכף את זה על ידי יצירת אינדקס ייחודי מאחורי הקלעים ודחיית כל INSERT או UPDATE שהיה יוצר כפילות.
מה ההבדל בין UNIQUE ל-PRIMARY KEY ב-SQLite?
לטבלה יכול להיות רק PRIMARY KEY אחד, אבל אילוצי UNIQUE רבים. PRIMARY KEY גם מרמז על NOT NULL (בטבלאות strict וב-INTEGER PRIMARY KEY), בעוד שעמודות UNIQUE יכולות להכיל כמה ערכי NULL. השתמשו במפתח הראשי לזהות של השורה, וב-UNIQUE לעמודות אחרות שאסור שיהיו בהן כפילויות.
למה SQLite מאפשר כמה NULL בעמודת UNIQUE?
כי SQL מתייחס ל-NULL כ'לא ידוע', ושני ערכים לא ידועים לא נחשבים שווים. לכן עמודת UNIQUE מקבלת כמה שורות NULL שתרצו: רק ערכים שאינם NULL חייבים להיות שונים. אם צריך לכל היותר NULL אחד, הוסיפו NOT NULL או השתמשו באינדקס ייחודי חלקי.
איך מתקנים שגיאת 'UNIQUE constraint failed'?
השגיאה אומרת ש-INSERT או UPDATE היה יוצר ערך כפול בעמודת UNIQUE (או PRIMARY KEY). אפשר לשנות את הערך שאתם מכניסים, למחוק קודם את השורה הקיימת, או להשתמש ב-INSERT ... ON CONFLICT (upsert) כדי להגיד ל-SQLite מה לעשות כשההתנגשות קורית.