Esempio di ricerca a testo completo in Android


87

Faccio fatica a capire come utilizzare la ricerca full text (FTS) con Android. Ho letto la documentazione di SQLite sulle estensioni FTS3 e FTS4 . E so che è possibile farlo su Android . Tuttavia, ho difficoltà a trovare esempi che posso comprendere.

Il modello di database di base

Una tabella di database SQLite (denominata example_table) ha 4 colonne. Tuttavia, c'è solo una colonna (denominata text_column) che deve essere indicizzata per una ricerca full-text. Ogni riga di text_columncontiene testo di lunghezza variabile da 0 a 1000 parole. Il numero totale di righe è maggiore di 10.000.

  • Come allestireste il tavolo e / o il tavolo virtuale FTS?
  • Come eseguiresti una query FTS text_column?

Note aggiuntive:

  • Poiché è necessario indicizzare solo una colonna, utilizzare solo una tabella FTS (e rilasciarla example_table) sarebbe inefficiente per le query non FTS .
  • Per una tabella così grande, la memorizzazione di voci duplicate text_columnnella tabella FTS sarebbe indesiderabile. Questo post suggerisce di utilizzare una tabella di contenuti esterni .
  • Le tabelle di contenuto esterno utilizzano FTS4, ma FTS4 non è supportato prima dell'API 11 di Android . Una risposta può presupporre un'API> = 11, ma sarebbe utile commentare le opzioni per supportare le versioni precedenti.
  • La modifica dei dati nella tabella originale non aggiorna automaticamente la tabella FTS (e viceversa). Includere i trigger nella risposta non è necessario per questo esempio di base, ma sarebbe comunque utile.

3
Domanda ben documentata, sto contrastando il voto negativo arbitrario che hai ottenuto qui.
Mekap

Risposte:


117

La risposta più semplice

Sto usando il semplice sql di seguito in modo che tutto sia il più chiaro e leggibile possibile. Nel tuo progetto puoi utilizzare i metodi di convenienza di Android. L' dboggetto utilizzato di seguito è un'istanza di SQLiteDatabase .

Crea tabella FTS

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

Questo potrebbe andare nel onCreate()metodo della tua SQLiteOpenHelperclasse estesa .

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

Sarebbe meglio usare SQLiteDatabase # insert o istruzioni preparate rispetto a execSQL.

Query FTS Table

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

È inoltre possibile utilizzare il metodo di query SQLiteDatabase # . Nota la MATCHparola chiave.

Risposta più piena

La tabella virtuale FTS sopra ha un problema con esso. Ogni colonna è indicizzata, ma questo è uno spreco di spazio e risorse se alcune colonne non devono essere indicizzate. L'unica colonna che necessita di un indice FTS è probabilmente la text_column.

Per risolvere questo problema useremo una combinazione di una tabella normale e una tabella FTS virtuale. La tabella FTS conterrà l'indice ma nessuno dei dati effettivi dalla tabella normale. Invece avrà un collegamento al contenuto della tabella normale. Questa è chiamata tabella dei contenuti esterni .

inserisci qui la descrizione dell'immagine

Crea le tabelle

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

Notare che dobbiamo usare FTS4 per farlo invece di FTS3. FTS4 non è supportato in Android prima della versione dell'API 11. È possibile (1) fornire solo funzionalità di ricerca per API> = 11 o (2) utilizzare una tabella FTS3 (ma ciò significa che il database sarà più grande perché esiste la colonna di testo completo in entrambi i database).

Popolare le tabelle

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

(Di nuovo, ci sono modi migliori per fare gli inserti che con execSQL. Lo sto usando solo per la sua leggibilità.)

Se provassi a eseguire una query FTS ora su, fts_example_tablenon otterrai risultati. Il motivo è che la modifica di una tabella non cambia automaticamente l'altra tabella. Devi aggiornare manualmente la tabella FTS:

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

( docidÈ come rowidper una tabella normale.) Devi assicurarti di aggiornare la tabella FTS (in modo che possa aggiornare l'indice) ogni volta che fai una modifica (INSERT, DELETE, UPDATE) alla tabella del contenuto esterno. Questo può diventare complicato. Se stai solo creando un database precompilato, puoi farlo

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

che ricostruirà l'intero tavolo. Questo può essere lento, tuttavia, quindi non è qualcosa che vuoi fare dopo ogni piccolo cambiamento. Lo faresti dopo aver terminato tutti gli inserti nella tabella dei contenuti esterni. Se è necessario mantenere i database sincronizzati automaticamente, è possibile utilizzare i trigger . Vai qui e scorri un po 'verso il basso per trovare le indicazioni stradali.

Interroga i database

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

È lo stesso di prima, tranne che questa volta hai accesso solo a text_column(e docid). E se hai bisogno di ottenere dati da altre colonne nella tabella del contenuto esterno? Poiché la dociddella tabella FTS corrisponde a rowid(e in questo caso _id) della tabella del contenuto esterno, è possibile utilizzare un join. (Grazie a questa risposta per l'aiuto in questo.)

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

Ulteriori letture

Esamina attentamente questi documenti per vedere altri modi di utilizzare le tabelle virtuali FTS:

Note aggiuntive

  • Gli operatori di impostazione (AND, OR, NOT) nelle query SQLite FTS hanno la sintassi di query standard e la sintassi di query avanzata . Sfortunatamente, apparentemente Android non supporta la sintassi delle query avanzata (vedi qui , qui , qui e qui ). Ciò significa che mescolare AND e OR diventa difficile (richiedendo l'uso UNIONo il controllo PRAGMA compile_options). Davvero sfortunato. Per favore aggiungi un commento se c'è un aggiornamento in quest'area.

1
Infatti, se stai usando la tabella fts nel modo in cui hai specificato (selezionando dalla tabella non-fts dove _id è contenuto nel set di docid restituito dalla corrispondenza della tabella fts), potresti risparmiare spazio usando content = "" . Questo creerà l'indice full-text senza duplicare il contenuto. Vedi le tabelle Contentless
FTS4

L'opzione di contenuto FTS4 è stata aggiunta non prima di SQLite 3.7.9 ( sqlite.org/releaselog/3_7_11.html ), il che significa che non è disponibile prima dell'API di Android 16. SQLiteDatabase verrà lanciato al tentativo di utilizzo.
Knuckles

Come ottengo una corrispondenza di mezza parola tramite questa query?
Hitesh Danidhariya

@HiteshDanidhariya, non è una corrispondenza parziale delle parole? Scusa, è passato un po 'di tempo dall'ultima volta che ci ho lavorato, ma pensavo che lo facesse già.
Suragch

@suragch Ho trovato la soluzione. Ho dovuto aggiungere "*" dopo la stringa di ricerca e grazie. La tua risposta mi ha aiutato molto. :)
Hitesh Danidhariya

3

Non dimenticare quando usi il contenuto di per ricostruire la tabella fts.

Lo faccio con un trigger su aggiornamento, inserimento, eliminazione


INSERT INTO foo_fts VALUES("rebuild")
James Kipling
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.