我正在尝试检索first名称中包含该字符串的所有任务文档。
我目前有以下代码,但只有在我传递确切名称时才有效:
res, err := db.client.Query(
f.Map(
f.Paginate(f.MatchTerm(f.Index("tasks_by_name"), "My first task")),
f.Lambda("ref", f.Get(f.Var("ref"))),
),
)
Run Code Online (Sandbox Code Playgroud)
我想我可以在ContainsStr()某处使用,但我不知道如何在查询中使用它。
另外,有没有办法在不使用的情况下做到这一点Filter()?我问是因为它似乎在分页后过滤,并且弄乱了页面
Bre*_*oms 18
FaunaDB 提供了很多结构,这使得它功能强大,但您有很多选择。强大的力量带来了一个小的学习曲线:)。
为了清楚起见,我在这里使用了 FQL 的 JavaScript 风格,并且通常从 JavaScript驱动程序公开 FQL 函数,如下所示:
const faunadb = require('faunadb')
const q = faunadb.query
const {
Not,
Abort,
...
} = q
Run Code Online (Sandbox Code Playgroud)
像这样导出 Map 时必须小心,因为它会与 JavaScript 映射冲突。在这种情况下,您可以使用 q.Map。
根据文档的基本用法
ContainsStr('Fauna', 'a')
Run Code Online (Sandbox Code Playgroud)
当然,这适用于特定值,因此为了使其工作,您需要Filter 和 Filter 仅适用于分页集。这意味着我们首先需要获得一个分页集。获取分页文档集的一种方法是:
q.Map(
Paginate(Documents(Collection('tasks'))),
Lambda(['ref'], Get(Var('ref')))
)
Run Code Online (Sandbox Code Playgroud)
但是我们可以更有效地做到这一点,因为一个 get === one read 并且我们不需要文档,我们将过滤掉很多文档。有趣的是,一个索引页也是一次阅读,因此我们可以如下定义索引:
{
name: "tasks_name_and_ref",
unique: false,
serialized: true,
source: "tasks",
terms: [],
values: [
{
field: ["data", "name"]
},
{
field: ["ref"]
}
]
}
Run Code Online (Sandbox Code Playgroud)
由于我们向值添加了 name 和 ref,索引将返回 name 和 ref 的页面,然后我们可以使用它们进行过滤。例如,我们可以对索引做类似的事情,映射它们,这将返回一个布尔数组。
Map(
Paginate(Match(Index('tasks_name_and_ref'))),
Lambda(['name', 'ref'], ContainsStr(Var('name'), 'first'))
)
Run Code Online (Sandbox Code Playgroud)
由于 Filter 也适用于数组,我们实际上可以简单地将Map替换为 filter。我们还将添加小写字母以忽略大小写,我们有我们需要的:
Filter(
Paginate(Match(Index('tasks_name_and_ref'))),
Lambda(['name', 'ref'], ContainsStr(LowerCase(Var('name')), 'first'))
)
Run Code Online (Sandbox Code Playgroud)
就我而言,结果是:
{
"data": [
[
"Firstly, we'll have to go and refactor this!",
Ref(Collection("tasks"), "267120709035098631")
],
[
"go to a big rock-concert abroad, but let's not dive in headfirst",
Ref(Collection("tasks"), "267120846106001926")
],
[
"The first thing to do is dance!",
Ref(Collection("tasks"), "267120677201379847")
]
]
}
Run Code Online (Sandbox Code Playgroud)
正如您所提到的,这并不是您想要的,因为这也意味着如果您请求大小为 500 的页面,它们可能会被过滤掉,您最终可能会得到大小为 3 的页面,然后是 7 中的一个。您可能会认为,为什么我不能在页面中获取我过滤的元素?嗯,出于性能原因,这是一个好主意,因为它基本上会检查每个值。想象一下,您有一个庞大的集合并过滤掉了 99.99%。您可能需要遍历许多元素才能达到 500 个,所有成本都读取。我们希望定价是可预测的:)。
每次你想要做一些更有效的事情时,答案就在于索引。FaunaDB 为您提供了实施不同搜索策略的原始能力,但您必须要有一点创意,我在这里为您提供帮助:)。
在索引绑定中,您可以转换文档的属性,在我们的第一次尝试中,我们会将字符串拆分为单词(我将实现多个,因为我不完全确定您想要哪种匹配)
我们没有字符串拆分函数,但由于 FQL 很容易扩展,我们可以自己编写它绑定到我们的宿主语言中的变量(在本例中为 javascript),或者使用来自这个社区驱动库的一个:https://github .com/shiftx/faunadb-fql-lib
function StringSplit(string: ExprArg, delimiter = " "){
return If(
Not(IsString(string)),
Abort("SplitString only accept strings"),
q.Map(
FindStrRegex(string, Concat(["[^\\", delimiter, "]+"])),
Lambda("res", LowerCase(Select(["data"], Var("res"))))
)
)
)
Run Code Online (Sandbox Code Playgroud)
并在我们的绑定中使用它。
CreateIndex({
name: 'tasks_by_words',
source: [
{
collection: Collection('tasks'),
fields: {
words: Query(Lambda('task', StringSplit(Select(['data', 'name']))))
}
}
],
terms: [
{
binding: 'words'
}
]
})
Run Code Online (Sandbox Code Playgroud)
提示,如果你不确定你是否做对了,你总是可以在值而不是术语中抛出绑定,然后你会在动物群仪表板中看到你的索引是否真的包含值:
我们做了什么?我们刚刚编写了一个绑定,它将在写入文档时将值转换为值数组。当您在 FaunaDB 中索引文档数组时,这些值是单独的索引,但都指向同一个文档,这对我们的搜索实现非常有用。
我们现在可以使用以下查询找到包含字符串 'first' 作为其单词之一的任务:
q.Map(
Paginate(Match(Index('tasks_by_words'), 'first')),
Lambda('ref', Get(Var('ref')))
)
Run Code Online (Sandbox Code Playgroud)
这会给我一个文件名:“要做的第一件事就是跳舞!”
另外两个文件没有包含确切的词,那么我们怎么做呢?
为了获得精确的包含匹配效率,您需要使用一个名为“NGram”的函数(仍然没有记录的函数,因为我们将在将来使它更容易)。在 ngrams 中划分字符串是一种搜索技术,在其他搜索引擎中经常在幕后使用。在 FaunaDB 中,由于索引和绑定的强大功能,我们可以轻松地应用它。该Fwitter例如在它的一个示例源代码,做自动完成。此示例不适用于您的用例,但我确实为其他用户引用了它,因为它用于自动完成短字符串,而不是像任务一样在较长的字符串中搜索短字符串。
不过,我们会根据您的用例对其进行调整。当涉及到搜索时,这完全是性能和存储的权衡,在 FaunaDB 中,用户可以选择他们的权衡。请注意,在前面的方法中,我们单独存储每个单词,使用 Ngrams 我们将进一步拆分单词以提供某种形式的模糊匹配。缺点是如果您做出错误的选择,索引大小可能会变得非常大(这对于搜索引擎来说同样如此,因此它们让您定义不同的算法)。
NGram 本质上所做的是获取特定长度字符串的子字符串。例如:
q.Map(
Paginate(Match(Index('tasks_by_words'), 'first')),
Lambda('ref', Get(Var('ref')))
)
Run Code Online (Sandbox Code Playgroud)
将返回:
如果我们知道我们不会搜索超过某个长度的字符串,假设长度为 10(这是一个权衡,增加大小会增加存储需求,但允许您查询更长的字符串),您可以编写跟随 Ngram 生成器。
NGram('lalala', 3, 3)
Run Code Online (Sandbox Code Playgroud)
然后,您可以按如下方式编写索引:
function GenerateNgrams(Phrase) {
return Distinct(
Union(
Let(
{
// Reduce this array if you want less ngrams per word.
indexes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
indexesFiltered: Filter(
Var('indexes'),
// filter out the ones below 0
Lambda('l', GT(Var('l'), 0))
),
ngramsArray: q.Map(Var('indexesFiltered'), Lambda('l', NGram(LowerCase(Var('Phrase')), Var('l'), Var('l'))))
},
Var('ngramsArray')
)
)
)
}
Run Code Online (Sandbox Code Playgroud)
而且你有一个索引支持的搜索,你的页面是你要求的大小。
q.Map(
Paginate(Match(Index('tasks_by_ngrams_exact'), 'first')),
Lambda('ref', Get(Var('ref')))
)
Run Code Online (Sandbox Code Playgroud)
如果您想要模糊搜索,通常会使用三元组,在这种情况下,我们的索引将很容易,因此我们不会使用外部函数。
CreateIndex({
name: 'tasks_by_ngrams_exact',
// we actually want to sort to get the shortest word that matches first
source: [
{
// If your collections have the same property tht you want to access you can pass a list to the collection
collection: [Collection('tasks')],
fields: {
wordparts: Query(Lambda('task', GenerateNgrams(Select(['data', 'name'], Var('task')))))
}
}
],
terms: [
{
binding: 'wordparts'
}
]
})
Run Code Online (Sandbox Code Playgroud)
如果我们再次将绑定放在值中以查看结果,我们将看到如下内容:
在这种方法中,我们在索引端和查询端一样使用两个三元组。在查询方面,这意味着我们搜索的“第一个”单词也将在 Trigrams 中划分如下:
例如,我们现在可以进行如下模糊搜索:
q.Map(
Paginate(Match(Index('tasks_by_ngrams_exact'), 'first')),
Lambda('ref', Get(Var('ref')))
)
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我们实际上进行了 3 次搜索,我们正在搜索所有的三元组并将结果合并。这将返回我们所有包含 first 的句子。

但是如果我们拼错了它并写了frst我们仍然会匹配所有三个,因为有一个三元组(rst)匹配。