如何使用FTS4在SQLite Android上修复错误“函数rank的参数个数错误”?

问题描述

我有两个表:

  • persons
  • persons_fts

这是表格的定义:

CREATE TABLE persons(name TEXT PRIMARY KEY NOT NULL,details TEXT);

CREATE VIRTUAL TABLE persons_fts USING FTS4(name TEXT NOT NULL,details TEXT,context=persons);

我想对persons_fts表上的查询进行全文搜索,然后根据相关性对结果进行排名。在查看了official docs的操作方法后,我以以下查询结束:

SELECT *
FROM persons
JOIN persons_fts ON persons.name = persons_fts.name
WHERE persons_fts MATCH :query
ORDER BY rank(matchinfo(persons_fts)) DESC;

除了额外的联接外,该查询与官方文档中列出的查询完全相同。但是,当我尝试执行它时出现错误

从表中检索数据时出错:函数rank()的参数数量错误代码1 sqlITE_ERROR)

我在做什么错了?

请注意,对于我来说,不能使用FTS5。

解决方法

问题中链接的SQLite文档阐明了response函数在其所用查询上方的注释中的作用:

如果应用程序提供了一个称为“ rank”的SQLite用户函数,该函数可解释matchinfo返回的数据块并基于其返回数字相关性,则可以使用以下SQL来返回10个最相关文档的标题在数据集中进行用户查询。

void wordCount(std::string wordFile) { std::map<std::string,int> M; std::string word = ""; for (int i = 0; i < str.size(); i++) { if (str[i] == ' ') { if (M.find(word) == M.end()) { M.insert(make_pair(word,1)); word = ""; } else { M[word]++; word = ""; } } else word += str[i]; } if (M.find(word) == M.end()) M.insert(make_pair(word,1)); else M[word]++; for (auto &it : M) { std::cout << it.first << ": Occurs " << it.second << std::endl; } } 应该是用户提供的功能。它不随SQLite一起提供。

这是Kotlin中rank函数的实现,该函数根据rank提供的数据,使用默认的“ pcx”参数计算相关性得分:

rank

要了解此代码的工作原理,请仔细阅读官方文档中提供的rankfunc example

由于我们的等级函数是Kotlin函数,因此SQLite不能直接使用它。相反,我们需要首先从数据库中检索matchinfo blob,然后将其传递给我们的rank函数。

以下是如何使用Room进行操作的示例:

fun rank(matchInfo: IntArray): Double {
  val numPhrases = matchInfo[0]
  val numColumns = matchInfo[1]

  var score = 0.0
  for (phrase in 0 until numPhrases) {
    val offset = 2 + phrase * numColumns * 3
    for (column in 0 until numColumns) {
      val numHitsInRow = matchInfo[offset + 3 * column]
      val numHitsInAllRows = matchInfo[offset + 3 * column + 1]
      if (numHitsInAllRows > 0) {
        score += numHitsInRow.toDouble() / numHitsInAllRows.toDouble()
      }
    }
  }

  return score
}

检索到的matchinfo包含代表匹配信息的数字序列,其中每个数字均由4个字节表示。第一个字节是实际值,接下来的三个字节为零。因此,我们需要在将此ByteArray传递到@Dao interface PersonsDao { @Query(""" SELECT *,matchinfo(persons_fts,'pcx') as mi FROM persons JOIN persons_fts ON persons.name = persons_fts.name WHERE persons_fts MATCH :query """) suspend fun search(query: String): List<PersonWithMatchInfo> } data class PersonWithMatchInfo( @Embedded val person: Person @ColumnInfo(name = "mi") val matchInfo: ByteArray ) 之前删除冗余零。这可以通过一种简单的方法完成:

ByteArray

此设置可以像这样使用:

rank