Ejemplo de búsqueda de texto completo en Android

87

Me cuesta entender cómo usar la búsqueda de texto completo (FTS) con Android. He leído la documentación de SQLite sobre las extensiones FTS3 y FTS4 . Y sé que es posible hacerlo en Android . Sin embargo, me está costando encontrar ejemplos que pueda comprender.

El modelo de base de datos básico

Una tabla de base de datos SQLite (nombrada example_table) tiene 4 columnas. Sin embargo, solo hay una columna (nombrada text_column) que debe indexarse ​​para una búsqueda de texto completo. Cada fila de text_columncontiene texto que varía en longitud de 0 a 1000 palabras. El número total de filas es superior a 10.000.

  • ¿Cómo configurarías la mesa y / o la mesa virtual FTS?
  • ¿Cómo realizaría una consulta FTS text_column?

Notas adicionales:

  • Debido a que solo se debe indexar una columna, solo usar una tabla FTS (y eliminar example_table) sería ineficaz para las consultas que no sean FTS .
  • Para una tabla tan grande, text_columnno sería deseable almacenar entradas duplicadas de en la tabla FTS. Esta publicación sugiere usar una tabla de contenido externa .
  • Las tablas de contenido externo usan FTS4, pero FTS4 no es compatible antes de la API 11 de Android . Una respuesta puede asumir una API> = 11, pero sería útil comentar las opciones para admitir versiones inferiores.
  • El cambio de datos en la tabla original no actualiza automáticamente la tabla FTS (y viceversa). Incluir desencadenantes en su respuesta no es necesario para este ejemplo básico, pero de todos modos sería útil.
Suragch
fuente
3
Pregunta bien documentada, estoy contrarrestando el voto negativo arbitrario que obtuvo aquí.
Mekap

Respuestas:

117

Respuesta más básica

Estoy usando el sql simple a continuación para que todo sea lo más claro y legible posible. En su proyecto puede utilizar los métodos de conveniencia de Android. El dbobjeto que se utiliza a continuación es una instancia de SQLiteDatabase .

Crear tabla FTS

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

Esto podría ir en el onCreate()método de su SQLiteOpenHelperclase extendida .

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

Sería mejor usar SQLiteDatabase # insert o declaraciones preparadas que execSQL.

Consultar tabla FTS

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

También puede utilizar el método de consulta SQLiteDatabase # . Tenga en cuenta la MATCHpalabra clave.

Respuesta más completa

La tabla FTS virtual anterior tiene un problema. Cada columna está indexada, pero esto es una pérdida de espacio y recursos si algunas columnas no necesitan indexarse. La única columna que necesita un índice FTS es probablemente text_column.

Para resolver este problema usaremos una combinación de una tabla regular y una tabla FTS virtual. La tabla FTS contendrá el índice, pero ninguno de los datos reales de la tabla normal. En su lugar, tendrá un enlace al contenido de la tabla normal. Esto se denomina tabla de contenido externo .

ingrese la descripción de la imagen aquí

Crea las tablas

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

Tenga en cuenta que tenemos que usar FTS4 para hacer esto en lugar de FTS3. FTS4 no es compatible con Android antes de la versión 11 de API. Puede (1) proporcionar solo la funcionalidad de búsqueda para API> = 11, o (2) usar una tabla FTS3 (pero esto significa que la base de datos será más grande porque existe la columna de texto completo en ambas bases de datos).

Rellenar las tablas

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

(Nuevamente, hay mejores formas de hacer inserciones que con execSQL. Solo lo estoy usando por su legibilidad).

Si intentara hacer una consulta FTS ahora fts_example_table, no obtendría resultados. La razón es que cambiar una mesa no cambia automáticamente la otra mesa. Tienes que actualizar manualmente la tabla FTS:

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

( docidEs como rowidpara una tabla normal). Debe asegurarse de actualizar la tabla FTS (para que pueda actualizar el índice) cada vez que realice un cambio (INSERT, DELETE, UPDATE) en la tabla de contenido externo. Esto puede resultar engorroso. Si solo está creando una base de datos rellenada previamente, puede hacer

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

que reconstruirá toda la tabla. Sin embargo, esto puede ser lento, por lo que no es algo que desee hacer después de cada pequeño cambio. Lo haría después de terminar todas las inserciones en la tabla de contenido externo. Si necesita mantener las bases de datos sincronizadas automáticamente, puede usar activadores . Vaya aquí y desplácese un poco hacia abajo para encontrar direcciones.

Consultar las bases de datos

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

Esto es lo mismo que antes, excepto que esta vez solo tiene acceso a text_column(y docid). ¿Qué sucede si necesita obtener datos de otras columnas en la tabla de contenido externo? Dado que el docidde la tabla FTS coincide con rowid(y en este caso _id) de la tabla de contenido externo, puede usar una combinación. (Gracias a esta respuesta por ayudar con eso).

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

Otras lecturas

Revise estos documentos detenidamente para ver otras formas de usar tablas virtuales FTS:

Notas adicionales

Suragch
fuente
1
De hecho, si está utilizando la tabla fts de la forma que especificó (seleccionando de la tabla que no es fts donde _id está contenido en el conjunto de docid devuelto por la coincidencia de la tabla fts), puede ahorrar espacio usando content = "" . Esto creará el índice de texto completo sin duplicar contenido. Ver tablas FTS4 sin contenido
astyanaxas
La opción de contenido FTS4 se agregó no antes que en SQLite 3.7.9 ( sqlite.org/releaselog/3_7_11.html ), lo que significa que no está disponible antes de la API 16 de Android. SQLiteDatabase arrojará un intento de uso.
Knuckles
¿Cómo obtengo una coincidencia de media palabra a través de esta consulta?
Hitesh Danidhariya
@HiteshDanidhariya, ¿esto no hace coincidir parcialmente las palabras? Lo siento, ha pasado un tiempo desde que trabajé en esto, pero pensé que ya lo había hecho.
Suragch
@suragch Obtuve la solución. Tuve que agregar "*" después de searchString y Thanks.Tu respuesta me ayudó mucho. :)
Hitesh Danidhariya
3

No olvide utilizar el contenido de para reconstruir la tabla fts.

Hago esto con un disparador al actualizar, insertar, eliminar

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