Exemple de recherche en texte intégral sous Android

87

J'ai du mal à comprendre comment utiliser la recherche en texte intégral (FTS) avec Android. J'ai lu la documentation SQLite sur les extensions FTS3 et FTS4 . Et je sais que c'est possible de le faire sur Android . Cependant, j'ai du mal à trouver des exemples que je peux comprendre.

Le modèle de base de données de base

Une table de base de données SQLite (nommée example_table) comporte 4 colonnes. Cependant, une seule colonne (nommée text_column) doit être indexée pour une recherche en texte intégral. Chaque ligne de text_columncontient du texte dont la longueur varie de 0 à 1000 mots. Le nombre total de lignes est supérieur à 10 000.

  • Comment configureriez-vous la table et / ou la table virtuelle FTS?
  • Comment exécuteriez-vous une requête FTS text_column?

Notes complémentaires:

  • Étant donné qu'une seule colonne doit être indexée, seule l'utilisation d'une table FTS (et la suppression example_table) serait inefficace pour les requêtes non FTS .
  • Pour une table aussi grande, le stockage d'entrées en double text_columndans la table FTS ne serait pas souhaitable. Cet article suggère d'utiliser une table de contenu externe .
  • Les tables de contenu externes utilisent FTS4, mais FTS4 n'est pas pris en charge avant l'API Android 11 . Une réponse peut supposer une API> = 11, mais commenter les options de prise en charge des versions inférieures serait utile.
  • La modification des données dans la table d'origine ne met pas automatiquement à jour la table FTS (et vice versa). L'inclusion de déclencheurs dans votre réponse n'est pas nécessaire pour cet exemple de base, mais serait néanmoins utile.
Suragch
la source
3
Question bien documentée, je m'oppose au vote négatif arbitraire que vous avez obtenu ici.
Mekap

Réponses:

117

Réponse la plus basique

J'utilise le SQL clair ci-dessous pour que tout soit aussi clair et lisible que possible. Dans votre projet, vous pouvez utiliser les méthodes pratiques Android. L' dbobjet utilisé ci-dessous est une instance de SQLiteDatabase .

Créer une table FTS

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

Cela pourrait aller dans la onCreate()méthode de votre SQLiteOpenHelperclasse étendue .

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

Il serait préférable d'utiliser des instructions SQLiteDatabase # insert ou préparées que execSQL.

Table FTS de requête

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

Vous pouvez également utiliser la méthode de requête SQLiteDatabase # . Notez le MATCHmot - clé.

Réponse plus complète

La table FTS virtuelle ci-dessus présente un problème. Chaque colonne est indexée, mais c'est un gaspillage d'espace et de ressources si certaines colonnes n'ont pas besoin d'être indexées. La seule colonne qui nécessite un index FTS est probablement le text_column.

Pour résoudre ce problème, nous utiliserons une combinaison d'une table régulière et d'une table FTS virtuelle. La table FTS contiendra l'index mais aucune des données réelles de la table régulière. Au lieu de cela, il aura un lien vers le contenu de la table régulière. C'est ce qu'on appelle une table de contenu externe .

entrez la description de l'image ici

Créer les tableaux

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

Notez que nous devons utiliser FTS4 pour ce faire plutôt que FTS3. FTS4 n'est pas pris en charge dans Android avant la version 11 de l'API. Vous pouvez soit (1) fournir uniquement une fonctionnalité de recherche pour API> = 11, ou (2) utiliser une table FTS3 (mais cela signifie que la base de données sera plus grande car la colonne de texte intégral existe dans les deux bases de données).

Remplir les tableaux

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

(Encore une fois, il existe de meilleures façons de faire des insertions qu'avec execSQL. Je ne l'utilise que pour sa lisibilité.)

Si vous essayez de faire une requête FTS maintenant, fts_example_tablevous n'obtiendrez aucun résultat. La raison en est que la modification d'une table ne modifie pas automatiquement l'autre table. Vous devez mettre à jour manuellement la table FTS:

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

( Cela docidressemble rowidà une table normale.) Vous devez vous assurer de mettre à jour la table FTS (afin qu'elle puisse mettre à jour l'index) chaque fois que vous apportez une modification (INSERT, DELETE, UPDATE) à la table de contenu externe. Cela peut devenir fastidieux. Si vous créez uniquement une base de données préremplie, vous pouvez faire

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

qui reconstruira toute la table. Cela peut être lent, cependant, ce n'est donc pas quelque chose que vous voulez faire après chaque petit changement. Vous le feriez après avoir terminé toutes les insertions sur la table de contenu externe. Si vous avez besoin de synchroniser automatiquement les bases de données, vous pouvez utiliser des déclencheurs . Allez ici et faites défiler un peu pour trouver des directions.

Interroger les bases de données

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

C'est la même chose qu'avant, sauf que cette fois, vous n'avez accès qu'à text_column(et docid). Que faire si vous avez besoin d'obtenir des données à partir d'autres colonnes de la table de contenu externe? Puisque le docidde la table FTS correspond au rowid(et dans ce cas _id) de la table de contenu externe, vous pouvez utiliser une jointure. (Merci à cette réponse pour votre aide.)

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

Lectures complémentaires

Parcourez attentivement ces documents pour voir d'autres façons d'utiliser les tables virtuelles FTS:

Notes complémentaires

  • Les opérateurs de définition (AND, OR, NOT) dans les requêtes SQLite FTS ont la syntaxe de requête standard et la syntaxe de requête améliorée . Malheureusement, Android ne prend apparemment pas en charge la syntaxe de requête améliorée (voir ici , ici , ici et ici ). Cela signifie que mélanger ET et OU devient difficile (nécessitant l'utilisation UNIONou la vérification PRAGMA compile_optionssemble-t-il). Très malheureux. Veuillez ajouter un commentaire s'il y a une mise à jour dans ce domaine.
Suragch
la source
1
En fait, si vous utilisez la table fts de la manière que vous avez spécifiée (en sélectionnant dans la table non-fts où _id est contenu dans l'ensemble de docid renvoyé par fts table match), vous pouvez économiser de l'espace en utilisant content = "" . Cela créera l'index de texte intégral sans dupliquer le contenu. Voir les tableaux FTS4 sans contenu
astyanaxas
L'option de contenu FTS4 a été ajoutée pas plus tôt que dans SQLite 3.7.9 ( sqlite.org/releaselog/3_7_11.html ), ce qui signifie qu'elle n'est pas disponible avant l'API Android 16. SQLiteDatabase lancera une tentative d'utilisation.
Knuckles
Comment obtenir une correspondance d'un demi-mot via cette requête?
Hitesh Danidhariya
@HiteshDanidhariya, cela ne fait-il pas une correspondance partielle des mots? Désolé, cela fait un moment que je n'ai pas travaillé dessus, mais je pensais que c'était déjà fait.
Suragch
@suragch Vous avez la solution. J'ai dû ajouter "*" après la chaîne de recherche et merci. Votre réponse m'a beaucoup aidé. :)
Hitesh Danidhariya
3

N'oubliez pas lorsque vous utilisez le contenu de pour reconstruire la table fts.

Je fais cela avec un déclencheur sur la mise à jour, l'insertion, la suppression

James Kipling
la source
INSERT INTO foo_fts VALUES("rebuild")
James Kipling