Menu

SQLite Full-Text Search: טבלאות וירטואליות של FTS5 ו-MATCH

איך מוסיפים חיפוש טקסט מלא ל-SQLite עם FTS5: יצירת טבלאות וירטואליות, האופרטור MATCH, דירוג BM25 ושמירה על סנכרון בין האינדקס לנתונים.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

LIKE לא עומד בעומס

אם כבר חיפשתם טקסט ב-SQLite, כנראה השתמשתם ב-LIKE '%word%'. זה עובד בטבלאות קטנות ומתפרק בגדולות. אין אינדקס שיכול לעזור: SQLite צריכה לסרוק כל שורה, להמיר אותה לאותיות קטנות ולבדוק אם תת-המחרוזת מופיעה. גבולות מילים, דירוג, שאילתות מרובות מילים והתאמת קידומות, את כל אלה אתם צריכים לממש בעצמכם.

FTS5 היא התשובה המובנית. זה סוג של טבלה וירטואלית שמתחזקת אינדקס הפוך על עמודות הטקסט שלכם, מבינה שפת שאילתות קטנה ומדרגת תוצאות עם BM25. היא מגיעה עם SQLite כברירת מחדל, בלי הרחבה להתקנה.

יצירת טבלת FTS5

יוצרים טבלת FTS5 עם CREATE VIRTUAL TABLE ... USING fts5(...), ומפרטים את עמודות הטקסט שרוצים לאנדקס:

שלושה דברים ששווה לשים לב אליהם. לעמודות אין טיפוסים: FTS5 מתייחסת לכל דבר כטקסט. האופרטור MATCH מופעל על שם הטבלה (posts MATCH ...), לא על עמודה. והשאילתה לא רגישה לאותיות גדולות וקטנות ומפורקת לטוקנים, כך ש-'sqlite' מוצא את SQLite בכל אחת מהשורות.

שפת השאילתות של MATCH

MATCH מקבל יותר ממילה בודדת. למחרוזת השאילתה יש דקדוק קטן משלה:

מה כל אחת עושה:

  • 'fts5 AND prefix': שני הטוקנים חייבים להופיע (בכל סדר, בכל מקום בשורה).
  • '"keep fts"': ביטוי מדויק, בסדר הזה.
  • 'trig*': חיפוש קידומת, מתאים ל-trigger, triggers, trigonometry...
  • 'index NOT trigger': מכיל את index, לא מכיל את trigger.

אפשר גם לכוון לעמודה בודדת עם column:term, למשל 'title:sqlite'. הדקדוק המלא כולל סוגריים לקיבוץ ו-OR לחלופות, בדיוק הצורה שהייתם מצפים לה ממנוע חיפוש.

דירוג עם BM25

כברירת מחדל, FTS5 מצמידה עמודה נסתרת בשם rank לכל שורה. זה ציון הרלוונטיות של BM25: מספרים נמוכים יותר הם התאמות טובות יותר. מיינו לפיה כדי לקבל קודם את התוצאות הרלוונטיות ביותר:

רוצים לתת לעמודות מסוימות משקל גבוה יותר מאחרות? קראו ל-bm25() עם משקלות, אחד לכל עמודה לפי סדר ההצהרה:

הפוסט הראשון מנצח כי sqlite מופיע ב-title (במשקל 10×) ולא רק ב-body (במשקל 1×). בחרו משקלות שמתאימים לאופן שבו האפליקציה שלכם באמת רוצה לדרג.

שמירה על סנכרון האינדקס

טבלת FTS5 הפשוטה ביותר שומרת עותק משלה של הטקסט. זה בסדר לנתונים בסגנון לוג שרק מוסיפים אליהם, אבל לרוב האפליקציות כבר יש טבלה אמיתית, והן רוצות ש-FTS יעקוב אחריה. התבנית הנקייה היא טבלת FTS עם תוכן חיצוני (external content) ועוד שלושה טריגרים.

content='articles' אומר ל-FTS5 לא לשמור את הטקסט בעצמה: היא תביא אותו מהטבלה articles כשצריך. הטריגרים משקפים את הכתיבות לאינדקס ה-FTS. עכשיו articles היא מקור האמת, ו-articles_fts היא רק מבנה החיפוש שלצידה.

ה-INSERT INTO articles_fts(articles_fts, ...) VALUES ('delete', ...) שנראה מוזר הוא תחביר הפקודות של FTS5 שאומר לאינדקס להסיר שורה.

קטעי תצוגה והדגשה

תוצאות חיפוש צריכות בדרך כלל תצוגה מקדימה שבה המונחים שנמצאו מודגשים. ל-FTS5 יש שתי פונקציות לזה:

  • highlight(table, column_index, open, close) מחזירה את הטקסט המלא של העמודה כשהטוקנים שנמצאו עטופים.
  • snippet(table, column_index, open, close, ellipsis, token_count) מחזירה קטע קצר שממורכז סביב ההתאמה.

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

מלכודות שכדאי להכיר

כמה דברים שתופסים אנשים:

  • MATCH עובד רק על טבלאות FTS. אי אפשר להפעיל MATCH על עמודה רגילה. אם צריך חיפוש על טבלה קיימת, השתמשו בתבנית ה-external-content שלמעלה.
  • אל תשכחו למיין לפי rank. בלי זה, FTS5 מחזירה שורות לפי סדר האחסון, שאין לו שום קשר לרלוונטיות.
  • ה-tokenizer חשוב. ה-tokenizer ברירת המחדל (unicode61) מפצל לפי גבולות מילים של Unicode וממיר לאותיות קטנות. בשביל stemming (ש-run יתאים ל-running), השתמשו ב-tokenizer porter: USING fts5(body, tokenize='porter').
  • FTS5 הוא לא מנוע שסולח על שגיאות הקלדה. הוא מבצע התאמת קידומות, לא התאמה מקורבת. אם צריך התנהגות של "האם התכוונתם ל...", זו שכבה מעל FTS5.
  • טבלאות contentless (content='') קטנות יותר אבל מאבדות מידע. אפשר לחפש בהן, אבל אי אפשר לשלוף את הטקסט המקורי, רק את ה-rowid. שימושי כשהטקסט שמור במקום אחר.

הצעד הבא: Window Functions

FTS5 מכסה חיפוש טקסט. העמוד הבא עוסק בצורה אחרת של שאילתות מתקדמות: window functions, שמאפשרות לחשב סכומים מצטברים, דירוגים וניתוחים לכל קבוצה בלי לכווץ את השורות לצבירות.

שאלות נפוצות

מה זה FTS5 ב-SQLite?

FTS5 היא הרחבת חיפוש הטקסט המלא המובנית של SQLite. יוצרים טבלה וירטואלית מיוחדת עם CREATE VIRTUAL TABLE ... USING fts5(...) ושולפים ממנה עם האופרטור MATCH. היא מפרקת את הטקסט לטוקנים בזמן ההכנסה, שומרת אינדקס הפוך ומדרגת תוצאות עם BM25 כברירת מחדל.

במה MATCH שונה מ-LIKE ב-SQLite?

LIKE מבצע סריקה ליניארית של תת-מחרוזות ומתעלם מגבולות מילים. MATCH משתמש באינדקס ההפוך של FTS5, ולכן הוא מהיר בטבלאות גדולות ומבין טוקנים, שאילתות קידומת (term*), אופרטורים בוליאניים (AND, OR, NOT) וחיפוש ביטויים ("exact phrase"). MATCH עובד רק על טבלאות וירטואליות של FTS.

איך שומרים על אינדקס FTS5 מסונכרן עם טבלה רגילה?

אפשר להשתמש בטבלת FTS5 מסוג contentless או external-content שמצביעה על הטבלה האמיתית, או ליצור טריגרים AFTER INSERT, AFTER UPDATE ו-AFTER DELETE שמשקפים את השינויים לטבלת ה-FTS. תבנית ה-external-content (content='posts') חוסכת שמירה כפולה של הטקסט.

איך מדרגים תוצאות של חיפוש טקסט מלא ב-SQLite?

FTS5 חושפת עמודה נסתרת בשם rank שמחזירה ציון BM25 (נמוך יותר הוא טוב יותר). מיינו לפיה ישירות: ORDER BY rank. אפשר גם לקרוא ל-bm25(table) כדי לקבל את הציון במפורש, או להעביר משקלות לעמודות כמו bm25(posts, 10.0, 1.0) כדי לתת לכותרת משקל גבוה יותר מהגוף.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל