Przykład wyszukiwania pełnotekstowego w systemie Android

87

Trudno mi zrozumieć, jak korzystać z wyszukiwania pełnotekstowego (FTS) w systemie Android. Przeczytałem dokumentację SQLite dotyczącą rozszerzeń FTS3 i FTS4 . Wiem, że można to zrobić na Androidzie . Trudno mi jednak znaleźć jakiekolwiek przykłady, które mogę zrozumieć.

Podstawowy model bazy danych

Tabela bazy danych SQLite (nazwana example_table) ma 4 kolumny. Jednak istnieje tylko jedna kolumna (nazwana text_column), która musi zostać zindeksowana w celu wyszukiwania pełnotekstowego. Każdy wiersz text_columnzawiera tekst o długości od 0 do 1000 słów. Całkowita liczba wierszy jest większa niż 10 000.

  • Jak skonfigurowałbyś stół i / lub wirtualny stół FTS?
  • Jak wykonałbyś zapytanie FTS na text_column?

Dodatkowe uwagi:

  • Ponieważ tylko jedna kolumna musi być indeksowana, użycie tylko tabeli FTS (i usuwanie example_table) byłoby nieefektywne w przypadku zapytań innych niż FTS .
  • W przypadku tak dużej tabeli przechowywanie zduplikowanych wpisów text_columnw tabeli FTS byłoby niepożądane. Ten post sugeruje użycie zewnętrznej tabeli zawartości .
  • Tabele zawartości zewnętrznej używają FTS4, ale FTS4 nie jest obsługiwany przed Android API 11 . Odpowiedź może zakładać, że API> = 11, ale skomentowanie opcji obsługi niższych wersji byłoby pomocne.
  • Zmiana danych w oryginalnej tabeli nie powoduje automatycznej aktualizacji tabeli FTS (i odwrotnie). Uwzględnianie wyzwalaczy w odpowiedzi nie jest konieczne w tym podstawowym przykładzie, ale mimo wszystko byłoby pomocne.
Suragch
źródło
3
Dobrze udokumentowane pytanie, odpieram arbitralne głosy przeciw, które tu dostałeś.
Mekap

Odpowiedzi:

117

Najbardziej podstawowa odpowiedź

Używam zwykłego sql poniżej, aby wszystko było tak jasne i czytelne, jak to tylko możliwe. W swoim projekcie możesz skorzystać z wygodnych metod Androida. dbPrzedmiot używany poniżej jest przykładem SQLiteDatabase .

Utwórz tabelę FTS

db.execSQL("CREATE VIRTUAL TABLE fts_table USING fts3 ( col_1, col_2, text_column )");

Może to dotyczyć onCreate()metody rozszerzonej SQLiteOpenHelperklasy.

Wypełnij tabelę FTS

db.execSQL("INSERT INTO fts_table VALUES ('3', 'apple', 'Hello. How are you?')");
db.execSQL("INSERT INTO fts_table VALUES ('24', 'car', 'Fine. Thank you.')");
db.execSQL("INSERT INTO fts_table VALUES ('13', 'book', 'This is an example.')");

Byłoby lepiej użyć SQLiteDatabase # insert lub przygotowanych instrukcji niż execSQL.

Zapytanie o tabelę FTS

String[] selectionArgs = { searchString };
Cursor cursor = db.rawQuery("SELECT * FROM fts_table WHERE fts_table MATCH ?", selectionArgs);

Możesz również użyć metody zapytania SQLiteDatabase # . ZanotujMATCH słowo kluczowe.

Pełniejsza odpowiedź

Powyższa wirtualna tabela FTS ma z tym problem. Każda kolumna jest indeksowana, ale jest to strata miejsca i zasobów, jeśli niektóre kolumny nie muszą być indeksowane. Jedyną kolumną, która wymaga indeksu FTS, jest prawdopodobnietext_column .

Aby rozwiązać ten problem, użyjemy kombinacji zwykłej tabeli i wirtualnej tabeli FTS. Tabela FTS będzie zawierać indeks, ale żadnych rzeczywistych danych ze zwykłej tabeli. Zamiast tego będzie zawierał link do zawartości zwykłej tabeli. Nazywa się to tabelą zawartości zewnętrznej .

wprowadź opis obrazu tutaj

Utwórz tabele

db.execSQL("CREATE TABLE example_table (_id INTEGER PRIMARY KEY, col_1 INTEGER, col_2 TEXT, text_column TEXT)");
db.execSQL("CREATE VIRTUAL TABLE fts_example_table USING fts4 (content='example_table', text_column)");

Zauważ, że musimy do tego użyć FTS4 zamiast FTS3. FTS4 nie jest obsługiwany w Androidzie przed wersją API 11. Możesz albo (1) zapewnić funkcjonalność wyszukiwania tylko dla API> = 11, albo (2) użyć tabeli FTS3 (ale to oznacza, że ​​baza danych będzie większa ponieważ istnieje kolumna pełnego tekstu w obu bazach danych).

Wypełnij tabele

db.execSQL("INSERT INTO example_table (col_1, col_2, text_column) VALUES ('3', 'apple', 'Hello. How are you?')");
db.execSQL("INSERT INTO example_table (col_1, col_2, text_column) VALUES ('24', 'car', 'Fine. Thank you.')");
db.execSQL("INSERT INTO example_table (col_1, col_2, text_column) VALUES ('13', 'book', 'This is an example.')");

(Ponownie, są lepsze sposoby na wstawianie niż z execSQL. Używam go tylko ze względu na jego czytelność).

Jeśli spróbujesz teraz wykonać zapytanie FTS fts_example_table, nie uzyskasz żadnych wyników. Powodem jest to, że zmiana jednej tabeli nie zmienia automatycznie drugiej tabeli. Musisz ręcznie zaktualizować tabelę FTS:

db.execSQL("INSERT INTO fts_example_table (docid, text_column) SELECT _id, text_column FROM example_table");

(To docidjest jak rowiddla zwykłej tabeli.) Musisz upewnić się, że zaktualizujesz tabelę FTS (tak, aby mogła aktualizować indeks) za każdym razem, gdy dokonujesz zmiany (INSERT, DELETE, UPDATE) w zewnętrznej tabeli zawartości. Może to być kłopotliwe. Jeśli tworzysz tylko wstępnie wypełnioną bazę danych, możesz to zrobić

db.execSQL("INSERT INTO fts_example_table(fts_example_table) VALUES('rebuild')");

który odbuduje cały stół. Może to jednak być powolne, więc nie jest to coś, co chcesz robić po każdej drobnej zmianie. Zrobiłbyś to po zakończeniu wszystkich wstawień w zewnętrznej tabeli zawartości. Jeśli musisz automatycznie synchronizować bazy danych, możesz użyć wyzwalaczy . Przejdź tutaj i przewiń trochę w dół, aby znaleźć wskazówki.

Przeszukuj bazy danych

String[] selectionArgs = { searchString };
Cursor cursor = db.rawQuery("SELECT * FROM fts_example_table WHERE fts_example_table MATCH ?", selectionArgs);

To jest to samo co poprzednio, z tym że tym razem masz dostęp tylko do text_column(i docid). Co się stanie, jeśli chcesz pobrać dane z innych kolumn w tabeli zawartości zewnętrznej? Ponieważ docidtabela FTS pasuje do rowid(iw tym przypadku _id) tabeli zawartości zewnętrznej, możesz użyć sprzężenia. (Dzięki tej odpowiedzi za pomoc.)

String sql = "SELECT * FROM example_table WHERE _id IN " +
        "(SELECT docid FROM fts_example_table WHERE fts_example_table MATCH ?)";
String[] selectionArgs = { searchString };
Cursor cursor = db.rawQuery(sql, selectionArgs);

Dalsze czytanie

Przejrzyj dokładnie te dokumenty, aby zobaczyć inne sposoby korzystania z wirtualnych tabel FTS:

Dodatkowe uwagi

Suragch
źródło
1
W rzeczywistości, jeśli używasz tabeli fts w sposób, który określiłeś (wybierając z tabeli non-fts, gdzie _id jest zawarty w zestawie docid zwracanym przez dopasowanie tabeli fts), możesz zaoszczędzić miejsce, używając content = "" . Spowoduje to utworzenie indeksu pełnotekstowego bez powielania treści. Zobacz tabele bez zawartości FTS4
astyanaxas
Opcja zawartości FTS4 została dodana nie wcześniej niż w SQLite 3.7.9 ( sqlite.org/releaselog/3_7_11.html ), co oznacza, że ​​jest niedostępna przed Android API 16. SQLiteDatabase wyrzuci ją przy próbie użycia.
Knuckles,
Jak uzyskać dopasowanie pół słowa za pomocą tego zapytania?
Hitesh Danidhariya
@HiteshDanidhariya, czy to nie powoduje częściowego dopasowania słów? Przepraszam, minęło trochę czasu, odkąd nad tym pracowałem, ale myślałem, że już to zrobiło.
Suragch
@suragch Mam rozwiązanie. Musiałem dodać „*” po wyszukiwaniu i dzięki. Twoja odpowiedź bardzo mi pomogła. :)
Hitesh Danidhariya
3

Nie zapomnij o używaniu zawartości z do odbudowania tabeli fts.

Robię to z wyzwalaczem podczas aktualizacji, wstawiania, usuwania

James Kipling
źródło
INSERT INTO foo_fts VALUES("rebuild")
James Kipling,