SQLite FTS3 和 FTS4 扩展(三十二)

返回:SQLite---系列文章目录

上一篇:SQLite 的命令行 Shell(三十一)

下一篇:SQLite---系列文章目录

概述

FTS3 和 FTS4 是 SQLite 虚拟表模块,允许用户执行 对一组文档进行全文搜索。最常见(和最有效) 描述全文搜索的方式是"Google、Yahoo 和 Bing 所做的 文件放在万维网上"。用户输入术语或系列 的项,可能由二元运算符连接或组合成 短语,全文查询系统会找到最能 考虑到用户拥有的运算符和分组,匹配这些术语 指定。本文介绍 FTS3 和 FTS4 的部署和使用。

FTS1 和 FTS2 是 SQLite 的过时全文搜索模块。有已知的 应避免这些较旧模块及其使用的问题。 原始 FTS3 代码的一部分被贡献给了 SQLite 项目 作者:谷歌的斯科特·赫斯(Scott Hess)。现在是 作为SQLite的一部分进行开发和维护。

1. FTS3 和 FTS4 简介

FTS3 和 FTS4 扩展模块允许用户创建具有 内置全文索引(以下简称"FTS表")。全文索引 允许用户有效地查询数据库中包含 一个或多个单词(以下称为"标记"),即使表 包含许多大型文档。

例如,如果 517430 文档中的每个文档 "Enron E-Mail 数据集" 同时插入到 FTS 表和普通 SQLite 表中 使用以下 SQL 脚本创建:

sql 复制代码
CREATE VIRTUAL TABLE enrondata1 USING fts3(content TEXT);     /* FTS3 table */
CREATE TABLE enrondata2(content TEXT);                        /* Ordinary table */

然后,可以执行以下两个查询中的任何一个来查找 数据库中包含单词"Linux"的文档 (351)。使用一个 台式 PC 硬件配置,FTS3 表上的查询返回 大约 0.03 秒,而查询普通表则为 22.5 秒。

sql 复制代码
SELECT count(*) FROM enrondata1 WHERE content MATCH 'linux';  /* 0.03 seconds */
SELECT count(*) FROM enrondata2 WHERE content LIKE '%linux%'; /* 22.5 seconds */

当然,上面的两个查询并不完全等同。例如 LIKE 查询匹配包含"linuxophobe"等术语的行 或"EnterpriseLinux"(碰巧的是,Enron E-Mail Dataset 没有 实际上包含任何此类术语),而 FTS3 表上的 MATCH 查询 仅选择包含"Linux"作为离散令牌的行。双 搜索不区分大小写。FTS3 表在 磁盘,而普通表只有 1453 MB。使用相同的 用于执行上述 SELECT 查询的硬件配置,FTS3 表格只需不到 31 分钟即可填充,而普通表格则需要 25 分钟 桌子。

1.1. FTS3 和 FTS4 之间的区别

FTS3 和 FTS4 几乎相同。他们共享大部分代码, 并且它们的接口是相同的。区别在于:

  • FTS4 包含查询性能优化,这些优化可能显著 改进包含以下术语的全文查询的性能 很常见(存在于很大比例的表行中)。

  • FTS4 支持一些可能与 matchinfo() 函数一起使用的附加选项。

  • 因为它在磁盘上存储了两个新的影子表中的额外信息,以支持性能 优化和额外的 matchinfo() 选项,FTS4 表可能会消耗更多 磁盘空间比使用 FTS3 创建的等效表。通常是开销 是 1-2% 或更少,但如果文档存储在 FTS 表非常小。开销可以通过指定 指令"matchinfo=fts3"作为 FTS4 表的一部分 声明,但这是以牺牲一些 额外支持的 matchinfo() 选项。

  • FTS4 提供钩子(压缩和解压缩选项),允许将数据存储在压缩的 表单,减少磁盘使用量和 IO。

FTS4 是对 FTS3 的增强。 FTS3 自 SQLite 版本 3.5.0 (2007-09-04) 起可用 SQLite 版本 3.7.4 (2010-12-07) 添加了 FTS4 的增强功能。

您应该在应用中使用哪个模块,FTS3 或 FTS4?FTS4 是 有时比 FTS3 快得多,甚至快几个数量级 取决于查询,尽管在常见情况下,两者的性能 模块是类似的。FTS4 还提供增强的 matchinfo() 输出,这些输出 可用于对 MATCH 操作的结果进行排名。在 另一方面,在没有 matchinfo=fts3 指令的情况下,FTS4 需要一点 磁盘空间比 FTS3 多,但在大多数情况下只有 2 的百分比。

对于较新的应用,建议使用 FTS4;虽然如果与旧的兼容性 SQLite 的版本很重要,那么 FTS3 通常也可以使用。

1.2. 创建和销毁 FTS 表

与其他虚拟表类型一样,新的 FTS 表是使用 CREATE VIRTUAL TABLE 语句创建的。模块名称,如下所示 USING 关键字是"fts3"或"fts4"。虚拟表模块参数可以 留空,在这种情况下,具有单个用户定义的 FTS 表 创建名为"content"的列。或者,模块参数 可以传递逗号分隔的列名称列表。

如果为 FTS 表显式提供列名作为 CREATE VIRTUAL TABLE 语句,则数据类型名称可以是可选的 为每列指定。这是纯句法糖, FTS 或 SQLite 核心不将提供的类型名用于任何 目的。这同样适用于与 FTS 列名称 - 它们被解析,但未被系统使用或记录 以任何方式。

sql 复制代码
-- Create an FTS table named "data" with one column - "content":
CREATE VIRTUAL TABLE data USING fts3();

-- Create an FTS table named "pages" with three columns:
CREATE VIRTUAL TABLE pages USING fts4(title, keywords, body);

-- Create an FTS table named "mail" with two columns. Datatypes
-- and column constraints are specified along with each column. These
-- are completely ignored by FTS and SQLite. 
CREATE VIRTUAL TABLE mail USING fts3(
  subject VARCHAR(256) NOT NULL,
  body TEXT CHECK(length(body)<10240)
);

除了列列表外,模块参数还传递给 CREATE 用于创建 FTS 表的 VIRTUAL TABLE 语句可用于指定 分词器。这是通过指定格式的字符串来完成的 "tokenize=<tokenizer name> <tokenizer args>"代替列 name,其中 <tokenizer name> 是要使用的分词器的名称,并且 <tokenizer args> 是空格分隔限定符的可选列表 传递给 Tokenizer 实现。分词器规范可以是 放置在列列表中的任何位置,但最多一个分词器声明是 允许用于每个 CREATE VIRTUAL TABLE 语句。请参阅下面的内容 使用(并在必要时实现)分词器的详细说明。

sql 复制代码
-- Create an FTS table named "papers" with two columns that uses
-- the tokenizer "porter".
CREATE VIRTUAL TABLE papers USING fts3(author, document, tokenize=porter);

-- Create an FTS table with a single column - "content" - that uses
-- the "simple" tokenizer.
CREATE VIRTUAL TABLE data USING fts4(tokenize=simple);

-- Create an FTS table with two columns that uses the "icu" tokenizer.
-- The qualifier "en_AU" is passed to the tokenizer implementation
CREATE VIRTUAL TABLE names USING fts3(a, b, tokenize=icu en_AU);

可以使用普通的 DROP TABLE 语句从数据库中删除 FTS 表。例如:

复制代码
-- Create, then immediately drop, an FTS4 table.
CREATE VIRTUAL TABLE data USING fts4();
DROP TABLE data;

1.3. 填充 FTS 表

FTS 表使用 INSERT、UPDATEDELETE 语句填充,其填充方式与普通 SQLite 表相同。

以及用户命名的列(如果没有,则为"内容"列 module 参数被指定为 CREATE VIRTUAL TABLE 语句的一部分),每个 FTS 表都有一个"rowid"列。FTS 的 rowid 表的行为方式与普通 SQLite 的 rowid 列相同 表,但存储在 FTS 表的 rowid 列中的值除外 如果使用 VACUUM 命令重建数据库,则保持不变。 对于 FTS 表,"docid"允许与通常的"rowid"一起作为别名, "oid"和"oid"标识符。尝试插入或更新带有 表中已存在的 docid 值是一个错误,就像它一样 使用普通的 SQLite 表。

"docid"和普通的SQLite之间还有一个微妙的区别 rowid 列的别名。通常,如果 INSERT 或 UPDATE 语句 将离散值分配给 rowid 列 SQLite 的两个或多个别名 写入 INSERT 或 UPDATE 中指定的最右边的此类值 语句添加到数据库中。但是,为两者分配非 NULL 值 插入或 更新 FTS 表被视为错误。有关示例,请参见下文。

sql 复制代码
-- Create an FTS table
CREATE VIRTUAL TABLE pages USING fts4(title, body);

-- Insert a row with a specific docid value.
INSERT INTO pages(docid, title, body) VALUES(53, 'Home Page', 'SQLite is a software...');

-- Insert a row and allow FTS to assign a docid value using the same algorithm as
-- SQLite uses for ordinary tables. In this case the new docid will be 54,
-- one greater than the largest docid currently present in the table.
INSERT INTO pages(title, body) VALUES('Download', 'All SQLite source code...');

-- Change the title of the row just inserted.
UPDATE pages SET title = 'Download SQLite' WHERE rowid = 54;

-- Delete the entire table contents.
DELETE FROM pages;

-- The following is an error. It is not possible to assign non-NULL values to both
-- the rowid and docid columns of an FTS table.
INSERT INTO pages(rowid, docid, title, body) VALUES(1, 2, 'A title', 'A document body');

为了支持全文查询,FTS 维护了一个反向索引,用于映射 从数据集中出现的每个唯一术语或单词到位置 它出现在表格内容中。对于好奇的人,一个 用于存储的数据结构的完整说明 数据库文件中的此索引如下所示。一个特点 这种数据结构是,在任何时候数据库都可能包含不 一个索引 B 树,但几个不同的 B 树是增量的 在插入、更新和删除行时合并。这种技术改进了 写入 FTS 表时的性能,但会导致一些开销 使用索引的全文查询。评估特殊的"优化"命令, 的 SQL 语句 表单 "INSERT INTO <fts-table>(<fts-table>) VALUES('optimize')", 导致 FTS 将所有现有的索引 b 树合并为一个大的 包含整个索引的 b 树。这可能是一项昂贵的操作, 但可能会加快将来的查询速度。

例如,要优化名为 FTS 表的全文索引 "文档":

sql 复制代码
-- Optimize the internal structure of FTS table "docs".
INSERT INTO docs(docs) VALUES('optimize');

对于某些人来说,上述陈述在语法上可能不正确。指 描述[简单 FTS 查询](#简单 FTS 查询)的部分,以获取说明。

还有另一种已弃用的方法来调用 optimize 使用 SELECT 语句进行操作。新代码应使用语句 类似于上面的 INSERT 来优化 FTS 结构。

1.4. 简单的 FTS 查询

至于所有其他 SQLite 表,无论是虚拟表还是其他表,都会检索数据 使用 SELECT 语句从 FTS 表中。

FTS 表可以使用两个 SELECT 语句进行有效查询 不同的形式:

  • 按 rowid 查询 。如果 SELECT 语句的 WHERE 子句 包含"rowid = ?"形式的子句,其中 ?是一个 SQL 表达式, FTS 能够使用等效项直接检索请求的行 SQLite INTEGER PRIMARY KEY 索引。

  • 全文查询。如果 SELECT 语句的 WHERE 子句包含 "<column> MATCH?"形式的子句,FTS 能够使用 内置全文索引,用于将搜索限制在这些文档上 与指定为右侧操作数的全文查询字符串匹配 的 MATCH 子句。

如果这两种查询策略都不能使用,则所有 对 FTS 表的查询是通过对整个 桌子。如果表包含大量数据,则这可能是 不切实际的方法(本页的第一个示例表明,线性 使用现代 PC 扫描 1.5 GB 数据大约需要 30 秒)。

sql 复制代码
-- The examples in this block assume the following FTS table:
CREATE VIRTUAL TABLE mail USING fts3(subject, body);

SELECT * FROM mail WHERE rowid = 15;                -- Fast. Rowid lookup.
SELECT * FROM mail WHERE body MATCH 'sqlite';       -- Fast. Full-text query.
SELECT * FROM mail WHERE mail MATCH 'search';       -- Fast. Full-text query.
SELECT * FROM mail WHERE rowid BETWEEN 15 AND 20;   -- Fast. Rowid lookup.
SELECT * FROM mail WHERE subject = 'database';      -- Slow. Linear scan.
SELECT * FROM mail WHERE subject MATCH 'database';  -- Fast. Full-text query.

在上面的所有全文查询中,MATCH 的右操作数 运算符是由单个术语组成的字符串。在本例中,MATCH 对于包含一个或多个文档的所有文档,表达式的计算结果为 true 指定单词的实例("sqlite"、"search"或"database",取决于 在你看的例子上)。将单个术语指定为右侧 MATCH 运算符的操作数生成最简单和最常见的类型 的全文查询是可能的。但是,可以进行更复杂的查询, 包括短语搜索、词缀搜索和文档搜索 包含每个术语在定义的邻近范围内发生的术语组合 其他。下面介绍了查询全文索引的各种方法。

通常,全文查询不区分大小写。但是,这 取决于 FTS 表使用的特定分词器 被查询。有关详细信息,请参阅有关分词器的部分

上面的段落指出,具有简单项的 MATCH 运算符 右操作数对于包含 指定期限。在这种情况下,"文件"可以指 存储在 FTS 表的一行的单列中的数据,或存储到内容中 一行中的所有列,具体取决于用作 MATCH 运算符的左手操作数。如果标识符指定为 MATCH 运算符的左操作数是 FTS 表列名, 则搜索词必须包含的文档是值 存储在指定的列中。但是,如果标识符是名称 ,则 MATCH 运算符的计算结果为 true 对于任何列包含搜索的 FTS 表的每一行 术语。以下示例演示了这一点:

sql 复制代码
-- Example schema
CREATE VIRTUAL TABLE mail USING fts3(subject, body);

-- Example table population
INSERT INTO mail(docid, subject, body) VALUES(1, 'software feedback', 'found it too slow');
INSERT INTO mail(docid, subject, body) VALUES(2, 'software feedback', 'no feedback');
INSERT INTO mail(docid, subject, body) VALUES(3, 'slow lunch order',  'was a software problem');

-- Example queries
SELECT * FROM mail WHERE subject MATCH 'software';    -- Selects rows 1 and 2
SELECT * FROM mail WHERE body    MATCH 'feedback';    -- Selects row 2
SELECT * FROM mail WHERE mail    MATCH 'software';    -- Selects rows 1, 2 and 3
SELECT * FROM mail WHERE mail    MATCH 'slow';        -- Selects rows 1 and 3

乍一看,上面示例中的最后两个全文查询似乎 语法不正确,因为有一个表名("mail")用作 SQL 表达式。这是可以接受的原因是每个 FTS 表 实际上有一个同名的 HIDDEN 列 作为表本身(在本例中为"邮件")。存储在 this 中的值 列对应用程序没有意义,但可以用作 MATCH 运算符的左手操作数。这个特殊的专栏也可能是 作为参数传递给 FTS 辅助函数

以下示例说明了上述内容。表达式"文档", "docs.docs"和"main.docs.docs"都是指"docs"列。但是, 表达式"main.docs"不引用任何列。它可以用来 引用表,但在以下上下文中不允许使用表名 它在下面使用。

sql 复制代码
-- Example schema
CREATE VIRTUAL TABLE docs USING fts4(content);

-- Example queries
SELECT * FROM docs WHERE docs MATCH 'sqlite';              -- OK.
SELECT * FROM docs WHERE docs.docs MATCH 'sqlite';         -- OK.
SELECT * FROM docs WHERE main.docs.docs MATCH 'sqlite';    -- OK.
SELECT * FROM docs WHERE main.docs MATCH 'sqlite';         -- Error.

1.5. 小结

从用户的角度来看,FTS表类似于普通的SQLite 表在许多方面。数据可以添加到、修改和删除 从 FTS 表中使用 INSERT、UPDATE 和 DELETE 命令,就像 它可能与普通桌子一起使用。同样,可以使用 SELECT 命令 查询数据。以下列表总结了 FTS 之间的差异 和普通表:

  1. 与所有虚拟表类型一样,无法创建索引或 附加到 FTS 表的触发器。也无法使用 ALTER TABLE 命令向 FTS 表添加额外的列(尽管可以使用 ALTER TABLE 重命名 FTS 表)。

  2. 作为"CREATE VIRTUAL TABLE"语句的一部分指定的数据类型 用于创建 FTS 表将被完全忽略。而不是 将类型关联应用于插入值的常规规则,全部 插入到 FTS 表列中的值(特殊 rowid 除外) column) 在存储之前转换为 TEXT 类型。

  3. FTS 表允许使用特殊别名"docid"来指代 所有虚拟表都支持的 rowid 列。

  4. FTS MATCH 运算符支持基于内置 全文索引。

  5. FTS 辅助函数 snippet()、offsets()matchinfo() 是 可用于支持全文查询。

  6. 每个 FTS 表都有一个隐藏列,其中包含 与表本身同名。每行中包含的值 hidden column 是一个 blob,它仅用作 MATCH 运算符的左操作数,或用作 MATCH 运算符的最左边的参数 [FTS 辅助功能](#FTS 辅助功能)。

2. 编译和启用 FTS3 和 FTS4

尽管 FTS3 和 FTS4 包含在 SQLite 核心源代码中,但它们不是 默认启用。要在启用 FTS 功能的情况下构建 SQLite,请定义 编译时SQLITE_ENABLE_FTS3预处理器宏。新应用 还应该定义 SQLITE_ENABLE_FTS3_PARENTHESIS 宏以启用增强的查询语法(见下文)。通常,这是通过添加 以下两个开关到编译器命令行:

sql 复制代码
-DSQLITE_ENABLE_FTS3
-DSQLITE_ENABLE_FTS3_PARENTHESIS

请注意,启用 FTS3 也会使 FTS4 可用。没有单独的 SQLITE_ENABLE_FTS4编译时选项。SQLite 的构建支持 FTS3 和 FTS4 都不支持,或者两者都不支持。

如果使用基于合并 autoconf 的构建系统,请将 CPPFLAGS 运行"configure"脚本时的环境变量很容易 设置这些宏的方式。例如,以下命令:

sql 复制代码
CPPFLAGS="-DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS" ./configure <configure options>

*其中<配置选项>*是通常传递给的选项 配置脚本(如果有)。

由于 FTS3 和 FTS4 是虚拟表,因此 SQLITE_ENABLE_FTS3编译时选项 与 SQLITE_OMIT_VIRTUALTABLE 选项不兼容。

如果 SQLite 的构建不包含 FTS 模块,那么任何准备 SQL 语句,用于创建 FTS3 或 FTS4 表,或者删除或访问现有的 FTS 表无论如何都会失败。返回的错误消息将与此类似 更改为"没有这样的模块:ftsN"(其中 N 是 3 或 4)。

如果 ICU 库的 C 版本可用,则 FTS 也可以使用 SQLITE_ENABLE_ICU 预处理器宏定义。使用此宏进行编译可使 FTS 分词器能够使用 ICU 库将文档拆分为术语 (words) 使用指定语言和区域设置的约定。

sql 复制代码
-DSQLITE_ENABLE_ICU

3. 全文索引查询

FTS 表最有用的是查询 使用内置的全文索引执行。全文查询是 通过指定表单的子句来执行 "<column> MATCH <full-text query expression>" 作为 WHERE 的一部分 从 FTS 表中读取数据的 SELECT 语句的子句。简单的 FTS 查询,返回所有文档 包含上面描述的给定术语。在那次讨论中,右手 假定 MATCH 运算符的操作数是由 单项。本节介绍支持的更复杂的查询类型 通过 FTS 表,以及如何通过指定更多 复杂查询表达式作为 MATCH 运算符的右操作数。

FTS 表支持三种基本查询类型:

  • 令牌或令牌前缀查询 。 可以查询 FTS 表中包含指定 术语(上述简单情况),或 包含具有指定前缀的术语的所有文档。正如我们所拥有的 因此,特定术语的查询表达式只是术语本身。 用于搜索词前缀的查询表达式是前缀 本身附加了一个"*"字符。例如:
sql 复制代码
-- Virtual table declaration
CREATE VIRTUAL TABLE docs USING fts3(title, body);

-- Query for all documents containing the term "linux":
SELECT * FROM docs WHERE docs MATCH 'linux';

-- Query for all documents containing a term with the prefix "lin". This will match
-- all documents that contain "linux", but also those that contain terms "linear",
--"linker", "linguistic" and so on.
SELECT * FROM docs WHERE docs MATCH 'lin*';
  • 通常,令牌或令牌前缀查询与 FTS 表匹配 指定为 MATCH 运算符左侧的列。或者,如果 指定了与 FTS 表本身同名的特殊列, 针对所有列。这可以通过指定列名来覆盖 后跟":" 基本术语查询前的字符。可能有空间 在":"和要查询的术语之间,但不在列名之间 和 ":" 字符。例如:
sql 复制代码
-- Query the database for documents for which the term "linux" appears in
-- the document title, and the term "problems" appears in either the title
-- or body of the document.
SELECT * FROM docs WHERE docs MATCH 'title:linux problems';

-- Query the database for documents for which the term "linux" appears in
-- the document title, and the term "driver" appears in the body of the document
-- ("driver" may also appear in the title, but this alone will not satisfy the
-- query criteria).
SELECT * FROM docs WHERE body MATCH 'title:linux driver';
  • 如果 FTS 表是 FTS4 表(不是 FTS3),则还可以添加令牌前缀 带有"^"字符。在这种情况下,为了匹配令牌必须 显示为匹配行的任何列中的第一个标记。例子:
sql 复制代码
-- All documents for which "linux" is the first token of at least one
-- column.
SELECT * FROM docs WHERE docs MATCH '^linux';

-- All documents for which the first token in column "title" begins with "lin".
SELECT * FROM docs WHERE body MATCH 'title: ^lin*';
  • 短语查询。 短语查询是一种查询,用于检索包含 按指定顺序指定的术语或术语前缀集,没有 干预令牌。短语查询通过括起空格来指定 用双引号 (") 分隔的术语或术语前缀序列。 例如:
sql 复制代码
-- Query for all documents that contain the phrase "linux applications".
SELECT * FROM docs WHERE docs MATCH '"linux applications"';

-- Query for all documents that contain a phrase that matches "lin* app*". As well as
-- "linux applications", this will match common phrases such as "linoleum appliances"
-- or "link apprentice".
SELECT * FROM docs WHERE docs MATCH '"lin* app*"';
  • NEAR 查询 。 NEAR 查询是返回包含两个或 在每个术语或短语的指定邻近范围内有更多指定的术语或短语 其他(默认情况下,干预项不超过 10 个)。NEAR 查询是 通过将关键字"NEAR"放在两个短语、令牌或 令牌前缀查询。要指定默认值以外的邻近性, 可以使用"NEAR/<N> "形式的运算符,其中 <N> 是允许的最大干预项数。 例如:
sql 复制代码
-- Virtual table declaration.
CREATE VIRTUAL TABLE docs USING fts4();

-- Virtual table data.
INSERT INTO docs VALUES('SQLite is an ACID compliant embedded relational database management system');

-- Search for a document that contains the terms "sqlite" and "database" with
-- not more than 10 intervening terms. This matches the only document in
-- table docs (since there are only six terms between "SQLite" and "database"
-- in the document).
SELECT * FROM docs WHERE docs MATCH 'sqlite NEAR database';

-- Search for a document that contains the terms "sqlite" and "database" with
-- not more than 6 intervening terms. This also matches the only document in
-- table docs. Note that the order in which the terms appear in the document
-- does not have to be the same as the order in which they appear in the query.
SELECT * FROM docs WHERE docs MATCH 'database NEAR/6 sqlite';

-- Search for a document that contains the terms "sqlite" and "database" with
-- not more than 5 intervening terms. This query matches no documents.
SELECT * FROM docs WHERE docs MATCH 'database NEAR/5 sqlite';

-- Search for a document that contains the phrase "ACID compliant" and the term
-- "database" with not more than 2 terms separating the two. This matches the
-- document stored in table docs.
SELECT * FROM docs WHERE docs MATCH 'database NEAR/2 "ACID compliant"';

-- Search for a document that contains the phrase "ACID compliant" and the term
-- "sqlite" with not more than 2 terms separating the two. This also matches
-- the only document stored in table docs.
SELECT * FROM docs WHERE docs MATCH '"ACID compliant" NEAR/2 sqlite';
  • 单个查询中可能会出现多个 NEAR 运算符。在这种情况下,每个 由 NEAR 运算符分隔的术语或短语对必须出现在 在文档中指定彼此的接近度。使用同一张表和 数据,如上面的示例块所示:
sql 复制代码
-- The following query selects documents that contains an instance of the term 
-- "sqlite" separated by two or fewer terms from an instance of the term "acid",
-- which is in turn separated by two or fewer terms from an instance of the term
-- "relational".
SELECT * FROM docs WHERE docs MATCH 'sqlite NEAR/2 acid NEAR/2 relational';

-- This query matches no documents. There is an instance of the term "sqlite" with
-- sufficient proximity to an instance of "acid" but it is not sufficiently close
-- to an instance of the term "relational".
SELECT * FROM docs WHERE docs MATCH 'acid NEAR/2 sqlite NEAR/2 relational';

短语和 NEAR 查询不能跨越一行中的多个列。

上述三种基本查询类型可用于查询全文 与指定条件匹配的文档集的索引。使用 FTS查询表达式语言可以执行各种设置 对基本查询结果的操作。目前有三个 支持的操作:

  • AND 运算符确定两组文档的交集
  • OR 运算符计算两组文档的并集
  • NOT 运算符(或者,如果使用标准语法,则为一元"-"运算符) 可用于计算一组 关于另一个文件。

FTS 模块可以编译为使用两个略有不同的版本之一 全文查询语法、"标准"查询语法和"增强型" 查询语法。描述的基本术语、术语前缀、短语和 NEAR 查询 以上在两个版本的语法中是相同的。设置的方式 指定的操作略有不同。以下两个小节 描述与集操作相关的两个查询语法的部分。 有关编译说明,请参阅如何编译 fts 的说明。

3.1. 使用增强型查询语法设置操作

增强的查询语法支持 AND、OR 和 NOT 二进制集运算符。 运算符的两个操作数中的每一个都可以是基本的 FTS 查询,或者 另一个 AND、OR 或 NOT 设置操作的结果。必须输入运算符 使用大写字母。否则,它们将被解释为基本术语查询 而不是 set 运算符。

可以隐式指定 AND 运算符。如果出现两个基本查询 在 FTS 查询字符串中没有运算符将它们分隔开来,结果是 就像两个基本查询被 AND 运算符分隔一样。 例如,查询表达式"隐式运算符"是一个更简洁的 "隐式 AND 运算符"的版本。

sql 复制代码
-- Virtual table declaration
CREATE VIRTUAL TABLE docs USING fts3();

-- Virtual table data
INSERT INTO docs(docid, content) VALUES(1, 'a database is a software system');
INSERT INTO docs(docid, content) VALUES(2, 'sqlite is a software system');
INSERT INTO docs(docid, content) VALUES(3, 'sqlite is a database');

-- Return the set of documents that contain the term "sqlite", and the
-- term "database". This query will return the document with docid 3 only.
SELECT * FROM docs WHERE docs MATCH 'sqlite AND database';

-- Again, return the set of documents that contain both "sqlite" and
-- "database". This time, use an implicit AND operator. Again, document
-- 3 is the only document matched by this query. 
SELECT * FROM docs WHERE docs MATCH 'database sqlite';

-- Query for the set of documents that contains either "sqlite" or "database".
-- All three documents in the database are matched by this query.
SELECT * FROM docs WHERE docs MATCH 'sqlite OR database';

-- Query for all documents that contain the term "database", but do not contain
-- the term "sqlite". Document 1 is the only document that matches this criteria.
SELECT * FROM docs WHERE docs MATCH 'database NOT sqlite';

-- The following query matches no documents. Because "and" is in lowercase letters,
-- it is interpreted as a basic term query instead of an operator. Operators must
-- be specified using capital letters. In practice, this query will match any documents
-- that contain each of the three terms "database", "and" and "sqlite" at least once.
-- No documents in the example data above match this criteria.
SELECT * FROM docs WHERE docs MATCH 'database and sqlite';

上面的示例都使用基本全文术语查询作为 演示了设置操作。也可以使用短语和 NEAR 查询, 其他集合操作的结果也是如此。当多套操作时 存在于 FTS 查询中,运算符的优先级如下:

算子 增强的查询语法优先级
最高优先级(最紧密的分组)。
最低优先级(最松散的分组)。

使用增强的查询语法时,可以使用括号来覆盖 各种运算符的默认优先级。例如:

sql 复制代码
-- Return the docid values associated with all documents that contain the
-- two terms "sqlite" and "database", and/or contain the term "library".
SELECT docid FROM docs WHERE docs MATCH 'sqlite AND database OR library';

-- This query is equivalent to the above.
SELECT docid FROM docs WHERE docs MATCH 'sqlite AND database'
  UNION
SELECT docid FROM docs WHERE docs MATCH 'library';

-- Query for the set of documents that contains the term "linux", and at least
-- one of the phrases "sqlite database" and "sqlite library".
SELECT docid FROM docs WHERE docs MATCH '("sqlite database" OR "sqlite library") AND linux';

-- This query is equivalent to the above.
SELECT docid FROM docs WHERE docs MATCH 'linux'
  INTERSECT
SELECT docid FROM (
  SELECT docid FROM docs WHERE docs MATCH '"sqlite library"'
    UNION
  SELECT docid FROM docs WHERE docs MATCH '"sqlite database"'
);

3.2. 使用标准查询语法设置操作

使用标准查询语法的 FTS 查询集操作类似,但 不相同,以使用增强的查询语法设置操作。那里 有四个区别,如下所示:

  1. 仅支持 AND 运算符的隐式版本。 将字符串"AND"指定为标准查询语法查询的一部分是 解释为包含术语的文档集的术语查询 "和"。
  1. 不支持括号。
  1. 不支持 NOT 运算符。而不是 NOT 运算符,标准查询语法支持一元"-"运算符 可以应用于基本术语和术语前缀查询(但不适用于短语 或 NEAR 查询)。具有一元"-"运算符的术语或术语前缀 附加到它可能不会显示为 OR 运算符的操作数。An FTS 查询可能不完全由术语或具有一元的术语前缀查询组成 附加到它们的"-"运算符。
sql 复制代码
-- Search for the set of documents that contain the term "sqlite" but do
-- not contain the term "database".
SELECT * FROM docs WHERE docs MATCH 'sqlite -database';
  1. 集合操作的相对优先级不同。 具体而言,使用标准查询语法时,"OR"运算符具有 优先级高于"AND"。使用 标准查询语法为:
算子 标准查询语法优先级
一元 "-" 最高优先级(最紧密的分组)。
最低优先级(最松散的分组)。
  1. 以下示例说明了使用标准的运算符的优先级 查询语法:
sql 复制代码
-- Search for documents that contain at least one of the terms "database"
-- and "sqlite", and also contain the term "library". Because of the differences
-- in operator precedences, this query would have a different interpretation using
-- the enhanced query syntax.
SELECT * FROM docs WHERE docs MATCH 'sqlite OR database library';

4. 辅助函数 - 片段、偏移量和 Matchinfo

FTS3 和 FTS4 模块提供了三个可能有用的特殊 SQL 标量函数 致全文查询系统的开发人员:"snippet"、"offsets"和 "匹配信息"。"snippet"和"offsets"函数的目的是允许 用户在返回的文档中标识查询词的位置。 "matchinfo"功能为用户提供可能有用的指标 用于根据相关性对查询结果进行筛选或排序。

所有三个特殊 SQL 标量函数的第一个参数 必须是函数所在的 FTS 表的 FTS 隐藏列 适用于。FTS 隐藏列是自动生成的列,可在 与 FTS 表本身同名的所有 FTS 表。 例如,给定一个名为"mail"的 FTS 表:

sql 复制代码
SELECT offsets(mail) FROM mail WHERE mail MATCH <full-text query expression>;
SELECT snippet(mail) FROM mail WHERE mail MATCH <full-text query expression>;
SELECT matchinfo(mail) FROM mail WHERE mail MATCH <full-text query expression>;

这三个辅助函数仅在 SELECT 语句中有用: 使用 FTS 表的全文索引。如果在使用 "按 rowid 查询"或"线性扫描"策略,然后是代码段和 offsets 都返回一个空字符串,而 matchinfo 函数返回 大小为 0 字节的 Blob 值。

所有三个辅助函数都从中提取一组"可匹配短语" 要使用的 FTS 查询表达式。可匹配短语集 给定的查询由所有短语(包括未加引号的标记和 token 前缀)中,但前缀为 一元"-"运算符(标准语法)或子表达式的一部分 用作 NOT 运算符的右手操作数。

根据以下条件,FTS 表中的每个系列令牌 匹配查询表达式中的一个可匹配短语,称为 "短语匹配":

  1. 如果一个可匹配的短语是一系列短语的一部分,则由 FTS 查询表达式中的 NEAR 运算符,则每个短语匹配 必须足够接近相关短语的其他匹配项 类型来满足 NEAR 条件。
  2. 如果 FTS 查询中的可匹配短语仅限于匹配 数据,则只有短语匹配 在该列内发生被考虑。

4.1. Offsets函数

对于使用全文索引的 SELECT 查询,offsets() 函数 返回一个文本值,其中包含一系列以空格分隔的整数。为 每个短语中的每个术语都与当前行匹配, 返回的列表中有四个整数。每组四个整数是 解释如下:

整数 解释
0 术语实例出现的列号 (0 表示 FTS 表的最左边列,1 表示下一个最左边的列,依此类推)。
1 全文查询中匹配术语的术语编号 表达。查询表达式中的术语从开始编号 从 0 开始,按它们出现的顺序。
2 列中匹配项的字节偏移量。
3 匹配项的大小(以字节为单位)。

以下块包含使用 offsets 函数的示例。

sql 复制代码
CREATE VIRTUAL TABLE mail USING fts3(subject, body);
INSERT INTO mail VALUES('hello world', 'This message is a hello world message.');
INSERT INTO mail VALUES('urgent: serious', 'This mail is seen as a more serious mail');

-- The following query returns a single row (as it matches only the first
-- entry in table "mail". The text returned by the offsets function is
-- "0 0 6 5 1 0 24 5".
--
-- The first set of four integers in the result indicate that column 0
-- contains an instance of term 0 ("world") at byte offset 6. The term instance
-- is 5 bytes in size. The second set of four integers shows that column 1
-- of the matched row contains an instance of term 0 ("world") at byte offset
-- 24. Again, the term instance is 5 bytes in size.
SELECT offsets(mail) FROM mail WHERE mail MATCH 'world';

-- The following query returns also matches only the first row in table "mail".
-- In this case the returned text is "1 0 5 7 1 0 30 7".
SELECT offsets(mail) FROM mail WHERE mail MATCH 'message';

-- The following query matches the second row in table "mail". It returns the
-- text "1 0 28 7 1 1 36 4". Only those occurrences of terms "serious" and "mail"
-- that are part of an instance of the phrase "serious mail" are identified; the
-- other occurrences of "serious" and "mail" are ignored.
SELECT offsets(mail) FROM mail WHERE mail MATCH '"serious mail"';

4.2. 代码段函数

代码段函数用于创建文档文本的格式化片段 作为全文查询结果报告的一部分显示。代码段函数 可以在 1 到 6 个参数之间传递,如下所示:

论点 默认值 描述
0 不适用 代码段函数的第一个参数必须始终是要查询的 FTS 表的 FTS 隐藏列,并且要从中获取代码段。FTS 隐藏列是自动生成的列,与 FTS 表本身。
1 "<b>" "开始匹配"文本。
2 "</b>" "结束匹配"文本。
3 "<b>......</b>" "省略号"文本。
4 -1 FTS 表列号,用于提取返回的片段 文本来自。列从左到右编号,从 零。负值表示可以提取文本 从任何列。
5 -15 此整数参数的绝对值用作 (近似)要包含在返回文本中的标记数 价值。最大允许绝对值为 64。的价值 在下面的讨论中,这个参数被称为 N

代码段函数首先尝试查找由 的 |N| 当前行中至少包含一个短语的标记 匹配当前行中某处匹配的每个可匹配短语, 其中 |N| 是传递给 代码段函数。如果存储在单列中的文本包含小于 *|N|*tokens,则考虑整个列值。文本片段 不能跨越多个列。

如果可以找到这样的文本片段,则返回以下内容 修改:

  • 如果文本片段不是从列值的开头开始, "省略号"文本位于其前面。
  • 如果文本片段未在列值的末尾结束, "省略号"文本附加到其中。
  • 对于文本片段中属于短语匹配的每个标记, "开始匹配"文本插入到令牌之前的片段中, 并在其后立即插入"结束匹配"文本。

如果可以找到多个此类片段,则包含 更多的"额外"短语匹配受到青睐。开始 选定的文本片段可以向前或向后移动几个标记 尝试将短语匹配集中在 片段。

假设 N 为正值,如果没有片段,则 包含与每个可匹配短语相对应的短语匹配,即代码段 函数尝试查找大约 N /2 个标记的两个片段 它们之间至少包含一个短语匹配,每个可匹配的短语 与当前行匹配。如果失败,则尝试查找三个 每个 N /3 个令牌的片段,最后是 4 个 N /4 令牌 碎片。如果找不到包含 必需的短语匹配,提供 N/4 令牌的四个片段 选择最佳覆盖范围。

如果 N 为负值,则找不到单个片段 包含所需的短语匹配项,代码段函数将搜索 对于两个片段 |N| 每个代币,然后是三个,然后是四个。在 换句话说,如果指定的 N 值为负数,则 如果需要多个片段,则不会减少片段 以提供所需的短语匹配覆盖率。

找到 M 片段后,M 介于 二和四如上一段所述,它们连接在一起 按排序顺序,用"省略号"文本分隔它们。三 前面列举的修改是在文本之前对文本执行的 返回。

sql 复制代码
Note: In this block of examples, newlines and whitespace characters have
been inserted into the document inserted into the FTS table, and the expected
results described in SQL comments. This is done to enhance readability only,
they would not be present in actual SQLite commands or output.

-- Create and populate an FTS table.
CREATE VIRTUAL TABLE text USING fts4();
INSERT INTO text VALUES('
  During 30 Nov-1 Dec, 2-3oC drops. Cool in the upper portion, minimum temperature 14-16oC
  and cool elsewhere, minimum temperature 17-20oC. Cold to very cold on mountaintops,
  minimum temperature 6-12oC. Northeasterly winds 15-30 km/hr. After that, temperature
  increases. Northeasterly winds 15-30 km/hr.
');

-- The following query returns the text value:
--
--   "<b>...</b>cool elsewhere, minimum temperature 17-20oC. <b>Cold</b> to very 
--    <b>cold</b> on mountaintops, minimum temperature 6<b>...</b>".
--
SELECT snippet(text) FROM text WHERE text MATCH 'cold';

-- The following query returns the text value:
--
--   "...the upper portion, [minimum] [temperature] 14-16oC and cool elsewhere,
--    [minimum] [temperature] 17-20oC. Cold..."
--
SELECT snippet(text, '[', ']', '...') FROM text WHERE text MATCH '"min* tem*"'

4.3. matchinfo函数

matchinfo 函数返回一个 blob 值。如果在查询中使用 不使用全文索引("按 rowid 查询"或"线性扫描"), 则 Blob 的大小为零字节。否则,Blob 由零组成 或更多按计算机字节顺序排列的 32 位无符号整数。确切数字 返回数组中的整数取决于查询和值 传递给 MatchInfo 函数的第二个参数(如果有)。

matchinfo 函数使用一个或两个参数调用。至于 所有辅助函数,第一个参数必须是特殊的 FTS 隐藏列。如果指定了第二个参数,则必须是文本值 仅由字符"p"、"c"、"n"、"a"、"l"、"s"、"x"、"y"和"b"组成。 如果未显式提供第二个参数,则默认为"pcx"。这 第二个参数在下面称为"格式字符串"。

matchinfo 格式字符串中的字符从左到右进行处理。 格式字符串中的每个字符都会导致一个或多个 32 位无符号 要添加到返回数组的整数值。中的"值"列 下表包含附加到 每个支持的格式字符串字符的输出缓冲区。在公式中 给定,cols 是 FTS 表中的列数,phrases 是 查询。

字符 描述
p 1 查询中可匹配短语的数量。
c 1 FTS 中用户定义的列数 表(即不包括 docid 或 FTS 隐藏列)。
x 3 * * 短语 对于短语和表格列的每个不同组合,则 以下三个值: * 在当前行中,短语出现的次数 列。 * 短语在列中出现的总次数 FTS 表中的所有行。 * FTS 表中 列包含短语的至少一个实例。 第一组三个值对应于最左边的列 的表格(第 0 列)和 查询(短语 0)。如果表包含多列,则第二列 输出数组中的三个值对应于短语 0 和 第 1 列。后跟短语 0、第 2 列,依此类推,用于 桌子。以此类推,对于短语 1,第 0 列,然后是短语 1,第 1 列 等。换言之,短语 p 在 可以使用以下公式找到 C 列: hits_this_row = array[3 * (c + p*cols) + 0] hits_all_rows = array[3 * (c + p*cols) + 1] docs_with_hits = array[3 * (c + p*cols) + 2]
y cols * 短语 对于短语和表格列的每个不同组合,则 列中显示的可用短语匹配项数。这是 通常与 MatchInfo 'x' 标志返回的每组三个值中的第一个值相同。但是,报告的命中数 对于属于子表达式的任何短语,"y"标志为零 与当前行不匹配。这对 包含作为 OR 后代的 AND 运算符的表达式 运营商。例如,考虑表达式: 和 document: matchinfo 'x' 标志将报告短语 "a" 和 "c" 的单次命中。 但是,"y"指令将"c"的命中次数报告为零,因为 它是与文档 - (b 和 c) 不匹配的子表达式的一部分。 对于不包含从 OR 派生的 AND 运算符的查询 运算符,'y' 返回的结果值始终与 那些由"x"返回的。 a OR (b AND c) "a c d" 整数值数组中的第一个值对应于 表的最左边列(第 0 列)和查询中的第一个短语 (短语 0)。与其他列/短语组合对应的值 可以使用以下公式进行定位: hits_for_phrase_p_column_c = array[c + p*cols] 对于使用 OR 表达式的查询,或者使用 LIMIT 或 return 的查询 许多行,"y"匹配信息选项可能比"x"更快。
b ((cols+31)/32) * 短语 matchinfo 'b' 标志提供与 matchinfo 'y' 标志类似的信息,但更 紧凑的形式。"b"提供的不是精确的命中数,而是单个 每个短语/列组合的布尔标志。如果短语存在于 列至少一次(即如果"y"的相应整数输出会 为非零),则设置相应的标志。否则清除。 如果表的列数不超过 32 列,则输出单个无符号整数 查询中的每个短语。如果设置了整数的最低有效位,则 短语在第 0 列中至少出现一次。第二个最低有效位是 设置短语是否在第 1 列中出现一次或多次。等等。 如果表的列数超过 32 列,则会在输出中添加一个额外的整数 每个短语对应每增加 32 列或其中的一部分。整数 对应于同一个短语聚集在一起。例如,如果表 查询 45 列的两个短语,输出 4 个整数。第一个 对应于表的短语 0 和第 0-31 列。第二个整数 包含短语 0 和第 32-44 列的数据,依此类推。 例如,如果 nCol 是表中的列数,则确定 短语 p 出现在 C 列中: p_is_in_c = array[p * ((nCol+31)/32)] & (1 << (c % 32))
n 1 FTS4 表中的行数。此值为 仅在查询 FTS4 表时可用,而在查询 FTS3 时不可用。
一个 科尔斯 对于每列,平均数 文本中的标记 列中存储的值(考虑 FTS4 表)。此值仅在查询 FTS4 表时可用, 不是 FTS3。
l 科尔斯 对于每一列,存储在当前行中的值的长度 FTS4 表,以令牌表示。此值仅在查询时可用 FTS4 表,而不是 FTS3。并且仅当"matchinfo=fts3"指令不是 指定为用于创建的"CREATE VIRTUAL TABLE"语句的一部分 FTS4 表。
s 科尔斯 对于每列,最长的长度 短语匹配项的子序列与列值具有共同点 替换为查询文本。例如,如果表列包含文本 'a b c d e',查询是 'a c "d e"',则最长的长度 公共子序列为 2(短语"c"后跟短语"d e")。

例如:

sql 复制代码
-- Create and populate an FTS4 table with two columns:
CREATE VIRTUAL TABLE t1 USING fts4(a, b);
INSERT INTO t1 VALUES('transaction default models default', 'Non transaction reads');
INSERT INTO t1 VALUES('the default transaction', 'these semantics present');
INSERT INTO t1 VALUES('single request', 'default data');

-- In the following query, no format string is specified and so it defaults
-- to "pcx". It therefore returns a single row consisting of a single blob
-- value 80 bytes in size (20 32-bit integers - 1 for "p", 1 for "c" and
-- 3*2*3 for "x"). If each block of 4 bytes in the blob is interpreted
-- as an unsigned integer in machine byte-order, the values will be:
--
--     3 2  1 3 2  0 1 1  1 2 2  0 1 1  0 0 0  1 1 1
--
-- The row returned corresponds to the second entry inserted into table t1.
-- The first two integers in the blob show that the query contained three
-- phrases and the table being queried has two columns. The next block of
-- three integers describes column 0 (in this case column "a") and phrase
-- 0 (in this case "default"). The current row contains 1 hit for "default"
-- in column 0, of a total of 3 hits for "default" that occur in column
-- 0 of any table row. The 3 hits are spread across 2 different rows.
--
-- The next set of three integers (0 1 1) pertain to the hits for "default"
-- in column 1 of the table (0 in this row, 1 in all rows, spread across 
-- 1 rows).
--
SELECT matchinfo(t1) FROM t1 WHERE t1 MATCH 'default transaction "these semantics"';

-- The format string for this query is "ns". The output array will therefore
-- contain 3 integer values - 1 for "n" and 2 for "s". The query returns
-- two rows (the first two rows in the table match). The values returned are:
--
--     3  1 1
--     3  2 0
--
-- The first value in the matchinfo array returned for both rows is 3 (the 
-- number of rows in the table). The following two values are the lengths 
-- of the longest common subsequence of phrase matches in each column.
SELECT matchinfo(t1, 'ns') FROM t1 WHERE t1 MATCH 'default transaction';

matchinfo 函数比代码段或偏移量快得多 功能。这是因为代码段和偏移量的实现 需要从磁盘中检索正在分析的文档,而 MatchInfo 所需的所有数据都作为相同部分的一部分提供 实现全文查询所需的全文索引 本身。这意味着在以下两个查询中,第一个查询可能是 比秒快一个数量级:

sql 复制代码
SELECT docid, matchinfo(tbl) FROM tbl WHERE tbl MATCH <query expression>;
SELECT docid, offsets(tbl) FROM tbl WHERE tbl MATCH <query expression>;

matchinfo 函数提供计算所需的所有信息 概率"词袋"相关性分数,例如 Okapi BM25/BM25F,可能 用于在全文搜索应用程序中对结果进行排序。本附录 A 文档"搜索应用程序提示"包含使用 matchinfo() 函数。

5. fts4aux - 直接访问全文索引

版本 3.7.6 (2011-04-12) 开始, SQLite包括一个名为 "fts4aux",可用于检查现有 FTS 表。尽管它的名字,fts4aux 与 FTS3 一样好用 表,就像它对 FTS4 表所做的那样。Fts4aux 表是只读的。唯一的 修改 fts4aux 表内容的方法是修改 关联的 FTS 表的内容。fts4aux 模块是自动的 包含在包含 FTS 的所有版本中。

fts4aux 虚拟表由一个或两个参数构造。什么时候 与单个参数一起使用时,该参数是 它将用于访问的 FTS 表。访问其他 数据库(例如,创建一个 TEMP fts4aux 表,该表将访问 MAIN 数据库中的 FTS3 表)使用双参数形式并给出 第一个参数中的目标数据库的名称(例如:"main")和名称 FTS3/4 表作为第二个参数。(双参数形式 SQLite 版本 3.7.17 添加了 fts4aux (2013-05-20) 并且会在以前的版本中抛出错误。 例如:

sql 复制代码
-- Create an FTS4 table
CREATE VIRTUAL TABLE ft USING fts4(x, y);

-- Create an fts4aux table to access the full-text index for table "ft"
CREATE VIRTUAL TABLE ft_terms USING fts4aux(ft);

-- Create a TEMP fts4aux table accessing the "ft" table in "main"
CREATE VIRTUAL TABLE temp.ft_terms_2 USING fts4aux(main,ft);

对于 FTS 表中存在的每个术语,有 2 到 N+1 行 在 fts4aux 表中,其中 N 是 关联的 FTS 表。fts4aux 表始终具有相同的四列, 如下,从左到右:

列名称 专栏内容
术语 包含此行的术语的文本。
山坳 此列可以包含文本值"*"(即单个 字符,U+002a)或介于 0 和 N-1 之间的整数,其中 N 为 再次是相应 FTS 表中用户定义的列数。
文件 此列始终包含大于零的整数值。 如果"col"列包含值"*",则此列 包含 FTS 表中至少包含一个 术语的实例(在任何列中)。如果 col 包含整数 值,则此列包含 FTS 表的行数 在由 col 值。像往常一样,FTS 表的列被编号 从左到右,从零开始。
事件 此列还始终包含大于零的整数值。 如果"col"列包含值"*",则此列 包含 FTS 表(在任何列中)。否则,如果 col 包含整数 值,则此列包含 出现在 FTS 表列中由 col 标识的术语 价值。
languageid*(隐藏)* 此列确定用于哪种[语言](#列名称 专栏内容 术语 包含此行的术语的文本。 山坳 此列可以包含文本值“”(即单个 字符,U+002a)或介于 0 和 N-1 之间的整数,其中 N 为 再次是相应 FTS 表中用户定义的列数。 文件 此列始终包含大于零的整数值。 如果“col”列包含值“”,则此列 包含 FTS 表中至少包含一个 术语的实例(在任何列中)。如果 col 包含整数 值,则此列包含 FTS 表的行数 在由 col 值。像往常一样,FTS 表的列被编号 从左到右,从零开始。 事件 此列还始终包含大于零的整数值。 如果“col”列包含值“*”,则此列 包含 FTS 表(在任何列中)。否则,如果 col 包含整数 值,则此列包含 出现在 FTS 表列中由 col 标识的术语 价值。 languageid(隐藏) 此列确定用于哪种语言 从 FTS3/4 表中提取词汇。 languageid 的默认值为 0。如果替代语言 在 WHERE 子句约束中指定,则该替代项为 用于代替 0。每个查询只能有一个 languageid。 换言之,WHERE 子句不能包含范围约束 或 languageid 上的 IN 运算符。) 从 FTS3/4 表中提取词汇。 languageid 的默认值为 0。如果替代语言 在 WHERE 子句约束中指定,则该替代项为 用于代替 0。每个查询只能有一个 languageid。 换言之,WHERE 子句不能包含范围约束 或 languageid 上的 IN 运算符。

例如,使用上面创建的表:

sql 复制代码
INSERT INTO ft(x, y) VALUES('Apple banana', 'Cherry');
INSERT INTO ft(x, y) VALUES('Banana Date Date', 'cherry');
INSERT INTO ft(x, y) VALUES('Cherry Elderberry', 'Elderberry');

-- The following query returns this data:
--
--     apple       |  *  |  1  |  1
--     apple       |  0  |  1  |  1
--     banana      |  *  |  2  |  2
--     banana      |  0  |  2  |  2
--     cherry      |  *  |  3  |  3
--     cherry      |  0  |  1  |  1
--     cherry      |  1  |  2  |  2
--     date        |  *  |  1  |  2
--     date        |  0  |  1  |  2
--     elderberry  |  *  |  1  |  2
--     elderberry  |  0  |  1  |  1
--     elderberry  |  1  |  1  |  1
--
SELECT term, col, documents, occurrences FROM ft_terms;

在示例中,"term"列中的值都是小写的, 即使它们以混合大小写插入表"ft"中。这是因为 fts4aux 表包含从文档文本中提取的术语 通过分词器。在本例中,由于表"ft"使用简单的分词器,这意味着所有项都已折叠为 小写。此外,(例如)没有带有"term"列的行 设置为"apple",列"col"设置为 1。由于没有实例 在第 1 列中的术语"Apple"中,FTS4aux 表中不存在任何行。

在事务期间,写入 FTS 表的某些数据可能是 缓存在内存中,仅当事务 承诺。但是,fts4aux 模块的实现只能 从数据库中读取数据。在实践中,这意味着如果 fts4aux 表是从事务中查询的,其中关联的 FTS 表已修改,查询结果很可能反映 只有所做的更改的子集(可能是空的)。

6. FTS4 选项

如果"CREATE VIRTUAL TABLE"语句指定模块 FTS4(不是 FTS3), 然后是特殊指令 - FTS4 选项 - 类似于"tokenize=*"选项 也可以代替列名出现。FTS4 选项包括 选项名称,后跟"="字符,后跟选项值。 选项值可以选择用单引号或双引号括起来,其中 嵌入的引号字符的转义方式与 SQL 文本的转义方式相同。那里 不能是"="字符两侧的空格。例如 要创建选项"matchinfo"值设置为"fts3"的 FTS4 表,请执行以下操作:

sql 复制代码
-- Create a reduced-footprint FTS4 table.
CREATE VIRTUAL TABLE papers USING fts4(author, document, matchinfo=fts3);

FTS4 目前支持以下选项:

选择 解释
压缩 compress 选项用于指定压缩功能。这是一个错误 指定压缩函数,而不指定解压缩 功能。有关详细信息,请参见下文
内容 内容允许被索引的文本 存储在与 FTS4 表不同的单独表中, 甚至在SQLite之外。
languageID languageid 选项会导致 FTS4 表具有额外的隐藏 整数列,用于标识 每一行。使用 languageid 选项允许相同的 FTS4 表 以多种语言或脚本保存文本,每种语言或脚本都具有不同的分词器 规则,并独立于其他语言查询每种语言。
比赛信息 当设置为值 "fts3" 时,matchinfo 选项会减少 FTS4 存储的信息,导致 matchinfo() 的"l"选项不再可用。
notindexed 此选项用于指定数据所在的列的名称 未编入索引。存储在未编制索引的列中的值不会 由 MATCH 查询匹配。它们也不被辅助功能所认可。 单个 CREATE VIRTUAL TABLE 语句可以有任意数量的 notindexed 选项。
次序 "order"选项可以设置为"DESC"或"ASC"(在上部或 小写)。如果设置为"DESC",则 FTS4 将其数据存储在 一种按 docid 按降序优化返回结果的方法。 如果设置为"ASC"(默认值),则数据结构为 针对 Docid 按升序返回结果进行了优化。在其他 words,如果许多查询针对 FTS4 表运行,则使用"ORDER BY docid DESC",那么添加"order=desc"可能会提高性能 CREATE VIRTUAL TABLE 语句的选项。
前缀 此选项可以设置为以逗号分隔的正非零列表 整数。对于列表中的每个整数 N,将创建一个单独的索引 在数据库文件中优化前缀查询,其中 查询词的长度为 N 个字节,不包括"*"字符, 使用 UTF-8 编码时。有关详细信息,[请参见下文](#选择 解释 压缩 compress 选项用于指定压缩功能。这是一个错误 指定压缩函数,而不指定解压缩 功能。有关详细信息,请参见下文。 内容 内容允许被索引的文本 存储在与 FTS4 表不同的单独表中, 甚至在SQLite之外。 languageID languageid 选项会导致 FTS4 表具有额外的隐藏 整数列,用于标识 每一行。使用 languageid 选项允许相同的 FTS4 表 以多种语言或脚本保存文本,每种语言或脚本都具有不同的分词器 规则,并独立于其他语言查询每种语言。 比赛信息 当设置为值 “fts3” 时,matchinfo 选项会减少 FTS4 存储的信息,导致 matchinfo() 的“l”选项不再可用。 notindexed 此选项用于指定数据所在的列的名称 未编入索引。存储在未编制索引的列中的值不会 由 MATCH 查询匹配。它们也不被辅助功能所认可。 单个 CREATE VIRTUAL TABLE 语句可以有任意数量的 notindexed 选项。 次序 “order”选项可以设置为“DESC”或“ASC”(在上部或 小写)。如果设置为“DESC”,则 FTS4 将其数据存储在 一种按 docid 按降序优化返回结果的方法。 如果设置为“ASC”(默认值),则数据结构为 针对 Docid 按升序返回结果进行了优化。在其他 words,如果许多查询针对 FTS4 表运行,则使用“ORDER BY docid DESC“,那么添加”order=desc“可能会提高性能 CREATE VIRTUAL TABLE 语句的选项。 前缀 此选项可以设置为以逗号分隔的正非零列表 整数。对于列表中的每个整数 N,将创建一个单独的索引 在数据库文件中优化前缀查询,其中 查询词的长度为 N 个字节,不包括“*”字符, 使用 UTF-8 编码时。有关详细信息,请参见下文。 解压 此选项用于指定解压缩函数。这是一个错误 指定解压缩函数,而不指定压缩 功能。有关详细信息,请参见下文。)。
解压 此选项用于指定解压缩函数。这是一个错误 指定解压缩函数,而不指定压缩 功能。有关详细信息,[请参见下文](#选择 解释 压缩 compress 选项用于指定压缩功能。这是一个错误 指定压缩函数,而不指定解压缩 功能。有关详细信息,请参见下文。 内容 内容允许被索引的文本 存储在与 FTS4 表不同的单独表中, 甚至在SQLite之外。 languageID languageid 选项会导致 FTS4 表具有额外的隐藏 整数列,用于标识 每一行。使用 languageid 选项允许相同的 FTS4 表 以多种语言或脚本保存文本,每种语言或脚本都具有不同的分词器 规则,并独立于其他语言查询每种语言。 比赛信息 当设置为值 “fts3” 时,matchinfo 选项会减少 FTS4 存储的信息,导致 matchinfo() 的“l”选项不再可用。 notindexed 此选项用于指定数据所在的列的名称 未编入索引。存储在未编制索引的列中的值不会 由 MATCH 查询匹配。它们也不被辅助功能所认可。 单个 CREATE VIRTUAL TABLE 语句可以有任意数量的 notindexed 选项。 次序 “order”选项可以设置为“DESC”或“ASC”(在上部或 小写)。如果设置为“DESC”,则 FTS4 将其数据存储在 一种按 docid 按降序优化返回结果的方法。 如果设置为“ASC”(默认值),则数据结构为 针对 Docid 按升序返回结果进行了优化。在其他 words,如果许多查询针对 FTS4 表运行,则使用“ORDER BY docid DESC“,那么添加”order=desc“可能会提高性能 CREATE VIRTUAL TABLE 语句的选项。 前缀 此选项可以设置为以逗号分隔的正非零列表 整数。对于列表中的每个整数 N,将创建一个单独的索引 在数据库文件中优化前缀查询,其中 查询词的长度为 N 个字节,不包括“*”字符, 使用 UTF-8 编码时。有关详细信息,请参见下文。 解压 此选项用于指定解压缩函数。这是一个错误 指定解压缩函数,而不指定压缩 功能。有关详细信息,请参见下文。)。

使用 FTS4 时,指定包含"="字符的列名 并且既不是"tokenize=*"规范,也不是公认的 FTS4 选项 是一个错误。使用 FTS3 时,无法识别的指令中的第一个标记是 解释为列名称。同样,指定多个"tokenize=*" 使用 FTS4 时,单个表声明中的指令是错误的,而 第二个及后续的"tokenize=*"指令被解释为列 FTS3 的名称。例如:

sql 复制代码
-- An error. FTS4 does not recognize the directive "xyz=abc".
CREATE VIRTUAL TABLE papers USING fts4(author, document, xyz=abc);

-- Create an FTS3 table with three columns - "author", "document"
-- and "xyz".
CREATE VIRTUAL TABLE papers USING fts3(author, document, xyz=abc);

-- An error. FTS4 does not allow multiple tokenize=* directives
CREATE VIRTUAL TABLE papers USING fts4(tokenize=porter, tokenize=simple);

-- Create an FTS3 table with a single column named "tokenize". The
-- table uses the "porter" tokenizer.
CREATE VIRTUAL TABLE papers USING fts3(tokenize=porter, tokenize=simple);

-- An error. Cannot create a table with two columns named "tokenize".
CREATE VIRTUAL TABLE papers USING fts3(tokenize=porter, tokenize=simple, tokenize=icu);

6.1. compress= 和 uncompress= 选项

压缩和解压缩选项允许将 FTS4 内容存储在 压缩形式的数据库。这两个选项都应设置为名称 使用 sqlite3_create_function() 注册的接受单个参数的 SQL 标量函数。

compress 函数应返回该值的压缩版本 作为参数传递给它。每次将数据写入 FTS4 表时, 每个列值都传递给压缩函数和结果值 存储在数据库中。compress 函数可以返回任何类型的 SQLite 值(blob、text、real、integer 或 null)。

解压缩函数应解压缩之前由 压缩函数。换句话说,对于所有 SQLite 值 X,它应该 uncompress(compress(X)) 等于 X。当数据已经 压缩的压缩函数是由FTS4从数据库中读取的,它 在使用之前传递给 uncompress 函数。

如果指定的压缩或解压缩函数不存在,则表 可能仍会创建。在 FTS4 表 读取(如果解压缩函数不存在)或写入(如果是 compress函数不存在)。

sql 复制代码
-- Create an FTS4 table that stores data in compressed form. This
-- assumes that the scalar functions zip() and unzip() have been (or
-- will be) added to the database handle.
CREATE VIRTUAL TABLE papers USING fts4(author, document, compress=zip, uncompress=unzip);

在实现压缩和解压缩功能时,重要的是 注意数据类型。具体来说,当用户从 一个压缩的 FTS 表,FTS 返回的值完全相同 作为 uncompress 函数返回的值,包括数据类型。 如果该数据类型与原始值的数据类型不同,则为 传递给 COMPRESS 函数(例如,如果 uncompress 函数是 返回 BLOB 时,压缩最初传递 TEXT),然后是用户 查询可能无法按预期运行。

6.2. content=选项

content 选项允许 FTS4 放弃存储正在索引的文本。 内容选项可以通过两种方式使用:

  • 索引文档不存储在 SQLite 数据库中 完全("无内容"FTS4 表),或

  • 索引文档存储在创建的数据库表中,并且 由用户管理("外部内容"FTS4 表)。

因为索引文档本身通常比 全文索引、内容选项可以用来实现 节省大量空间。

6.2.1. 无内容FTS4表

为了创建一个不存储索引副本的 FTS4 表 文档,则内容选项应设置为空字符串。 例如,以下 SQL 创建这样一个 FTS4 表,其中包含三个 列 - "A"、"B"和"C":

sql 复制代码
CREATE VIRTUAL TABLE t1 USING fts4(content="", a, b, c);

可以使用 INSERT 语句将数据插入到这样的 FTS4 表中。 但是,与普通的 FTS4 表不同,用户必须提供显式 整数 docid 值。例如:

sql 复制代码
-- This statement is Ok:
INSERT INTO t1(docid, a, b, c) VALUES(1, 'a b c', 'd e f', 'g h i');

-- This statement causes an error, as no docid value has been provided:
INSERT INTO t1(a, b, c) VALUES('j k l', 'm n o', 'p q r');

无法更新或删除存储在无内容 FTS4 中的行 桌子。尝试这样做是错误的。

无内容的 FTS4 表还支持 SELECT 语句。然而,它是 尝试检索除 docid 列。可以使用辅助函数 matchinfo(),但 snippet() 和 offsets() 可能不会。例如:

sql 复制代码
-- The following statements are Ok:
SELECT docid FROM t1 WHERE t1 MATCH 'xxx';
SELECT docid FROM t1 WHERE a MATCH 'xxx';
SELECT matchinfo(t1) FROM t1 WHERE t1 MATCH 'xxx';

-- The following statements all cause errors, as the value of columns
-- other than docid are required to evaluate them.
SELECT * FROM t1;
SELECT a, b FROM t1 WHERE t1 MATCH 'xxx';
SELECT docid FROM t1 WHERE a LIKE 'xxx%';
SELECT snippet(t1) FROM t1 WHERE t1 MATCH 'xxx';

与尝试检索 docid 以外的列值相关的错误 是 sqlite3_step() 内发生的运行时错误。在某些情况下,对于 例如,如果 SELECT 查询中的 MATCH 表达式与零行匹配,则 即使语句确实引用了列值,也可能完全没有错误 除了 Docid。

6.2.2. 外部内容 FTS4 表

"外部内容"FTS4 表类似于无内容表,除了 如果查询的计算需要列的值,则 docid,FTS4 尝试从表(或视图,或 虚拟表)由用户指定(以下简称"内容" 表")。FTS4 模块从不写入内容表,并且写入 到内容表不影响全文索引。它是 用户有责任确保内容表和 全文索引是一致的。

通过设置内容选项创建外部内容 FTS4 表 更改为可查询的表(或视图或虚拟表)的名称 FTS4 在需要时检索列值。如果指定的表有 不存在,则外部内容表的行为方式与 一个无内容的表。例如:

sql 复制代码
CREATE TABLE t2(id INTEGER PRIMARY KEY, a, b, c);
CREATE VIRTUAL TABLE t3 USING fts4(content="t2", a, c);

假设指定表确实存在,则其列必须相同 作为为 FTS 表定义的超集或超集。外部表 还必须与 FTS 表位于同一数据库文件中。换言之, 外部表不能位于使用 ATTACH 连接的其他数据库文件中,FTS 表和外部内容之一也不能位于 当另一个位于持久性数据库文件(如 MAIN)中时,为 TEMP 数据库。

当用户对 FTS 表进行查询时,需要的列值不是 docid,FTS 尝试从 内容表中 rowid 值等于当前 FTS 的行 多西德。仅在 FTS/34 中重复的内容表列的子集 可以查询表声明 - 以从任何其他位置检索值 列 必须直接查询内容表。或者,如果这样的行不能 在内容表中找到,则改用 NULL 值。例如:

sql 复制代码
CREATE TABLE t2(id INTEGER PRIMARY KEY, a, b, c);
CREATE VIRTUAL TABLE t3 USING fts4(content="t2", b, c);

INSERT INTO t2 VALUES(2, 'a b', 'c d', 'e f');
INSERT INTO t2 VALUES(3, 'g h', 'i j', 'k l');
INSERT INTO t3(docid, b, c) SELECT id, b, c FROM t2;
-- The following query returns a single row with two columns containing
-- the text values "i j" and "k l".
--
-- The query uses the full-text index to discover that the MATCH
-- term matches the row with docid=3. It then retrieves the values
-- of columns b and c from the row with rowid=3 in the content table
-- to return.
--
SELECT * FROM t3 WHERE t3 MATCH 'k';

-- Following the UPDATE, the query still returns a single row, this
-- time containing the text values "xxx" and "yyy". This is because the
-- full-text index still indicates that the row with docid=3 matches
-- the FTS4 query 'k', even though the documents stored in the content
-- table have been modified.
--
UPDATE t2 SET b = 'xxx', c = 'yyy' WHERE rowid = 3;
SELECT * FROM t3 WHERE t3 MATCH 'k';

-- Following the DELETE below, the query returns one row containing two
-- NULL values. NULL values are returned because FTS is unable to find
-- a row with rowid=3 within the content table.
--
DELETE FROM t2;
SELECT * FROM t3 WHERE t3 MATCH 'k';

从外部内容 FTS4 表中删除行时,FTS4 需要 检索要从内容表中删除的行的列值。 这样 FTS4 就可以更新每个令牌的全文索引条目 在已删除的行中发生,以指示该行已 删除。如果找不到内容表行,或者它包含值 与FTS指数的内容不一致,结果可能很困难 来预测。FTS 指数可能保留包含对应于 已删除的行,这可能会导致返回看似荒谬的结果 通过后续的 SELECT 查询。更新行时也是如此,如 在内部,UPDATE 与 DELETE 后跟 INSERT 相同。

这意味着为了使 FTS 与外部内容保持同步 表中,任何 UPDATE 或 DELETE 操作都必须首先应用于 FTS 表,然后转到外部内容表。例如:

sql 复制代码
CREATE TABLE t1_real(id INTEGER PRIMARY KEY, a, b, c, d);
CREATE VIRTUAL TABLE t1_fts USING fts4(content="t1_real", b, c);

-- This works. When the row is removed from the FTS table, FTS retrieves
-- the row with rowid=123 and tokenizes it in order to determine the entries
-- that must be removed from the full-text index.
--
DELETE FROM t1_fts WHERE rowid = 123;
DELETE FROM t1_real WHERE rowid = 123;

-- This does not work. By the time the FTS table is updated, the row
-- has already been deleted from the underlying content table. As a result
-- FTS is unable to determine the entries to remove from the FTS index and
-- so the index and content table are left out of sync.
--
DELETE FROM t1_real WHERE rowid = 123;
DELETE FROM t1_fts WHERE rowid = 123;

而不是分别写入全文索引和内容表, 某些用户可能希望使用数据库触发器来保留全文索引 与存储在内容表中的文档集有关的最新信息。 例如,使用前面示例中的表:

sql 复制代码
CREATE TRIGGER t2_bu BEFORE UPDATE ON t2 BEGIN
  DELETE FROM t3 WHERE docid=old.rowid;
END;
CREATE TRIGGER t2_bd BEFORE DELETE ON t2 BEGIN
  DELETE FROM t3 WHERE docid=old.rowid;
END;

CREATE TRIGGER t2_au AFTER UPDATE ON t2 BEGIN
  INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c);
END;
CREATE TRIGGER t2_ai AFTER INSERT ON t2 BEGIN
  INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c);
END;

在实际删除发生之前,必须触发 DELETE 触发器 在内容表上。这样 FTS4 仍然可以检索原始文件 值,以便更新全文索引。并且 INSERT 触发器必须 在插入新行后触发,以便处理 rowid 在系统内自动分配。UPDATE 触发器必须 分为两部分,一部分在更新之前触发,一部分在更新后触发 内容表,出于同样的原因。

[FTS4 "rebuild" 命令](#FTS4 “rebuild” 命令)删除整个全文索引,并根据 内容表中的文档集。再次假设"t3"是 外部内容 FTS4 表的名称,则 rebuild 命令如下所示:

sql 复制代码
INSERT INTO t3(t3) VALUES('rebuild');

此命令也可用于普通的 FTS4 表,例如,如果 分词器的实现发生了变化。这是一个 尝试重建由 Contentless 维护的全文索引时出错 FTS4 表,因为没有内容可用于进行重建。

6.3. languageid= 选项

当 languageid 选项存在时,它指定 添加到 FTS4 的另一个隐藏列 表,用于指定存储在每行中的语言 的 FTS4 表。languageid 隐藏列的名称必须 与 FTS4 表中的所有其他列名不同。例:

sql 复制代码
CREATE VIRTUAL TABLE t1 USING fts4(x, y, languageid="lid")

languageid 列的默认值为 0。插入的任何值 into languageid 列转换为 32 位(不是 64 位)签名 整数。

默认情况下,FTS 查询(使用 MATCH 运算符的查询) 仅考虑 languageID 列设置为 0 的行。自 查询具有其他 languageID 值的行,这是 形式"

= "必须添加到查询中 WHERE 子句。例如:

sql 复制代码
SELECT * FROM t1 WHERE t1 MATCH 'abc' AND lid=5;

单个 FTS 查询无法返回具有 不同的 languageID 值。添加 WHERE 子句的结果 使用其他运算符(例如 lid!=5 或 lid<=5)是未定义的。

如果 content 选项与 languageid 选项一起使用, 则命名的 LanguageID 列必须存在于 Content= 表中 (受通常规则的约束 - 如果查询永远不需要读取 内容表,则此限制不适用)。

当使用 languageid 选项时,SQLite 调用 xLanguageid() 在紧跟在对象之后的sqlite3_tokenizer_module对象上 创建是为了传入语言 ID 分词器应该使用。xLanguageid() 方法永远不会被调用 对于任何单个 Tokenizer 对象,请多次使用。事实上,不同的 语言的标记化可能不同,这是为什么没有单一的原因之一 FTS 查询可以返回具有不同 languageid 值的行。

6.4. matchinfo= 选项

matchinfo 选项只能设置为值"fts3"。 尝试将 matchinfo 设置为"fts3"以外的任何值是错误的。 如果指定了此选项,则存储的一些额外信息由 FTS4 被省略。这样可以减少 FTS4 表,直到它与将要的金额几乎相同 被等效的FTS3表使用,也意味着数据 通过将"l"标志传递给 matchinfo() 函数来访问是 不可用。

6.5. notindexed= 选项

通常,FTS 模块维护 表的所有列。此选项用于指定 不应将条目添加到索引中的列。倍数 "notindexed"选项可用于指定多列应 从索引中省略。例如:

sql 复制代码
-- Create an FTS4 table for which only the contents of columns c2 and c4
-- are tokenized and added to the inverted index.
CREATE VIRTUAL TABLE t1 USING fts4(c1, c2, c3, c4, notindexed=c1, notindexed=c3);

存储在未编制索引的列中的值不符合匹配 MATCH 的条件 运营商。它们不会影响 offsets() 或 matchinfo() 的结果 辅助功能。snippet() 函数也不会返回 基于存储在未编制索引的列中的值的代码段。

6.6. prefix= 选项

FTS4 前缀选项使 FTS 索引指定长度的术语前缀 就像它总是索引完整的术语一样。前缀选项 必须设置为以逗号分隔的非零正整数列表。 对于列表中的每个值 N,长度为 N 字节的前缀(编码时 使用 UTF-8) 进行索引。FTS4 使用术语前缀索引来加快前缀查询速度。当然,代价是将术语前缀索引为 以及完整的术语会增加数据库大小并减慢写入速度 对 FTS4 表的操作。

在两种情况下,前缀索引可用于优化前缀查询。 如果查询是针对 N 个字节的前缀,则创建前缀索引 with "prefix=N" 提供最佳优化。或者,如果没有"prefix=N" 索引可用,可以使用"prefix=N+1"索引代替。 使用"prefix=N+1"索引较少 比"prefix=N"索引有效,但总比没有前缀索引要好。

sql 复制代码
-- Create an FTS4 table with indexes to optimize 2 and 4 byte prefix queries.
CREATE VIRTUAL TABLE t1 USING fts4(c1, c2, prefix="2,4");

-- The following two queries are both optimized using the prefix indexes.
SELECT * FROM t1 WHERE t1 MATCH 'ab*';
SELECT * FROM t1 WHERE t1 MATCH 'abcd*';

-- The following two queries are both partially optimized using the prefix
-- indexes. The optimization is not as pronounced as it is for the queries
-- above, but still an improvement over no prefix indexes at all.
SELECT * FROM t1 WHERE t1 MATCH 'a*';
SELECT * FROM t1 WHERE t1 MATCH 'abc*';

7. FTS3 和 FTS4 的特殊命令

特殊的 INSERT 操作可用于向 FTS3 和 FTS4 表发出命令。 每个 FTS3 和 FTS4 都有一个隐藏的只读列,该列的名称与 桌子本身。此隐藏列中的 INSERT 被解释为命令 到 FTS3/4 表。对于名称为"xyz"的表,请执行以下命令 支持:

  • 插入 xyz(xyz) VALUES('optimize');

  • 插入 XYZ(xyz) VALUES('重建');

  • 插入 XYZ(xyz) VALUES('完整性检查');

  • 插入 xyz(xyz) VALUES('merge=X,Y');

  • 插入 xyz(xyz) VALUES('automerge=N');

7.1. "optimize"命令

"optimize"命令使 FTS3/4 将其所有 将索引 B 树倒置为一个大而完整的 B 树。行为 优化将使后续查询运行得更快,因为有 需要搜索的 B 树更少,并且可以通过合并来减少磁盘使用量 冗余条目。但是,对于大型 FTS 表,运行 optimize 可能与运行 VACUUM 一样昂贵。optimize 命令 基本上必须读取和写入整个 FTS 表,导致 在大型交易中。

在批处理模式操作中,FTS 表最初构建 使用大量 INSERT 操作,然后重复查询 如果不进行进一步的更改,这通常是一个好主意 在最后一个 INSERT 之后和第一个查询之前运行"optimize"。

7.2. "rebuild"命令

"rebuild"命令导致SQLite丢弃整个FTS3/4 表,然后从原始文本重新构建它。概念 与 REINDEX类似,只是它适用于 FTS3/4 表代替普通索引。

每当实现时,都应运行"rebuild"命令 自定义标记器更改,以便可以重新标记所有内容。 在对原始内容进行更改后使用 FTS4 内容选项时,"重建"命令也很有用 内容表。

7.3. "integrity-check"命令

"integrity-check"命令使SQLite读取和验证 通过比较 FTS3/4 表中所有倒挂指数的准确性 那些与原始内容相反的倒挂索引。这 "integrity-check"命令静默成功,如果反转 索引都没问题,但会因SQLITE_CORRUPT错误而失败 如果发现任何问题。

"integrity-check"命令在概念上类似于 PRAGMA integrity_check。在工作系统中,"完整性命令" 应该总是成功的。完整性检查的可能原因 故障包括:

  • 应用程序直接对 [FTS 影子表](#FTS 影子表)进行了更改,而不使用 FTS3/4 虚拟表,导致 影子表彼此不同步。
  • 使用 FTS4 内容选项但无法手动保留 与 FTS4 倒挂指数同步的内容。
  • FTS3/4 虚拟表中的错误。("完整性检查" 命令最初是作为测试套件的一部分构想的 用于 FTS3/4。
  • 基础 SQLite 数据库文件损坏。(见 有关如何损坏和 SQLite 数据库的文档 其他信息。

7.4. "merge=X,Y"命令

"merge=X,Y"命令(其中 X 和 Y 是整数)会导致 SQLite 做有限的工作来合并各种倒置 将 FTS3/4 表的 b 树索引为一个大的 b 树。 X 值是要合并的"块"的目标数,Y 是 之前所需的级别上 B 树段的最小数量 合并将应用于该级别。Y 的值应 介于 2 和 16 之间,建议值为 8。X 的值 可以是任何正整数,但值在 100 到 300 之间 是推荐的。

当 FTS 表在同一级别累积 16 个 b 树段时, 该表的下一个 INSERT 将导致所有 16 个段 合并到下一个更高级别的单个 B 树段中。这 这些级别合并的效果是大多数 INSERT 进入 FTS 表 速度非常快,占用的内存最少,但偶尔的 INSERT 是 速度慢并生成大笔交易,因为需要 进行合并。这会导致 INSERT 的"尖峰"性能。

为了避免尖锐的 INSERT 性能,应用程序可以运行 "merge=X,Y" 命令,可能在空闲线程中或 空闲进程,确保FTS表永不累积 同一级别的 B 树段过多。INSERT 性能 通常可以避免尖峰,并且 FTS3/4 的性能可以 最大化,每隔几千次运行"merge=X,Y" 文档插页。每个"merge=X,Y"命令都将在单独的 事务(除非使用 BEGIN...提交, 当然)。通过选择一个值,可以保持交易规模较小 对于 100 到 300 范围内的 X。正在运行的空闲线程 合并命令可以通过检查差异来知道何时完成 在每个"merge=X,Y"之前和之后的 sqlite3_total_changes() 中 命令,并在差值降至 2 以下时停止循环。

7.5. "automerge=N"命令

"automerge=N"命令(其中 N 是介于 0 和 15 之间的整数, inclusive)用于配置 FTS3/4 表的"自动合并"参数, 它控制自动增量倒排索引合并。默认值 新表的自动合并值为 0,表示自动增量 合并是完全禁用的。如果 automerge 参数的值 使用"automerge=N"命令修改,新参数值为 永久存储在数据库中,并被随后的所有用户使用 已建立的数据库连接。

将 automerge 参数设置为非零值将启用自动 增量合并。这会导致 SQLite 执行少量反转 索引合并后执行每个 INSERT 操作。合并量 执行的设计使 FTS3/4 表永远不会达到一个点 它在同一级别有 16 个段,因此必须做很大的 合并以完成插入。换句话说,自动 增量合并旨在防止 INSERT 性能出现峰值。

自动增量合并的缺点是它使 FTS3/4表上的每个INSERT、UPDATE和DELETE操作都运行 稍微慢一点,因为必须使用额外的时间来执行增量操作 合并。为了获得最佳性能,建议应用程序 禁用自动增量合并,而是在空闲进程中使用"merge"命令来保留倒排索引 很好地融合了。但是,如果应用程序的结构不容易 允许空闲进程,使用自动增量合并是 一个非常合理的后备解决方案。

automerge 参数的实际值决定了 由自动倒排索引同时合并的索引段 合并。如果该值设置为 N,则系统将等待,直到 在开始增量之前,单个级别上至少有 N 个段 合并它们。设置较低的 N 值会导致段合并得更多 快速,这可能会加快全文查询的速度,如果工作量 包含 UPDATE 或 DELETE 操作以及 INSERT,减少空间 在全文索引使用的磁盘上。但是,它也增加了 写入磁盘的数据量。

在工作负载包含很少的 UPDATE 或 DELETE 的情况下用于一般用途 操作,自动合并的不错选择是 8。如果工作负载包含 许多 UPDATE 或 DELETE 命令, 或者,如果查询速度是一个问题,减少自动合并可能是有利的 至 2.

出于向后兼容性的原因,"automerge=1"命令将 automerge 参数设置为 8,而不是 1(值为 1 没有意义 无论如何,因为合并来自单个段的数据是无操作的)。

8. 分词器

FTS 分词器是一组用于从文档中提取术语的规则 或基本 FTS 全文查询。

除非将特定的分词器指定为 CREATE 的一部分 用于创建 FTS 表的 VIRTUAL TABLE 语句,默认 使用分词器"简单"。简单的分词器从中提取令牌 根据以下内容进行文档或基本 FTS 全文查询 规则:

  • 术语是符合条件的字符的连续序列,其中 符合条件的字符是所有字母数字字符和所有具有 Unicode 代码位值大于或等于 128。 所有其他字符都是 将文档拆分为术语时被丢弃。他们唯一的贡献是 以分隔相邻的术语。

  • ASCII 范围内的所有大写字符(Unicode 代码点 小于 128),作为一部分转换为其小写等价物 标记化过程。因此,全文查询是 使用简单分词器时不区分大小写。

例如,当包含文本"现在,他们非常 沮丧",从文档中提取并添加到 全文索引按顺序是"现在他们非常沮丧"。这样 文档将与全文查询匹配,例如"MATCH 'Frustrated'", 当简单的分词器将查询中的术语转换为小写时 在搜索全文索引之前。

除了"简单"的分词器外,FTS 源代码还具有分词器 使用波特 词干提取算法。此分词器使用相同的规则来分隔 将输入文档转换为术语,包括将所有术语折叠成小写, 但也使用 Porter Stemming 算法来减少相关的英语语言 词到一个共同的词根。例如,使用与 上面的段落,Porter Tokenizer 提取以下标记: "现在我非常沮丧"。即使其中一些术语不是偶数 英文单词,在某些情况下用它们来建立全文索引比较多 比简单的分词器产生的更易于理解的输出有用。 使用 porter 分词器,文档不仅匹配全文查询 例如"MATCH 'Frustrated'",还有诸如"MATCH 'Frustration'"之类的查询, 由于术语"挫折"被波特词干算法简化为 "沮丧"------就像"沮丧"一样。因此,在使用 porter 分词器时, FTS 不仅能够找到查询术语的完全匹配项,还能够找到匹配项 反对类似的英语语言术语。有关 Porter Stemmer 算法,请参考上面链接的页面。

说明"simple"和"porter"之间区别的示例 分词器:

sql 复制代码
-- Create a table using the simple tokenizer. Insert a document into it.
CREATE VIRTUAL TABLE simple USING fts3(tokenize=simple);
INSERT INTO simple VALUES('Right now they''re very frustrated');

-- The first of the following two queries matches the document stored in
-- table "simple". The second does not.
SELECT * FROM simple WHERE simple MATCH 'Frustrated';
SELECT * FROM simple WHERE simple MATCH 'Frustration';

-- Create a table using the porter tokenizer. Insert the same document into it
CREATE VIRTUAL TABLE porter USING fts3(tokenize=porter);
INSERT INTO porter VALUES('Right now they''re very frustrated');

-- Both of the following queries match the document stored in table "porter".
SELECT * FROM porter WHERE porter MATCH 'Frustrated';
SELECT * FROM porter WHERE porter MATCH 'Frustration';

如果此扩展是使用SQLITE_ENABLE_ICU预处理器编译的 符号定义,则存在一个名为"icu"的内置分词器 使用 ICU 库实现。传递给 此分词器的 xCreate() 方法(参见 fts3_tokenizer.h)可能是 ICU 区域设置标识符。例如,"tr_TR"表示土耳其语 在土耳其,或在澳大利亚使用的英语中为"en_AU"。例如:

sql 复制代码
CREATE VIRTUAL TABLE thai_text USING fts3(text, tokenize=icu th_TH)

ICU 分词器实现非常简单。它拆分输入 根据 ICU 规则查找单词边界和丢弃的文本 任何完全由空格组成的标记。这可能是合适的 适用于某些区域设置中的某些应用程序,但不是全部。如果更复杂 需要处理,例如实现词干提取或 丢弃标点符号,这可以通过创建分词器来完成 使用 ICU 分词器作为其实现的一部分的实现。

"unicode61"分词器从 SQLite 版本 3.7.13 (2012-06-11) 开始可用。 Unicode61 的工作方式与"简单"非常相似,只是它做了简单的 unicode 根据 Unicode 版本 6.1 中的规则折叠大小写,它可以识别 Unicode 空格和标点符号,并使用它们来分隔标记。 简单的分词器只做 ASCII 字符的大小写折叠,并且只做 将 ASCII 空格和标点符号字符识别为标记分隔符。

默认情况下,"unicode61"尝试从拉丁脚本中删除变音符号 字符。可以通过添加 tokenizer 参数来覆盖此行为 "remove_diacritics=0"。例如:

sql 复制代码
-- Create tables that remove alldiacritics from Latin script characters
-- as part of tokenization.
CREATE VIRTUAL TABLE txt1 USING fts4(tokenize=unicode61);
CREATE VIRTUAL TABLE txt2 USING fts4(tokenize=unicode61 "remove_diacritics=2");

-- Create a table that does not remove diacritics from Latin script
-- characters as part of tokenization.
CREATE VIRTUAL TABLE txt3 USING fts4(tokenize=unicode61 "remove_diacritics=0");

remove_diacritics选项可以设置为"0"、"1"或"2"。默认值 value 为"1"。如果设置为"1"或"2",则从 如上所述的拉丁字母字符。但是,如果设置为"1", 那么在相当罕见的情况下,变音符号不会被删除,其中单个 Unicode 代码点用于表示具有多个字符的字符 读音符号。例如,不会从代码点中删除变音符号0x1ED9 ("拉丁文小写字母 O,下面有 CIRCUMFLEX 和点")。从技术上讲,这是 一个错误,但如果不创建向后兼容性就无法修复 问题。如果此选项设置为"2",则音调符号正确 从所有拉丁字符中删除。

也可以自定义 unicode61 处理的代码点集 作为分隔符。"separators="选项可用于指定一个 或更多应被视为分隔符的额外字符,以及 "TokenChars="选项可用于指定一个或多个额外字符 应将其视为标记的一部分,而不是分隔符。 例如:

sql 复制代码
-- Create a table that uses the unicode61 tokenizer, but considers "."
-- and "=" characters to be part of tokens, and capital "X" characters to
-- function as separators.
CREATE VIRTUAL TABLE txt3 USING fts4(tokenize=unicode61 "tokenchars=.=" "separators=X");

-- Create a table that considers space characters (codepoint 32) to be
-- a token character
CREATE VIRTUAL TABLE txt4 USING fts4(tokenize=unicode61 "tokenchars= ");

如果将指定为"tokenchars="参数的一部分的字符被考虑 默认情况下,要成为标记字符,则将其忽略。即使有,这也是真的 被早期的"separators="选项标记为分隔符。同样,如果 指定为"separators="选项一部分的字符将被视为分隔符 字符,默认情况下,它被忽略。如果多个"tokenchars="或"separators=" 指定选项,所有选项都经过处理。例如:

sql 复制代码
-- Create a table that uses the unicode61 tokenizer, but considers "."
-- and "=" characters to be part of tokens, and capital "X" characters to
-- function as separators. Both of the "tokenchars=" options are processed
-- The "separators=" option ignores the "." passed to it, as "." is by
-- default a separator character, even though it has been marked as a token
-- character by an earlier "tokenchars=" option.
CREATE VIRTUAL TABLE txt5 USING fts4(
    tokenize=unicode61 "tokenchars=." "separators=X." "tokenchars=="
);

传递给"tokenchars="或"separators="选项的参数是 区分大小写。在上面的示例中,指定"X"是分隔符 字符不会影响"x"的处理方式。

8.1. 自定义(应用程序定义)分词器

除了提供内置的"简单"、"搬运工"和(可能)"icu"和 "Unicode61"分词器, FTS 为应用程序提供了一个接口,用于实现和注册自定义 用 C 语言编写的分词器。定义了用于创建新分词器的接口 并在 fts3_tokenizer.h 源文件中进行了描述。

注册新的 FTS 分词器类似于注册新的 FTS 分词器 带有 SQLite 的虚拟表模块。用户将指针传递给 包含指向各种回调函数的指针的结构,这些函数 构成新的分词器类型的实现。对于分词器, 结构(在 fts3_tokenizer.h 中定义)称为 "sqlite3_tokenizer_module"。

FTS 不会公开用户调用以注册新 具有数据库句柄的分词器类型。相反,指针必须 编码为 SQL blob 值,并通过 SQL 传递到 FTS 引擎通过计算一个特殊的标量函数"fts3_tokenizer()"。 fts3_tokenizer() 函数可以用一个或两个参数调用, 如下:

sql 复制代码
SELECT fts3_tokenizer(<tokenizer-name>);
SELECT fts3_tokenizer(<tokenizer-name>, <sqlite3_tokenizer_module ptr>);

其中 <tokenizer-name> 是使用 sqlite3_bind_text() 绑定字符串的参数,其中字符串标识分词器和 <sqlite3_tokenizer_module ptr> 是 BLOB 所属的参数 使用 sqlite3_bind_blob() 绑定,其中 BLOB 的值为 指向sqlite3_tokenizer_module结构的指针。 如果存在第二个参数, 它被注册为 tokenizer <tokenizer-name> 及其副本 返回。如果只传递一个参数,则指针指向分词器 返回当前注册为 <tokenizer-name> 的实现, 编码为 Blob。或者,如果不存在此类分词器,则为 SQL 异常 (错误)被提出。

在 SQLite 版本 3.11.0 (2016-02-15) 之前,参数 fts3_tokenizer() 可以是文本字符串或 BLOB。他们不必这样做 be 绑定参数。但这可能会导致安全问题 SQL 注入的事件。因此,旧行为现在被禁用 默认情况下。但是可以启用旧的遗留行为,以便向后 在真正需要它的应用程序中的兼容性, 通过调用 sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,1,0)。

以下块包含调用 fts3_tokenizer() 的示例 来自 C 代码的函数:

cpp 复制代码
/*
** Register a tokenizer implementation with FTS3 or FTS4.
*/
int registerTokenizer(
  sqlite3 *db,
  char *zName,
  const sqlite3_tokenizer_module *p
){
  int rc;
  sqlite3_stmt *pStmt;
  const char *zSql = "SELECT fts3_tokenizer(?1, ?2)";

  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  if( rc!=SQLITE_OK ){
    return rc;
  }

  sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
  sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC);
  sqlite3_step(pStmt);

  return sqlite3_finalize(pStmt);
}

/*
** Query FTS for the tokenizer implementation named zName.
*/
int queryTokenizer(
  sqlite3 *db,
  char *zName,
  const sqlite3_tokenizer_module **pp
){
  int rc;
  sqlite3_stmt *pStmt;
  const char *zSql = "SELECT fts3_tokenizer(?)";

  *pp = 0;
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  if( rc!=SQLITE_OK ){
    return rc;
  }

  sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
  if( SQLITE_ROW==sqlite3_step(pStmt) ){
    if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){
      memcpy(pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp));
    }
  }

  return sqlite3_finalize(pStmt);
}

8.2. 查询分词器

"fts3tokenize"虚拟表可用于直接访问任何 分词器。以下 SQL 演示了如何创建实例 fts3tokenize 虚拟表:

sql 复制代码
CREATE VIRTUAL TABLE tok1 USING fts3tokenize('porter');

应用所需分词器的名称代替 当然,示例中的"porter"。如果分词器需要一个或 更多参数,它们应该在 fts3tokenize 中用逗号分隔 声明(即使它们在声明中用空格分隔 常规 fts4 表)。以下创建 fts4 和 fts3tokenize 使用相同分词器的表:

sql 复制代码
CREATE VIRTUAL TABLE text1 USING fts4(tokenize=icu en_AU);
CREATE VIRTUAL TABLE tokens1 USING fts3tokenize(icu, en_AU);

CREATE VIRTUAL TABLE text2 USING fts4(tokenize=unicode61 "tokenchars=@." "separators=123");
CREATE VIRTUAL TABLE tokens2 USING fts3tokenize(unicode61, "tokenchars=@.", "separators=123");

创建虚拟表后,可以按如下方式查询:

sql 复制代码
SELECT token, start, end, position
  FROM tok1
 WHERE input='This is a test sentence.';

虚拟表将为 输入字符串。"token"列是令牌的文本。"开始" 和 "end" 列是 开始和结束的字节偏移量 token 在原始输入字符串中。 "position"列是序列号 原始输入字符串中的令牌。还有一个"输入" 列,它只是 WHERE 子句。请注意,形式为 "input=?" 的约束必须 出现在 WHERE 子句中,否则虚拟表将没有输入 进行标记化,并且不返回任何行。上面的示例生成 以下输出:

bash 复制代码
thi|0|4|0
is|5|7|1
a|8|9|2
test|10|14|3
sentenc|15|23|4

请注意,来自 fts3tokenize 虚拟的结果集中的令牌 表已根据分词器的规则进行了转换。 由于此示例使用了"porter"分词器,因此"This"令牌是 转换为"thi"。如果需要令牌的原始文本, 可以使用带有 substr() 函数的 "start" 和 "end" 列来检索它。例如:

sql 复制代码
SELECT substr(input, start+1, end-start), token, position
  FROM tok1
 WHERE input='This is a test sentence.';

fts3tokenize 虚拟表可以在任何分词器上使用,无论 是否存在实际使用的 FTS3 或 FTS4 表 那个分词器。

9. 数据结构

本节简要介绍了 FTS 模块存储其 数据库中的索引和内容。没有必要阅读或 了解本节中的材料,以便在 应用。但是,对于尝试尝试的应用程序开发人员来说,它可能很有用 分析和了解 FTS 性能特征,或向开发人员提供 考虑对现有 FTS 功能集进行增强。

9.1. 影子表

对于数据库中的每个 FTS 虚拟表,三到五个真实(非虚拟)表 用于存储基础数据。这些实际表称为"影子表"。 实际表命名为"%_content", "%_segdir"、"%_segments"、"%_stat"和"%_docsize",其中"%"替换为名称 FTS 虚拟表。

"%_content" 表的最左边的列是 INTEGER PRIMARY KEY 字段 命名为"docid"。接下来是 FTS 每列的一列 由用户声明的虚拟表,以列名为前缀命名 由用户提供 "cN ",其中 N 是 表中的列,从左到右编号,从 0 开始。数据 作为虚拟表声明的一部分提供的类型不用作 %_content 表声明的一部分。例如:

sql 复制代码
-- Virtual table declaration
CREATE VIRTUAL TABLE abc USING fts4(a NUMBER, b TEXT, c);

-- Corresponding %_content table declaration
CREATE TABLE abc_content(docid INTEGER PRIMARY KEY, c0a, c1b, c2c);

%_content 表包含用户插入的纯数据 由用户放入 FTS 虚拟表中。如果用户没有显式 插入记录时提供"docid"值,系统会自动选择一个 由系统。

%_stat 和 %_docsize 表仅在 FTS 表使用 FTS4 模块,而不是 FTS3。此外_docsize,如果 FTS4 表是使用"matchinfo=fts3"指令创建的 指定为 CREATE VIRTUAL TABLE 语句的一部分。如果它们被创建, 这两个表的架构如下所示:

sql 复制代码
CREATE TABLE %_stat(
  id INTEGER PRIMARY KEY,
  value BLOB
);

CREATE TABLE %_docsize(
  docid INTEGER PRIMARY KEY,
  size BLOB
);

对于 FTS 表中的每一行,%_docsize 表都包含相应的 具有相同"docid"值的行。"size"字段包含一个 blob,包括 的 N 个 FTS 变量,其中 N 是用户定义的列数 在表中。"size" blob 中的每个变量都是 FTS 表中关联行的相应列。%_stat 表 始终包含"id"列设置为 0 的单行。"价值" 列包含由 N+1 个 FTS 变量组成的 Blob,其中 N 再次是 FTS 表中用户定义的列数。第一个 blob 中的 varint 设置为 FTS 表中的总行数。这 第二个和后续的 varint 包含存储在 FTS 表中所有行的相应列。

剩下的两个表 %_segments 和 %_segdir 用于存储 全文索引。从概念上讲,此索引是一个查找表,用于映射每个 term (word) 设置为与 %_content 包含一个或多个术语出现项的表。自 检索包含指定术语 FTS 模块的所有文档 查询此索引以确定以下记录的 docid 值集 包含术语,然后从 %_content 桌子。无论 FTS 虚拟表的架构如何,%_segments 和 %_segdir 表始终按如下方式创建:

sql 复制代码
CREATE TABLE %_segments(
  blockid INTEGER PRIMARY KEY,       -- B-tree node id
  block blob                         -- B-tree node data
);

CREATE TABLE %_segdir(
  level INTEGER,
  idx INTEGER,
  start_block INTEGER,               -- Blockid of first node in %_segments
  leaves_end_block INTEGER,          -- Blockid of last leaf node in %_segments
  end_block INTEGER,                 -- Blockid of last node in %_segments
  root BLOB,                         -- B-tree root node
  PRIMARY KEY(level, idx)
);

上面描述的架构不是为存储全文索引而设计的 径直。相反,它用于存储一个或多个 b 树结构。那里 是 %_segdir 表中每行的一个 B 树。%_segdir 表 行包含根节点和与 b 树结构,并且 %_segments 表包含所有其他(非根) B 树节点。每个 b 树称为一个"段"。一旦它有 创建后,段 B 树永远不会更新(尽管它可能是 完全删除)。

每个段 b 树使用的键是术语(单词)。以及 键,每个段 B 树条目都有一个关联的"文档列表"(文档列表)。 文档列表由零个或多个条目组成,其中每个条目由以下部分组成:

  • docid(文档 ID)和
  • 术语偏移量列表,每个术语在内出现一个 文档。术语偏移量表示标记(字)的数量 出现在相关术语之前,而不是字符数 或字节。例如,术语"战争"的术语偏移量 短语"祖先的声音预言战争!"是 3.

文档列表中的条目按 docid 排序。文档列表中的职位 条目按升序存储。

逻辑全文索引的内容是通过合并 所有段 B 树的内容。如果一个术语存在于多个术语中 段 b 树,然后它映射到每个单独文档列表的并集。如果 对于单个术语,相同的 docid 出现在多个 doclist 中,然后仅出现在多个 doclist 中 作为最近创建的段 B 树的一部分的文档列表是 被认为是有效的。

使用多个 b 树结构而不是单个 b 树来减少 将记录插入 FTS 表的成本。当新记录是 插入到已经包含大量数据的 FTS 表中,它是 新记录中的许多术语可能已经存在于 大量现有记录。如果使用单个 b 树,则 大型文档列表结构必须从数据库中加载, 修改以包含新的 docid 和术语偏移列表,然后写回 到数据库。使用多个 b 树表可以避免这种情况 通过创建一个可以与现有 B 树合并的新 B 树 (或 b 树)稍后。b树结构的合并可以执行为: 后台任务,或者一次一定数量的单独 B 树结构 已经积累起来。当然,此方案会使查询更加昂贵 (因为 FTS 代码可能必须在多个术语中查找单个术语 b树并合并结果),但已经发现在实践中 开销通常可以忽略不计。

9.2. 可变长度整数 (varint) 格式

存储为段 b 树节点一部分的整数值使用 FTS 变量格式。此编码类似于,但不完全相同 SQLite 变体格式

编码的 FTS 变量占用 1 到 10 个字节的空间。这 所需的字节数由 编码的整数值。更准确地说,是用于存储的字节数 编码的整数取决于最高有效设置位的位置 在整数值的 64 位二进制补码表示形式中。阴性 值始终设置了最高有效位(符号位),也是如此 始终使用完整的 10 个字节进行存储。正整数值可以是 使用更少的空间进行存储。

编码的 FTS 变量的最后一个字节具有其最高有效位 清除。前面的所有字节都设置了最高有效位。数据 存储在每个字节的其余七个最低有效位中。 编码表示的第一个字节包含最低有效字节 编码整数值的 7 位。编码的第二个字节 表示形式(如果存在)包含七个下一个最不重要的表示 整数值的位,依此类推。下表包含示例 编码的整数值:

十进制 十六进制 编码表示
43 0x000000000000002B 0x2B
200815 0x000000000003106F 0xEF 0xA0 0x0C
-1 0xFFFFFFFFFFFFFFFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x01

9.3. 段B树格式

段 b 树是前缀压缩的 b+树。有一个段 b 树 对于 %_segdir 表中的每一行(见上文)。段的根节点 b-tree 作为 blob 存储在相应行的"root"字段中 的 %_segdir 表。所有其他节点(如果存在)都存储在 %_segments 表的"blob"列。%_segments 表中的节点是 由对应 BlockID 字段中的整数值标识 排。下表描述了 %_segdir 表的字段:

解释
水平 在它们之间,"level"和"idx"字段的内容定义了 段 B 树的相对年龄。存储在 "level"字段,则最近创建段 b 树的时间越大。如果两个 段 B 树属于相同的"级别",段越大 存储在"idx"列中的值是最近的。PRIMARY KEY 约束 在 %_segdir 表上,防止任何两个段具有相同的值 对于"level"和"idx"字段。
IDX公司 见上文。
start_block 对应于具有最小 blockid 的节点的 blockid,该节点 属于此段 B 树。如果整个段 b 树为零,则为零 适合根节点。如果存在,则此节点始终是叶节点。
leaves_end_block 对应于具有最大 blockid 的叶节点的 blockid 属于此段 B 树。如果整个段 b 树为零,则为零 适合根节点。
end_block 此字段可以包含整数或文本字段,其中包含以下内容: 两个整数,用空格字符分隔(Unicode Codepoint 0x20)。 第一个或唯一的整数是对应于内部的 blockid 具有属于此段 B 树的最大 blockID 的节点。或零 如果整个段 B 树适合根节点。如果存在,则此节点 始终是一个内部节点。 第二个整数(如果存在)是所有数据的聚合大小 以字节为单位存储在叶页上。如果该值为负数,则段 是未完成的增量合并操作的输出,而 绝对值是当前大小(以字节为单位)。
包含段 b 树的根节点的 Blob。

除了根节点之外,构成单个段 b 树的节点是 始终使用连续的 blockID 序列进行存储。此外, 构成 B 树的单个级别的节点本身存储为 一个连续的块,按 B 树顺序排列。块的连续序列 用于存储 B 树叶的分配从 blockID 开始 值存储在相应 %_segdir 行的"start_block"列中, 并以存储在"leaves_end_block"中的 blockid 值结束 字段。因此,可以遍历所有 段 B 树的叶子,按键顺序,通过遍历 %_segments 按 blockid 顺序从"start_block"到"leaves_end_block"的表。

9.3.1. 段B树叶节点

下图描述了段 b 树叶节点的格式。

段 B 树叶节点格式

存储在每个节点上的第一个项(上图中的"项 1")是 逐字存储。每个后续术语都是前缀压缩的 到它的前身。术语以 sorted (memcmp) 的形式存储在页面中 次序。

9.3.2. 段B树内部节点

下图描述了段 b 树内部的格式 (非叶)节点。

段 B 树内部节点格式

9.4. 文档列表格式

文档列表由一个 64 位有符号整数数组组成,使用 FTS 变量格式。每个文档列表条目都由两个系列组成 或更多整数,如下所示:

  1. docid 值。doclist 中的第一个条目包含文本 docid 价值。每个后续 doclist 条目的第一个字段包含 新的 docid 和以前的 docid 之间的区别(始终为正数) 数字)。
  2. 零个或多个术语偏移列表。每个术语都有一个术语偏移列表 包含术语的 FTS 虚拟表的列。期限偏移 列表由以下内容组成:
    1. 常量值 1。对于任何术语偏移列表,此字段都将被省略 与列 0 关联。
    2. 列号(1 表示最左边的第二列,以此类推)。这 对于与列 0 关联的任何术语偏移列表,字段将被省略。
    3. 术语偏移量列表,按从小到大排序。相反 从字面上存储项偏移值,每个整数存储 是当前项偏移量与前一个项偏移量之间的差值 1(如果当前项偏移量是第一个,则为零)加上 2。
  3. 常量值 0。

FTS3 文档列表格式

FTS 文档列表条目格式

对于该术语出现在 FTS 的多个列中的文档列表 虚拟表,文档列表中的术语偏移列表存储在列中 数字顺序。这可确保与 列 0(如果有)始终排在第一位,允许 在这种情况下,将省略术语偏移列表。

10. 限制

10.1. UTF-16字节顺序标记问题

对于 UTF-16 数据库,当使用"简单"分词器时,可以使用 格式错误的 Unicode 字符串导致 integrity-check 特殊命令错误报告 损坏,或返回辅助功能 结果不正确。更具体地说,该错误可由以下任何一种触发:

  • UTF-16 字节顺序标记 (BOM) 嵌入在 SQL 字符串的开头 插入到 FTS3 表中的文本值。例如:

    sql 复制代码
    INSERT INTO fts_table(col) VALUES(char(0xfeff)||'text...');
  • SQLite 转换为 UTF-16 字节顺序标记的格式错误的 UTF-8 是 嵌入在插入的 SQL 字符串文本值的开头 到 FTS3 表中。

  • 通过强制转换以 2 开头的 blob 创建的文本值 字节 0xFF 和 0xFE,以任一可能的顺序插入到 FTS3 表。例如:

    sql 复制代码
    INSERT INTO fts_table(col) VALUES(CAST(X'FEFF' AS TEXT));

如果满足以下任一条件,则一切正常:

  • 数据库编码为 UTF-8。
  • 所有文本字符串都是使用 sqlite3_bind_text() 系列函数之一插入的。
  • 文本字符串不包含字节顺序标记。
  • 使用识别字节顺序标记的分词器 作为空格。(默认的"简单"分词器 FTS3/4 不认为 BOM 是空格,而是 Unicode 分词器可以。

上述所有条件都必须为假才能出现问题 发生。即使上述所有条件都是假的, 大多数事情仍然可以正确操作。只有 integrity-check 命令和辅助函数可以给出 意想不到的结果。

附录 A:搜索应用程序提示

FTS 主要设计用于支持布尔全文查询 - 查询 查找与指定条件匹配的文档集。然而,许多 (大多数?搜索应用程序要求以某种方式对结果进行排序 的"相关性",其中"相关性"被定义为用户的可能性 执行搜索的人对返回的特定元素感兴趣 一套文件。使用搜索引擎查找世界上的文档时 在全网中,用户期望最有用或"相关"的文档 将作为结果的第一页返回,并且每个后续页面 包含逐渐不太相关的结果。机器究竟如何 根据用户查询确定文档相关性是一个复杂的问题 以及许多正在进行的研究的主题。

一个非常简单的方案可能是计算 用户在每个结果文档中搜索术语。包含以下内容的文档 这些术语的许多实例被认为比那些具有 每个术语的少量实例。在 FTS 应用程序中, 每个结果中的术语实例数可以通过计数来确定 offsets 函数的返回值中的整数数。 下面的示例演示了一个查询,该查询可用于获取 用户输入的查询的十个最相关的结果:

sql 复制代码
-- This example (and all others in this section) assumes the following schema
CREATE VIRTUAL TABLE documents USING fts3(title, content);

-- Assuming the application has supplied an SQLite user function named "countintegers"
-- that returns the number of space-separated integers contained in its only argument,
-- the following query could be used to return the titles of the 10 documents that contain
-- the greatest number of instances of the users query terms. Hopefully, these 10
-- documents will be those that the users considers more or less the most "relevant".
SELECT title FROM documents
  WHERE documents MATCH <query>
  ORDER BY countintegers(offsets(documents)) DESC
  LIMIT 10 OFFSET 0

通过使用 FTS matchinfo 函数确定每个查询中出现的查询词实例数,可以使上述查询运行得更快 结果。matchinfo 函数比偏移量高效得多 功能。此外,matchinfo 函数提供额外信息 关于每个查询词在整体中的出现总数 文档集(不仅仅是当前行)以及其中的文档数 将显示每个查询词。这可用于(例如)附加更高的 权重到不太常见的术语,这可能会增加整体计算的相关性 在这些结果中,用户认为更有趣。

sql 复制代码
-- If the application supplies an SQLite user function called "rank" that
-- interprets the blob of data returned by matchinfo and returns a numeric
-- relevancy based on it, then the following SQL may be used to return the
-- titles of the 10 most relevant documents in the dataset for a users query.
SELECT title FROM documents
  WHERE documents MATCH <query>
  ORDER BY rank(matchinfo(documents)) DESC
  LIMIT 10 OFFSET 0

上面示例中的 SQL 查询使用的 CPU 比第一个示例少 在本节中,但仍然有一个不明显的性能问题。SQLite的 通过检索"title"列的值来满足此查询,并且 来自 FTS 模块的用户匹配的每一行的 matchinfo 数据 在对结果进行排序和限制之前进行查询。因为 SQLite 的方式 虚拟表接口工作,检索"title"列的值 需要从磁盘加载整行(包括"内容"字段, 这可能相当大)。这意味着,如果用户查询匹配 数千个文档,数兆字节的"标题"和"内容"数据 可以从磁盘加载到内存中,即使它们永远不会被使用 出于任何目的。

以下示例块中的 SQL 查询是解决此问题的一种解决方案 问题。在 SQLite 中,当子查询 用于包含 LIMIT 子句的联接,子查询的结果是 在执行主查询之前计算并存储在临时表中。 这意味着 SQLite 将仅加载每个 docid 和 matchinfo 数据 将用户查询匹配到内存中的行,确定 docid 值 对应十个最相关的文档,然后只加载标题 以及仅这 10 个文档的内容信息。因为 matchinfo docid 值完全从全文索引中收集,结果如下 从数据库加载到内存中的数据大大减少。

sql 复制代码
SELECT title FROM documents JOIN (
    SELECT docid, rank(matchinfo(documents)) AS rank
    FROM documents
    WHERE documents MATCH <query>
    ORDER BY rank DESC
    LIMIT 10 OFFSET 0
) AS ranktable USING(docid)
ORDER BY ranktable.rank DESC

下一个 SQL 块通过解决另外两个问题来增强查询 在使用 FTS 开发搜索应用程序时可能会出现以下问题:

  1. snippet 函数不能与上述查询一起使用。因为 外部查询不包括"WHERE ...MATCH"子句,代码段 函数不能与它一起使用。一种解决方案是复制 WHERE 子句在外部查询中使用。关联的开销 这通常可以忽略不计。

  2. 文档的相关性可能取决于其他因素 matchinfo 的返回值中可用的数据。例如 数据库中的每个文档都可以根据静态权重分配 与其内容无关的因素(来源、作者、年龄、编号) 参考文献等)。这些值可以由应用程序存储 在可以与文档表联接的单独表中 ,以便 rank 函数可以访问它们。

此版本的查询与 sqlite.org 文档搜索应用程序使用的查询版本非常相似。

sql 复制代码
-- This table stores the static weight assigned to each document in FTS table
-- "documents". For each row in the documents table there is a corresponding row
-- with the same docid value in this table.
CREATE TABLE documents_data(docid INTEGER PRIMARY KEY, weight);

-- This query is similar to the one in the block above, except that:
--
--   1. It returns a "snippet" of text along with the document title for display. So
--      that the snippet function may be used, the "WHERE ... MATCH ..." clause from
--      the sub-query is duplicated in the outer query.
--
--   2. The sub-query joins the documents table with the document_data table, so that
--      implementation of the rank function has access to the static weight assigned
--      to each document.
SELECT title, snippet(documents) FROM documents JOIN (
    SELECT docid, rank(matchinfo(documents), documents_data.weight) AS rank
    FROM documents JOIN documents_data USING(docid)
    WHERE documents MATCH <query>
    ORDER BY rank DESC
    LIMIT 10 OFFSET 0
) AS ranktable USING(docid)
WHERE documents MATCH <query>
ORDER BY ranktable.rank DESC

上述所有示例查询都返回十个最相关的查询结果。 通过修改用于 OFFSET 和 LIMIT 子句的值,查询 返回(比如)接下来的十个最相关的结果很容易构建。 这可用于获取搜索应用程序所需的数据 以及后续结果页面。

下一个块包含一个使用 matchinfo 数据的示例 rank 函数 在 C 语言中实现。它不是单个权重,而是允许权重 外部分配给每个文档的每一列。它可以被注册 与SQLite一样,使用sqlite3_create_function的任何其他用户函数。

**安全警告:**因为它只是一个普通的SQL函数, rank() 可以在任何上下文中作为任何 SQL 查询的一部分调用。这意味着 传递的第一个参数可能不是有效的 MatchInfo Blob。 实现者应注意处理这种情况,而不会造成缓冲 超支或其他潜在的安全问题。

cpp 复制代码
/*
** SQLite user defined function to use with matchinfo() to calculate the
** relevancy of an FTS match. The value returned is the relevancy score
** (a real value greater than or equal to zero). A larger value indicates 
** a more relevant document.
**
** The overall relevancy returned is the sum of the relevancies of each 
** column value in the FTS table. The relevancy of a column value is the
** sum of the following for each reportable phrase in the FTS query:
**
**   (<hit count> / <global hit count>) * <column weight>
**
** where <hit count> is the number of instances of the phrase in the
** column value of the current row and <global hit count> is the number
** of instances of the phrase in the same column of all rows in the FTS
** table. The <column weight> is a weighting factor assigned to each
** column by the caller (see below).
**
** The first argument to this function must be the return value of the FTS 
** matchinfo() function. Following this must be one argument for each column 
** of the FTS table containing a numeric weight factor for the corresponding 
** column. Example:
**
**     CREATE VIRTUAL TABLE documents USING fts3(title, content)
**
** The following query returns the docids of documents that match the full-text
** query <query> sorted from most to least relevant. When calculating
** relevance, query term instances in the 'title' column are given twice the
** weighting of those in the 'content' column.
**
**     SELECT docid FROM documents 
**     WHERE documents MATCH <query> 
**     ORDER BY rank(matchinfo(documents), 1.0, 0.5) DESC
*/
static void rankfunc(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){
  int *aMatchinfo;                /* Return value of matchinfo() */
  int nMatchinfo;                 /* Number of elements in aMatchinfo[] */
  int nCol = 0;                   /* Number of columns in the table */
  int nPhrase = 0;                /* Number of phrases in the query */
  int iPhrase;                    /* Current phrase */
  double score = 0.0;             /* Value to return */

  assert( sizeof(int)==4 );

  /* Check that the number of arguments passed to this function is correct.
  ** If not, jump to wrong_number_args. Set aMatchinfo to point to the array
  ** of unsigned integer values returned by FTS function matchinfo. Set
  ** nPhrase to contain the number of reportable phrases in the users full-text
  ** query, and nCol to the number of columns in the table. Then check that the
  ** size of the matchinfo blob is as expected. Return an error if it is not.
  */
  if( nVal<1 ) goto wrong_number_args;
  aMatchinfo = (unsigned int *)sqlite3_value_blob(apVal[0]);
  nMatchinfo = sqlite3_value_bytes(apVal[0]) / sizeof(int);
  if( nMatchinfo>=2 ){
    nPhrase = aMatchinfo[0];
    nCol = aMatchinfo[1];
  }
  if( nMatchinfo!=(2+3*nCol*nPhrase) ){
    sqlite3_result_error(pCtx,
      "invalid matchinfo blob passed to function rank()", -1);
    return;
  }
  if( nVal!=(1+nCol) ) goto wrong_number_args;

  /* Iterate through each phrase in the users query. */
  for(iPhrase=0; iPhrase<nPhrase; iPhrase++){
    int iCol;                     /* Current column */

    /* Now iterate through each column in the users query. For each column,
    ** increment the relevancy score by:
    **
    **   (<hit count> / <global hit count>) * <column weight>
    **
    ** aPhraseinfo[] points to the start of the data for phrase iPhrase. So
    ** the hit count and global hit counts for each column are found in 
    ** aPhraseinfo[iCol*3] and aPhraseinfo[iCol*3+1], respectively.
    */
    int *aPhraseinfo = &aMatchinfo[2 + iPhrase*nCol*3];
    for(iCol=0; iCol<nCol; iCol++){
      int nHitCount = aPhraseinfo[3*iCol];
      int nGlobalHitCount = aPhraseinfo[3*iCol+1];
      double weight = sqlite3_value_double(apVal[iCol+1]);
      if( nHitCount>0 ){
        score += ((double)nHitCount / (double)nGlobalHitCount) * weight;
      }
    }
  }

  sqlite3_result_double(pCtx, score);
  return;

  /* Jump here if the wrong number of arguments are passed to this function */
wrong_number_args:
  sqlite3_result_error(pCtx, "wrong number of arguments to function rank()", -1);
}
相关推荐
Mr.1313 分钟前
数据库的三范式是什么?
数据库
yuyanjingtao15 分钟前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
Cachel wood19 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Python之栈26 分钟前
【无标题】
数据库·python·mysql
闻缺陷则喜何志丹31 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
风_流沙38 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
charlie11451419142 分钟前
C++ STL CookBook
开发语言·c++·stl·c++20
亽仒凣凣1 小时前
Windows安装Redis图文教程
数据库·windows·redis
小林熬夜学编程1 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
亦世凡华、1 小时前
MySQL--》如何在MySQL中打造高效优化索引
数据库·经验分享·mysql·索引·性能分析