בדף הזה מוסבר איך לשרשר מחרוזות TOKENLIST באינדקס חיפוש כשמגדירים את הסכימה, או בשאילתת חיפוש כשמבצעים חיפוש טקסט מלא ב-Spanner.
שילוב של TOKENLISTs באינדקס חיפוש
לפעמים צריך שהאפליקציה תחפש בשדות נפרדים. במקרים אחרים, האפליקציה צריכה לחפש בכל השדות. לדוגמה, בטבלה עם שתי עמודות של מחרוזות, יכול להיות שתרצו שהאפליקציה תחפש בשתי העמודות בלי להבחין מאיזו עמודה מגיעות ההתאמות.
ב-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 הוא 'כחול' אבל לא 'הערה', והערך של Studio_Tokens הוא 'הערה' אבל לא 'כחול'.