1 回答

TA貢獻(xiàn)1887條經(jīng)驗(yàn) 獲得超5個贊
FaunaDB 提供了很多構(gòu)造,這使其功能強(qiáng)大,但您有很多選擇。強(qiáng)大的力量帶來了一個小的學(xué)習(xí)曲線:)。
如何閱讀代碼示例
為了清楚起見,我在這里使用了 FQL 的 JavaScript 風(fēng)格,并且通常從 JavaScript驅(qū)動程序公開 FQL 函數(shù),如下所示:
const faunadb = require('faunadb')
const q = faunadb.query
const {
Not,
Abort,
...
} = q
像這樣導(dǎo)出 Map 時必須小心,因?yàn)樗鼤c JavaScript 映射沖突。在這種情況下,您可以只使用 q.Map。
選項(xiàng) 1:使用 ContainsStr() 和過濾器
根據(jù)文檔的基本用法
ContainsStr('Fauna', 'a')
當(dāng)然,這適用于特定值,因此為了使其工作,您需要Filter 并且 Filter 僅適用于分頁集。這意味著我們首先需要獲得一個分頁集。獲取一組分頁文檔的一種方法是:
q.Map(
Paginate(Documents(Collection('tasks'))),
Lambda(['ref'], Get(Var('ref')))
)
但是我們可以更有效地做到這一點(diǎn),因?yàn)橐淮伍喿x === 一次閱讀并且我們不需要文檔,我們將過濾掉很多文檔。有趣的是,一個索引頁也是一次讀取,因此我們可以如下定義索引:
{
name: "tasks_name_and_ref",
unique: false,
serialized: true,
source: "tasks",
terms: [],
values: [
{
field: ["data", "name"]
},
{
field: ["ref"]
}
]
}
由于我們將 name 和 ref 添加到值中,索引將返回 name 和 ref 的頁面,然后我們可以使用它們進(jìn)行過濾。例如,我們可以對索引做類似的事情,對它們進(jìn)行映射,這將返回一個布爾數(shù)組。
Map(
Paginate(Match(Index('tasks_name_and_ref'))),
Lambda(['name', 'ref'], ContainsStr(Var('name'), 'first'))
)
由于 Filter 也適用于數(shù)組,我們實(shí)際上可以簡單地將Map替換為 filter。我們還將添加一個小寫字母以忽略大小寫,我們有我們需要的:
Filter(
Paginate(Match(Index('tasks_name_and_ref'))),
Lambda(['name', 'ref'], ContainsStr(LowerCase(Var('name')), 'first'))
)
就我而言,結(jié)果是:
{
"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")
]
]
}
過濾和縮小頁面大小
正如您所提到的,這并不是您想要的,因?yàn)檫@也意味著如果您請求 500 個大小的頁面,它們可能會被過濾掉,您最終可能會得到一個大小為 3 的頁面,然后是 7 個頁面中的一個。您可能會認(rèn)為,為什么我不能只在頁面中獲取過濾后的元素?好吧,出于性能原因,這是一個好主意,因?yàn)樗旧蠙z查每個值。想象一下,你有一個龐大的集合并過濾掉了 99.99%。您可能必須遍歷許多元素才能達(dá)到 500,所有這些都需要讀取。我們希望定價是可預(yù)測的:)。
選項(xiàng) 2:索引!
每次你想做更高效的事情時,答案就在于索引。FaunaDB 為您提供了實(shí)現(xiàn)不同搜索策略的原始能力,但您必須有點(diǎn)創(chuàng)意,我在這里為您提供幫助:)。
綁定
在索引綁定中,您可以轉(zhuǎn)換文檔的屬性,在我們的第一次嘗試中,我們會將字符串拆分為單詞(我將實(shí)現(xiàn)多個,因?yàn)槲也煌耆_定您想要哪種匹配)
我們沒有字符串拆分功能,但由于 FQL 很容易擴(kuò)展,我們可以自己編寫它,綁定到我們宿主語言中的變量(在本例中為 javascript),或者使用這個社區(qū)驅(qū)動的庫中的一個: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"))))
)
)
)
并在我們的綁定中使用它。
CreateIndex({
name: 'tasks_by_words',
source: [
{
collection: Collection('tasks'),
fields: {
words: Query(Lambda('task', StringSplit(Select(['data', 'name']))))
}
}
],
terms: [
{
binding: 'words'
}
]
})
提示,如果你不確定你是否做對了,你總是可以用值而不是術(shù)語來綁定,然后你會在動物儀表板中看到你的索引是否真的包含值:
我們做了什么?我們剛剛編寫了一個綁定,它將在寫入文檔時將值轉(zhuǎn)換為值數(shù)組。當(dāng)您在 FaunaDB 中索引文檔數(shù)組時,這些值是單獨(dú)的索引,但都指向同一個文檔,這對于我們的搜索實(shí)現(xiàn)非常有用。
我們現(xiàn)在可以使用以下查詢找到包含字符串“first”作為其單詞之一的任務(wù):
q.Map(
Paginate(Match(Index('tasks_by_words'), 'first')),
Lambda('ref', Get(Var('ref')))
)
這會給我一個文件名:“首先要做的是跳舞!”
其他兩個文檔沒有包含確切的單詞,那么我們該怎么做呢?
選項(xiàng) 3:索引和 Ngram(精確包含匹配)
為了獲得精確的包含匹配效率,您需要使用一個名為“NGram”的(仍然沒有記錄的函數(shù),因?yàn)槲覀儗頃蛊涓菀祝┖瘮?shù)。在 ngram 中劃分字符串是一種搜索技術(shù),通常在其他搜索引擎的底層使用。在 FaunaDB 中,由于索引和綁定的強(qiáng)大功能,我們可以輕松地應(yīng)用它。Fwitter 示例的源代碼中有一個自動完成的示例。此示例不適用于您的用例,但我確實(shí)為其他用戶引用了它,因?yàn)樗糜谧詣油瓿啥套址皇窍袢蝿?wù)一樣在較長字符串中搜索短字符串。
我們會根據(jù)您的用例對其進(jìn)行調(diào)整。在搜索方面,這完全是性能和存儲的權(quán)衡,在 FaunaDB 中,用戶可以選擇他們的權(quán)衡。請注意,在前面的方法中,我們分別存儲每個單詞,使用 Ngrams 我們將進(jìn)一步拆分單詞以提供某種形式的模糊匹配。不利的一面是,如果您做出錯誤的選擇,索引大小可能會變得非常大(搜索引擎同樣如此,因此它們讓您定義不同的算法)。
NGram 本質(zhì)上所做的是獲取一定長度的字符串的子字符串。例如:
NGram('lalala', 3, 3)
將返回:
如果我們知道我們不會搜索超過某個長度的字符串,假設(shè)長度為 10(這是一個折衷,增加大小會增加存儲需求,但允許您查詢更長的字符串),您可以編寫跟隨 Ngram 生成器。
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')
)
)
)
}
然后,您可以按如下方式編寫索引:
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'
}
]
})
你有一個索引支持的搜索,你的頁面是你請求的大小。
q.Map(
Paginate(Match(Index('tasks_by_ngrams_exact'), 'first')),
Lambda('ref', Get(Var('ref')))
)
選項(xiàng) 4:索引和大小為 3 的 Ngram 或三元組(模糊匹配)
如果你想要模糊搜索,通常使用三元組,在這種情況下我們的索引會很容易,所以我們不會使用外部函數(shù)。
CreateIndex({
name: 'tasks_by_ngrams',
source: {
collection: Collection('tasks'),
fields: {
ngrams: Query(Lambda('task', Distinct(NGram(LowerCase(Select(['data', 'name'], Var('task'))), 3, 3))))
}
},
terms: [
{
binding: 'ngrams'
}
]
})
如果我們再次將綁定放在值中以查看結(jié)果,我們將看到如下內(nèi)容:
在這種方法中,我們在索引端和查詢端一樣使用兩個三元組。在查詢方面,這意味著我們搜索的“第一個”單詞也將被劃分為三元組,如下所示:
例如,我們現(xiàn)在可以進(jìn)行如下模糊搜索:
q.Map(
Paginate(Union(q.Map(NGram('first', 3, 3), Lambda('ngram', Match(Index('tasks_by_ngrams'), Var('ngram')))))),
Lambda('ref', Get(Var('ref')))
)
在這種情況下,我們實(shí)際上進(jìn)行了 3 次搜索,我們正在搜索所有三元組并將結(jié)果合并。這將返回我們所有包含 first 的句子。
但是,如果我們拼錯了它并寫了frst,我們?nèi)匀粫ヅ渌腥齻€,因?yàn)橛幸粋€匹配的 trigram (rst)。
- 1 回答
- 0 關(guān)注
- 130 瀏覽
添加回答
舉報(bào)