Beispiel für eine Volltextsuche in Android

87

Es fällt mir schwer zu verstehen, wie man die Volltextsuche (FTS) mit Android verwendet. Ich habe die SQLite-Dokumentation zu den Erweiterungen FTS3 und FTS4 gelesen . Und ich weiß, dass es auf Android möglich ist . Es fällt mir jedoch schwer, Beispiele zu finden, die ich verstehen kann.

Das grundlegende Datenbankmodell

Eine SQLite-Datenbanktabelle (benannt example_table) enthält 4 Spalten. Es gibt jedoch nur eine Spalte (benannt text_column), die für eine Volltextsuche indiziert werden muss. Jede Zeile text_columnenthält Text mit einer Länge von 0 bis 1000 Wörtern. Die Gesamtzahl der Zeilen ist größer als 10.000.

  • Wie würden Sie die Tabelle und / oder die virtuelle FTS-Tabelle einrichten?
  • Wie würden Sie eine FTS-Abfrage durchführen text_column?

Zusätzliche Bemerkungen:

  • Da nur eine Spalte indiziert werden muss, wäre die Verwendung einer FTS-Tabelle (und das Löschen example_table) für Nicht-FTS-Abfragen ineffizient .
  • Für eine so große Tabelle wäre das Speichern doppelter Einträge text_columnin der FTS-Tabelle unerwünscht. In diesem Beitrag wird die Verwendung einer externen Inhaltstabelle vorgeschlagen .
  • Externe Inhaltstabellen verwenden FTS4, aber FTS4 wird vor Android API 11 nicht unterstützt . Eine Antwort kann eine API> = 11 annehmen, aber das Kommentieren von Optionen zur Unterstützung niedrigerer Versionen wäre hilfreich.
  • Durch Ändern der Daten in der Originaltabelle wird die FTS-Tabelle nicht automatisch aktualisiert (und umgekehrt). Das Einfügen von Triggern in Ihre Antwort ist für dieses grundlegende Beispiel nicht erforderlich, wäre aber dennoch hilfreich.
Suragch
quelle
3
Gut dokumentierte Frage, ich kontere die willkürliche Ablehnung, die Sie hier erhalten haben.
Mekap

Antworten:

117

Grundlegendste Antwort

Ich benutze das einfache SQL unten, damit alles so klar und lesbar wie möglich ist. In Ihrem Projekt können Sie die Android-Convenience-Methoden verwenden. Das dbunten verwendete Objekt ist eine Instanz von SQLiteDatabase .

FTS-Tabelle erstellen

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

Dies könnte in die onCreate()Methode Ihrer erweiterten SQLiteOpenHelperKlasse gehen.

Füllen Sie die FTS-Tabelle aus

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.')");

Es wäre besser, SQLiteDatabase # insert oder vorbereitete Anweisungen zu verwenden als execSQL.

FTS-Tabelle abfragen

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

Sie können auch die SQLiteDatabase # -Abfragemethode verwenden. Beachten Sie das MATCHSchlüsselwort.

Vollständigere Antwort

Die obige virtuelle FTS-Tabelle weist ein Problem auf. Jede Spalte ist indiziert, dies ist jedoch eine Verschwendung von Speicherplatz und Ressourcen, wenn einige Spalten nicht indiziert werden müssen. Die einzige Spalte, die einen FTS-Index benötigt, ist wahrscheinlich die text_column.

Um dieses Problem zu lösen, verwenden wir eine Kombination aus einer regulären Tabelle und einer virtuellen FTS-Tabelle. Die FTS-Tabelle enthält den Index, jedoch keine der tatsächlichen Daten aus der regulären Tabelle. Stattdessen wird ein Link zum Inhalt der regulären Tabelle angezeigt. Dies wird als externe Inhaltstabelle bezeichnet .

Geben Sie hier die Bildbeschreibung ein

Erstellen Sie die Tabellen

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)");

Beachten Sie, dass wir dafür FTS4 verwenden müssen, anstatt FTS3. FTS4 wird in Android vor API-Version 11 nicht unterstützt. Sie können entweder (1) nur Suchfunktionen für API> = 11 bereitstellen oder (2) eine FTS3-Tabelle verwenden (dies bedeutet jedoch, dass die Datenbank größer ist, da die Volltextspalte vorhanden ist in beiden Datenbanken).

Füllen Sie die Tabellen

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.')");

(Auch hier gibt es bessere Möglichkeiten zum Einfügen von Beilagen als mit execSQL. Ich verwende es nur wegen seiner Lesbarkeit.)

Wenn Sie jetzt versuchen würden, eine FTS-Abfrage durchzuführen fts_example_table, würden Sie keine Ergebnisse erhalten. Der Grund ist, dass das Ändern einer Tabelle die andere Tabelle nicht automatisch ändert. Sie müssen die FTS-Tabelle manuell aktualisieren:

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

(Dies docidist wie rowidbei einer regulären Tabelle.) Sie müssen sicherstellen, dass die FTS-Tabelle bei jeder Änderung (INSERT, DELETE, UPDATE) an der externen Inhaltstabelle aktualisiert wird (damit der Index aktualisiert werden kann). Dies kann umständlich werden. Wenn Sie nur eine vorab ausgefüllte Datenbank erstellen, können Sie dies tun

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

Dadurch wird die gesamte Tabelle neu erstellt. Dies kann jedoch langsam sein, sodass Sie es nicht nach jeder kleinen Änderung tun möchten. Sie würden dies tun, nachdem Sie alle Einfügungen in der externen Inhaltstabelle abgeschlossen haben. Wenn Sie die Datenbanken automatisch synchronisieren müssen, können Sie Trigger verwenden . Gehen Sie hierher und scrollen Sie ein wenig nach unten, um eine Wegbeschreibung zu finden.

Fragen Sie die Datenbanken ab

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

Dies ist das gleiche wie zuvor, außer dass Sie diesmal nur Zugriff auf text_column(und docid) haben. Was ist, wenn Sie Daten aus anderen Spalten in der externen Inhaltstabelle abrufen müssen? Da die docidder FTS-Tabelle mit der rowid(und in diesem Fall _id) der externen Inhaltstabelle übereinstimmt , können Sie einen Join verwenden. (Dank dieser Antwort für die Hilfe dabei.)

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);

Weiterführende Literatur

Sehen Sie sich diese Dokumente sorgfältig an, um weitere Möglichkeiten zur Verwendung virtueller FTS-Tabellen zu finden:

Zusätzliche Bemerkungen

  • Set-Operatoren (AND, OR, NOT) in SQLite FTS-Abfragen verfügen über die Standardabfragesyntax und die erweiterte Abfragesyntax . Leider unterstützt Android die Enhanced Query Syntax anscheinend nicht (siehe hier , hier , hier und hier ). Das bedeutet, dass das Mischen von AND und OR schwierig wird (was die Verwendung UNIONoder Überprüfung erfordert PRAGMA compile_options). Äußerst unglücklich. Bitte fügen Sie einen Kommentar hinzu, wenn es in diesem Bereich ein Update gibt.
Suragch
quelle
1
Wenn Sie die fts-Tabelle in der von Ihnen angegebenen Weise verwenden (Auswahl aus einer Nicht-fts-Tabelle, in der _id in einer von fts table match zurückgegebenen Dokument-ID enthalten ist), können Sie mit content = "" Speicherplatz sparen. . Dadurch wird der Volltextindex erstellt, ohne dass Inhalte dupliziert werden. Siehe Inhaltslose FTS4-Tabellen
Astyanaxas
Die FTS4-Inhaltsoption wurde nicht früher als in SQLite 3.7.9 ( sqlite.org/releaselog/3_7_11.html ) hinzugefügt. Dies bedeutet, dass sie vor der Android-API 16 nicht verfügbar ist. SQLiteDatabase löst einen Verwendungsversuch aus.
Knöchel
Wie erhalte ich durch diese Abfrage eine Übereinstimmung mit einem halben Wort?
Hitesh Danidhariya
@HiteshDanidhariya, führt dies nicht zu einer teilweisen Wortübereinstimmung? Entschuldigung, es ist eine Weile her, seit ich daran gearbeitet habe, aber ich dachte, es hat es bereits getan.
Suragch
@suragch Habe die Lösung. Musste nach dem searchString und Thanks "*" hinzufügen. Deine Antwort hat mir sehr geholfen. :)
Hitesh Danidhariya
3

Vergessen Sie nicht, wenn Sie Inhalte von verwenden, um die fts-Tabelle neu zu erstellen.

Ich mache dies mit einem Auslöser beim Aktualisieren, Einfügen, Löschen

James Kipling
quelle
INSERT INTO foo_fts VALUES("rebuild")
James Kipling