2015-04-23 18 views
69

Ho difficoltà a capire come utilizzare la ricerca full-text (FTS) con Android. Ho letto il SQLite documentation on the FTS3 and FTS4 extensions. E so it's possible to do on Android. Tuttavia, sto avendo difficoltà a trovare esempi che io possa comprendere.Esempio di ricerca testo completo in Android

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 di testo completo. Ogni riga di text_column contiene un testo di lunghezza variabile da 0 a 1000 parole. Il numero totale di righe è maggiore di 10.000.

  • Come impostare la tabella e/o il tavolo virtuale FTS?
  • Come si esegue una query FTS su text_column?

Note aggiuntive:

  • Perché solo una colonna deve essere indicizzato, solo utilizzando una tabella FTS (e rilasciando example_table) sarebbe inefficient for non-FTS queries.
  • Per una tabella così grande, la memorizzazione di voci duplicate di text_column nella tabella FTS non sarebbe auspicabile. This post suggerisce di utilizzare un external content table.
  • Le tabelle di contenuto esterno utilizzano FTS4, ma FTS4 è not supported before Android API 11. Una risposta può assumere un'API> = 11, ma sarebbe utile fornire commenti sulle opzioni per supportare versioni inferiori.
  • La modifica dei dati nella tabella originale non aggiorna automaticamente la tabella FTS (e viceversa). Includere triggers nella risposta non è necessario per questo esempio di base, ma sarebbe comunque utile.
+2

domanda ben documentato, sto contrastare la downvote arbitraria sei arrivato qui. – Mekap

risposta

92

maggior risposta di base

Sto utilizzando SQL pianura sottostante in modo che tutto sia più chiaro e leggibile possibile. Nel tuo progetto puoi utilizzare i metodi di convenienza Android. L'oggetto db utilizzato di seguito è un'istanza di SQLiteDatabase.

Create FTS Table

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

Questo potrebbe andare nel metodo del vostro esteso SQLiteOpenHelper classe onCreate().

Populate FTS Table

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 prepared statements di execSQL.

Query FTS Table

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

si potrebbe anche utilizzare il metodo SQLiteDatabase#query. Nota la parola chiave MATCH.

Fuller Risposta

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

Per risolvere questo problema verrà utilizzata 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 link al contenuto della tabella normale. Questo è chiamato external content table.

enter image description here

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

Nota che dobbiamo usare FTS4 per fare questo, piuttosto che FTS3. FTS4 non è supportato in Android prima dell'API versione 11. Potresti (1) solo fornire funzionalità di ricerca per API> = 11 o (2) utilizzare una tabella FTS3 (ma questo significa che il database sarà più grande perché la colonna di testo completo esiste 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.')"); 

(Anche in questo caso, ci sono modi migliori di fare inserti che con execSQL. Sto solo usando per la sua leggibilità.)

Se si è tentato di fare un La query FTS ora su fts_example_table non otterrebbe risultati. Il motivo è che cambiando una tabella non cambia automaticamente l'altra tabella. È necessario aggiornare manualmente la tabella FTS:

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

(. Il docid è come il rowid per una tabella regolare) Bisogna fare in modo di aggiornare la tabella FTS (in modo che possa aggiornare l'indice) ogni volta si apporta una modifica (INSERT, DELETE, UPDATE) alla tabella dei contenuti esterni. Questo può diventare ingombrante. Se si sta solo creando un database prepopolato, è possibile effettuare

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

che ricostruirà l'intera tabella. Questo può essere lento, quindi, non è qualcosa che vuoi fare dopo ogni piccolo cambiamento. Lo faresti dopo aver terminato tutti gli inserimenti nella tabella dei contenuti esterni. Se è necessario mantenere i database sincronizzati automaticamente, è possibile utilizzare triggers. Go here e scorri un po 'per trovare le direzioni.

query dei database

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

Questa è la stessa di prima, ma questa volta si ha accesso solo a text_column (e docid). Che cosa succede se è necessario ottenere dati da altre colonne nella tabella dei contenuti esterni? Poiché lo docid della tabella FTS corrisponde allo rowid (e in questo caso _id) della tabella del contenuto esterno, è possibile utilizzare un join. (Grazie a this answer per l'aiuto.)

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

Letture

passare attraverso questi documenti con attenzione per vedere altri modi di utilizzare FTS tavoli virtuali:

Note aggiuntive

  • Gli operatori di set (AND, OR, NOT) nelle query FTS di SQLite hanno Standard Query Syntax e Enhanced Query Syntax. Sfortunatamente, Android non supporta la sintassi avanzata delle query (vedi here, here, here e here). Ciò significa che la combinazione di AND e OR diventa difficile (richiede l'uso di UNION o il controllo di PRAGMA compile_options sembra). Davvero sfortunato. Si prega di aggiungere 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 in cui _id è contenuto nel set di docid restituito dalla tabella fts), puoi risparmiare spazio usando content = " ". Questo creerà l'indice full-text senza duplicare il contenuto. Vedi [Contentless FTS4 Tables] (http://www.sqlite.org/fts3.html#section_6_2_1) – astyanaxas

+0

L'opzione di contenuto FTS4 è stata aggiunta non prima di in SQLite 3.7.9 (http://www.sqlite.org/releaselog/ 3_7_11.html), il che significa che non è disponibile prima dell'API Android 16. SQLiteDatabase genererà un tentativo di utilizzo. – Knuckles

1

Non dimenticare quando si utilizza il contenuto da ricostruire la tabella fts.

Lo faccio con un trigger su aggiornamento, inserire, cancellare

+1

Ulteriori dettagli con il codice sarebbero utili. – Suragch

+0

'INSERISCI IN foo_fts VALUES (" rebuild ")' –