Exemplo de pesquisa de texto completo no Android

87

Estou tendo dificuldade em entender como usar a pesquisa de texto completo (FTS) com o Android. Eu li a documentação do SQLite sobre as extensões FTS3 e FTS4 . E eu sei que é possível fazer no Android . No entanto, estou tendo dificuldade em encontrar exemplos que possa compreender.

O modelo básico de banco de dados

Uma tabela de banco de dados SQLite (nomeada example_table) possui 4 colunas. No entanto, há apenas uma coluna (nomeada text_column) que precisa ser indexada para uma pesquisa de texto completo. Cada linha de text_columncontém texto que varia em comprimento de 0 a 1000 palavras. O número total de linhas é maior que 10.000.

  • Como você configuraria a mesa e / ou a mesa virtual FTS?
  • Como você executaria uma consulta FTS no text_column?

Notas Adicionais:

  • Como apenas uma coluna precisa ser indexada, apenas usar uma tabela FTS (e descartá-la example_table) seria ineficiente para consultas não FTS .
  • Para uma tabela tão grande, o armazenamento de entradas duplicadas text_columnna tabela FTS seria indesejável. Esta postagem sugere o uso de uma tabela de conteúdo externo .
  • As tabelas de conteúdo externo usam FTS4, mas FTS4 não é compatível antes da API 11 do Android . Uma resposta pode assumir uma API> = 11, mas comentar sobre as opções de suporte a versões anteriores pode ser útil.
  • Alterar dados na tabela original não atualiza automaticamente a tabela FTS (e vice-versa). Incluir acionadores em sua resposta não é necessário para este exemplo básico, mas seria útil mesmo assim.
Suragch
fonte
3
Pergunta bem documentada, estou contrariando o voto negativo arbitrário que você obteve aqui.
Mekap

Respostas:

117

Resposta mais básica

Estou usando o sql simples abaixo para que tudo seja o mais claro e legível possível. Em seu projeto, você pode usar os métodos de conveniência Android. O dbobjeto usado a seguir é uma instância de SQLiteDatabase .

Criar Tabela FTS

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

Isso poderia ir no onCreate()método de sua SQLiteOpenHelperclasse estendida .

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

Seria melhor usar SQLiteDatabase # insert ou instruções preparadas do que execSQL.

Consultar Tabela FTS

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

Você também pode usar o método de consulta SQLiteDatabase # . Observe a MATCHpalavra - chave.

Resposta mais completa

A tabela FTS virtual acima tem um problema com isso. Cada coluna é indexada, mas isso é um desperdício de espaço e recursos se algumas colunas não precisam ser indexadas. A única coluna que precisa de um índice FTS é provavelmente o text_column.

Para resolver este problema, usaremos uma combinação de uma mesa regular e uma mesa FTS virtual. A tabela FTS conterá o índice, mas nenhum dos dados reais da tabela regular. Em vez disso, ele terá um link para o conteúdo da tabela regular. Isso é chamado de tabela de conteúdo externo .

insira a descrição da imagem aqui

Crie as tabelas

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

Observe que temos que usar FTS4 para fazer isso em vez de FTS3. FTS4 não é compatível com Android antes da API versão 11. Você poderia (1) fornecer funcionalidade de pesquisa apenas para API> = 11 ou (2) usar uma tabela FTS3 (mas isso significa que o banco de dados será maior porque a coluna de texto completo existe em ambas as bases de dados).

Preencher as tabelas

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

(Novamente, há maneiras melhores de fazer inserções do que com execSQL. Estou apenas usando para sua legibilidade.)

Se você tentasse fazer uma consulta FTS agora fts_example_table, não obteria resultados. O motivo é que alterar uma tabela não altera automaticamente a outra. Você deve atualizar manualmente a tabela FTS:

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

(O docidé como rowidpara uma tabela regular.) Você deve certificar-se de atualizar a tabela FTS (para que ela possa atualizar o índice) toda vez que fizer uma alteração (INSERT, DELETE, UPDATE) na tabela de conteúdo externo. Isso pode ser complicado. Se você está fazendo apenas um banco de dados pré-preenchido, você pode fazer

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

que irá reconstruir toda a tabela. Isso pode ser lento, portanto, não é algo que você queira fazer após cada pequena mudança. Você faria isso depois de terminar todas as inserções na tabela de conteúdo externo. Se você precisa manter os bancos de dados em sincronia automaticamente, você pode usar gatilhos . Vá aqui e role um pouco para baixo para encontrar as direções.

Consultar os bancos de dados

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

Este é o mesmo de antes, exceto que desta vez você só tem acesso a text_column(e docid). E se você precisar obter dados de outras colunas na tabela de conteúdo externo? Visto que o docidda tabela FTS corresponde ao rowid(e neste caso _id) da tabela de conteúdo externo, você pode usar uma junção. (Obrigado a esta resposta pela ajuda com isso.)

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

Leitura Adicional

Leia estes documentos cuidadosamente para ver outras maneiras de usar tabelas virtuais FTS:

Notas Adicionais

Suragch
fonte
1
Na verdade, se você estiver usando a tabela fts da maneira especificada (selecionando a partir de uma tabela não-fts, onde _id está contido no conjunto de docid retornado por fts table match), você pode economizar espaço usando content = "" . Isso criará o índice de texto completo sem duplicar o conteúdo. Ver Tabelas FTS4 sem conteúdo
astyanaxas
A opção de conteúdo FTS4 não foi adicionada antes do SQLite 3.7.9 ( sqlite.org/releaselog/3_7_11.html ), o que significa que não está disponível antes da API do Android 16. SQLiteDatabase lançará na tentativa de uso.
Knuckles
Como faço para obter uma correspondência de meia palavra, por meio desta consulta?
Hitesh Danidhariya
@HiteshDanidhariya, isso não faz correspondência parcial de palavras? Desculpe, já faz um tempo que não trabalho nisso, mas achei que já tivesse resolvido.
Suragch
@suragch Conseguiu a solução. Tive de adicionar "*" após a string de pesquisa e Obrigado. Sua resposta me ajudou muito. :)
Hitesh Danidhariya
3

Não se esqueça de quando usar o conteúdo de para reconstruir a mesa fts.

Eu faço isso com um gatilho em atualizar, inserir, excluir

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