בדף הזה מוסבר איך לשרשר מחרוזות TOKENLIST באינדקס חיפוש כשמגדירים את הסכימה, או בשאילתת חיפוש כשמבצעים חיפוש טקסט מלא ב-Spanner.
שילוב של רשימות אסימונים באינדקס חיפוש
לפעמים צריך שהאפליקציה תחפש בשדות נפרדים. במקרים אחרים, האפליקציה צריכה לחפש בכל השדות. לדוגמה, בטבלה עם שתי עמודות מחרוזות, יכול להיות שתרצו שהאפליקציה תחפש בשתי העמודות בלי להבחין מאיזו עמודה מגיעות ההתאמות.
ב-Spanner, יש שתי דרכים לעשות את זה:
- להפריד את המילים לטוקנים ולשרשר את ה-
TOKENLISTs שמתקבלים (מומלץ). - שרשור מחרוזות וטוקניזציה של התוצאה.
בגישה השנייה יש שתי בעיות:
- אם רוצים ליצור אינדקס ל-
Titleאו ל-Studioבנפרד, בנוסף ליצירת אינדקס משולב ל-TOKENLIST, אותו טקסט עובר טוקניזציה פעמיים. כתוצאה מכך, העסקאות משתמשות ביותר משאבים. - חיפוש של ביטוי יתבצע בשני השדות. לדוגמה, אם
@pמוגדר כ-"Blue Note", הוא תואם לשורה שמכילה גםTitle="Big Blue Note" וגםStudio="Blue Note Studios".
הגישה הראשונה פותרת את הבעיות האלה כי ביטוי מתאים רק לשדה אחד, וכל שדה מחרוזת עובר טוקניזציה רק פעם אחת אם גם ה-TOKENLISTים הנפרדים וגם ה-TOKENLISTים המשולבים עוברים אינדוקס. למרות שכל שדה מחרוזת עובר טוקניזציה רק פעם אחת, התוצאות של TOKENLIST מאוחסנות בנפרד באינדקס.
הופכים מילים לאסימונים בנפרד ומשרשרים את TOKENLISTs
בדוגמה הבאה, כל מילה עוברת טוקניזציה, ונעשה שימוש ב-TOKENLIST_CONCAT כדי לשרשר את ה-TOKENLIST:
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
Title STRING(MAX),
Studio STRING(MAX),
Title_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title)) HIDDEN,
Studio_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Studio)) HIDDEN,
Combined_Tokens TOKENLIST AS (TOKENLIST_CONCAT([Title_Tokens, Studio_Tokens])) HIDDEN,
) PRIMARY KEY(AlbumId);
CREATE SEARCH INDEX AlbumsIndex ON Albums(Combined_Tokens);
SELECT AlbumId FROM Albums WHERE SEARCH(Combined_Tokens, @p);
PostgreSQL
ב-PostgreSQL משתמשים ב-spanner.tokenlist_concat לשרשור. פרמטר השאילתה $1 קשור ל-'Hatel Kaliphorn'.
CREATE TABLE albums (
albumid character varying NOT NULL,
title character varying,
studio character varying,
title_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title)) VIRTUAL HIDDEN,
studio_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(studio)) VIRTUAL HIDDEN,
combined_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenlist_concat(ARRAY[spanner.tokenize_fulltext(title), spanner.tokenize_fulltext(studio)])) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));
CREATE SEARCH INDEX albumsindex ON albums(combined_tokens);
SELECT albumid FROM albums WHERE spanner.search(combined_tokens, $1);
שימו לב ש-tokenlist_concat לא מתקשר ל-title_tokens או ל-studio_tokens, אלא ל-spanner.tokenize_fulltext(title) ול-spanner.tokenize_fulltext(studio). הסיבה לכך היא ש-PostgreSQL לא תומך בהפניה לעמודות שנוצרו שנמצאות בתוך עמודות שנוצרו אחרות. spanner.tokenlist_concat צריך לקרוא לפונקציות של טוקניזציה ולא להפנות ישירות לעמודות של רשימת טוקנים.
אפשר להטמיע שרשור של TOKENLIST גם בצד השאילתה.
מידע נוסף זמין במאמר בנושא שרשור TOKENLISTבצד השאילתה.
TOKENLIST_CONCAT נתמך גם בחיפושים של טקסט מלא וגם בחיפושים של מחרוזות משנה.
ב-Spanner אי אפשר לשלב סוגים של טוקניזציה, כמו TOKENIZE_FULLTEXT ו-TOKENIZE_SUBSTRING באותה קריאה ל-TOKENLIST_CONCAT.
ב-GoogleSQL, אפשר לשנות את ההגדרה של עמודות טקסט TOKENLIST בעמודות לא מאוחסנות כדי להוסיף עמודות נוספות. האפשרות הזו שימושית כשרוצים להוסיף עמודה נוספת לTOKENLIST_CONCAT. שינוי הביטוי של העמודה שנוצרה לא יעדכן את השורות הקיימות באינדקס.
שרשור מחרוזות וטוקניזציה של התוצאה
בדוגמה הבאה, מחברים מחרוזות ומבצעים טוקניזציה של התוצאה:
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
Title STRING(MAX),
Studio STRING(MAX),
Combined_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title || " " || Studio)) HIDDEN,
) PRIMARY KEY(AlbumId);
CREATE SEARCH INDEX AlbumsIndex ON Albums(Combined_Tokens);
SELECT AlbumId FROM Albums WHERE SEARCH(Combined_Tokens, @p);
PostgreSQL
CREATE TABLE albums (
albumid character varying NOT NULL,
title character varying,
studio character varying,
combined_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title || ' ' || studio)) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));
CREATE SEARCH INDEX albumsindex ON albums(combined_tokens);
SELECT albumid FROM albums WHERE spanner.search(combined_tokens, $1);
שרשור TOKENLIST בצד השאילתה
החיסרון באינדוקס של TOKENLIST הוא שהוא מגדיל את עלות האחסון והכתיבה. כל אסימון מאוחסן עכשיו בדיסק פעמיים:
פעם אחת ברשימת פרסום של TOKENLIST המקורי, ופעם אחת ברשימת פרסום של TOKENLIST המשולב. שרשור של עמודות TOKENLIST בצד השאילתה מאפשר להימנע מהעלות הזו, אבל השאילתה משתמשת ביותר משאבי מחשוב.
כדי לשרשר כמה TOKENLIST, משתמשים בפונקציה TOKENLIST_CONCAT בשאילתה SEARCH. בקטע הזה נשתמש בסכימה לדוגמה הבאה:
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
Title STRING(MAX),
Studio STRING(MAX),
Title_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Title)) HIDDEN,
Studio_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Studio)) HIDDEN,
) PRIMARY KEY(AlbumId);
CREATE SEARCH INDEX AlbumsIndex ON Albums(Title_Tokens, Studio_Tokens);
PostgreSQL
CREATE TABLE albums (
albumid character varying NOT NULL,
title character varying,
studio character varying,
title_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(title)) VIRTUAL HIDDEN,
studio_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(studio)) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));
CREATE SEARCH INDEX albumsindex ON albums(title_tokens, studio_tokens);
השאילתה הבאה מחפשת שורות שבהן האסימונים blue ו-note מופיעים איפשהו בעמודות Title ו-Studio. זה כולל שורות עם הערכים blue ו-note בעמודה Title, הערכים blue ו-note בעמודה Studio, הערך blue בעמודה Title והערך note בעמודה Studio, או להיפך.
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH(TOKENLIST_CONCAT([AlbumTitle_Tokens, Studio_Tokens]), 'blue note')
PostgreSQL
בדוגמה הזו נעשה שימוש ב-spanner.search עם spanner.tokenlist_concat.
SELECT albumid
FROM albums
WHERE spanner.search(spanner.tokenlist_concat(ARRAY[albumtitle_tokens, studio_tokens]), 'blue note')
TOKENLISTשרשור בצד הכתיבה ובצד השאילתה מניב תוצאות זהות.
הבחירה בין שתי האפשרויות היא פשרה בין עלות הדיסק לעלות השאילתה.
אפשרות אחרת היא שאפליקציה תחפש בכמה עמודות TOKENLIST ותשתמש בפונקציה OR יחד עם הפונקציה SEARCH:
GoogleSQL
SEARCH(AlbumTitle_Tokens, 'Blue Note') OR SEARCH(Studio_Tokens, 'Blue Note')
PostgreSQL
spanner.search(albumtitle_tokens, 'Blue Note') OR spanner.search(studio_tokens, 'Blue Note')
עם זאת, יש הבדל בסמנטיקה. התנאי לא תואם לאלבומים שבהם הערך של AlbumTitle_Tokens הוא blue אבל לא note, והערך של Studio_Tokens הוא note אבל לא blue.