對于我的應(yīng)用程序來說,能夠從 Firebase 的集合中隨機選擇多個文檔至關(guān)重要。
由于 Firebase 中沒有內(nèi)置的本機函數(shù)(據(jù)我所知)來實現(xiàn)執(zhí)行此操作的查詢,因此我的第一個想法是使用查詢游標(biāo)來選擇隨機的開始和結(jié)束索引,前提是我有數(shù)字集合中的文檔數(shù)量。
這種方法可以工作,但只能以有限的方式工作,因為每個文檔每次都會與其相鄰文檔按順序提供;但是,如果我能夠通過其父集合中的索引選擇文檔,我可以實現(xiàn)隨機文檔查詢,但問題是我找不到任何文檔來描述如何執(zhí)行此操作,或者即使可以執(zhí)行此操作。
這是我想要做的事情,請考慮以下 firestore 架構(gòu):
root/ posts/ docA docB docC docD
然后在我的客戶端(我在 Swift 環(huán)境中)我想編寫一個可以執(zhí)行此操作的查詢:
db.collection("posts")[0, 1, 3] // would return: docA, docB, docD
我可以做一些類似的事情嗎?或者,是否有其他方法可以以類似的方式選擇隨機文檔?
請幫忙。
發(fā)布此內(nèi)容是為了幫助將來遇到此問題的任何人。
如果您使用自動 ID,則可以生成新的自動 ID 并查詢最接近的自動 ID,如Dan McGrath 的回答.
我最近創(chuàng)建了一個隨機報價 api,需要從 firestore 集合中獲取隨機報價。
這就是我解決這個問題的方法:
var db = admin.firestore(); var quotes = db.collection("quotes"); var key = quotes.doc().id; quotes.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get() .then(snapshot => { if(snapshot.size > 0) { snapshot.forEach(doc => { console.log(doc.id, '=>', doc.data()); }); } else { var quote = quotes.where(admin.firestore.FieldPath.documentId(), '<', key).limit(1).get() .then(snapshot => { snapshot.forEach(doc => { console.log(doc.id, '=>', doc.data()); }); }) .catch(err => { console.log('Error getting documents', err); }); } }) .catch(err => { console.log('Error getting documents', err); });
查詢的關(guān)鍵是這樣的:
.where(admin.firestore.FieldPath.documentId(), '>', key)
如果沒有找到文檔,則以相反的操作再次調(diào)用。
希望這會有所幫助!
使用隨機生成的索引和簡單查詢,您可以從 Cloud Firestore 中的集合或集合組中隨機選擇文檔。
這個答案分為 4 個部分,每個部分都有不同的選項:
這個答案的基礎(chǔ)是創(chuàng)建一個索引字段,當(dāng)按升序或降序排序時,會導(dǎo)致所有文檔隨機排序。有多種不同的方法來創(chuàng)建它,所以讓我們看一下 2,從最容易獲得的方法開始。
如果您使用我們的客戶端庫中提供的隨機生成的自動 ID,您可以使用同一系統(tǒng)隨機選擇文檔。在這種情況下,隨機排序的索引是文檔ID。
稍后在我們的查詢部分中,您生成的隨機值是一個新的自動 ID (iOS,Android,Web),您查詢的字段是 __name__
字段,并且 '后面提到的“l(fā)ow value”就是一個空字符串。這是迄今為止生成隨機索引的最簡單方法,并且無論語言和平臺如何,都可以工作。
默認(rèn)情況下,文檔名稱 (__name__
) 僅按升序索引,并且除了刪除和重新創(chuàng)建之外,您也無法重命名現(xiàn)有文檔。如果您需要其中任何一個,您仍然可以使用此方法,只需將自動 ID 存儲為名為 random
的實際字段,而不是為此目的重載文檔名稱。
當(dāng)你編寫文檔時,首先生成一個有界范圍內(nèi)的隨機整數(shù),并將其設(shè)置為一個名為random
的字段。根據(jù)您期望的文檔數(shù)量,您可以使用不同的有界范圍來節(jié)省空間或降低沖突風(fēng)險(這會降低此技術(shù)的有效性)。
您應(yīng)該考慮您需要哪種語言,因為會有不同的考慮因素。雖然 Swift 很簡單,但 JavaScript 卻有一個值得注意的問題:
這將創(chuàng)建一個索引,其中的文檔隨機排序。稍后在我們的查詢部分中,您生成的隨機值將是這些值中的另一個,而后面提到的“低值”將是 -1。
現(xiàn)在您已經(jīng)有了一個隨機索引,您將需要查詢它。下面我們看一些選擇 1 個隨機文檔的簡單變體,以及選擇多個 1 個文檔的選項。
對于所有這些選項,您需要生成一個新的隨機值,其形式與您在編寫文檔時創(chuàng)建的索引值相同,由下面的變量 random
表示。我們將使用該值在索引上查找隨機點。
現(xiàn)在您有了隨機值,您可以查詢單個文檔:
let postsRef = db.collection("posts") queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random) .order(by: "random") .limit(to: 1)
檢查是否已返回文檔。如果沒有,請再次查詢,但使用隨機索引的“低值”。例如,如果您執(zhí)行隨機整數(shù),則 lowValue
為 0
:
let postsRef = db.collection("posts") queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: lowValue) .order(by: "random") .limit(to: 1)
只要您有一個文檔,就保證您至少返回 1 個文檔。
環(huán)繞方法實現(xiàn)起來很簡單,并且允許您在僅啟用升序索引的情況下優(yōu)化存儲。缺點之一是價值觀可能受到不公平的保護(hù)。例如,如果 10K 中的前 3 個文檔(A、B、C)具有隨機索引值 A:409496、B:436496、C:818992,則 A 和 C 被選擇的機會小于 1/10K,而B 因 A 的接近而被有效屏蔽,并且只有大約 1/160K 的機會。
您可以在 >=
和 之間隨機選擇,而不是單向查詢并在未找到值時回繞,這會降低概率不公平屏蔽的值減半,代價是索引存儲加倍。
如果一個方向沒有返回結(jié)果,則切換到另一個方向:
queryRef = postsRef.whereField("random", isLessThanOrEqualTo: random) .order(by: "random", descending: true) .limit(to: 1) queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random) .order(by: "random") .limit(to: 1)
通常,您需要一次選擇多個隨機文檔。根據(jù)您想要的權(quán)衡,有兩種不同的方法可以調(diào)整上述技術(shù)。
這個方法很簡單。只需重復(fù)該過程,包括每次選擇一個新的隨機整數(shù)。
此方法將為您提供隨機的文檔序列,而不必?fù)?dān)心重復(fù)看到相同的模式。
代價是它會比下一個方法慢,因為它需要為每個文檔單獨往返服務(wù)。
在此方法中,只需增加所需文檔的限制數(shù)量即可。這有點復(fù)雜,因為您可能會在調(diào)用中返回 0..limit
文檔。然后,您需要以相同的方式獲取丟失的文檔,但限制減少到僅存在差異。如果您知道文檔總數(shù)比您要求的數(shù)量多,則可以通過忽略在第二次調(diào)用(但不是第一次)時永遠(yuǎn)無法取回足夠文檔的邊緣情況來進(jìn)行優(yōu)化。
此解決方案的權(quán)衡是重復(fù)序列。雖然文檔是隨機排序的,但如果最終出現(xiàn)重疊范圍,您將看到與之前看到的相同模式。有一些方法可以減輕這種擔(dān)憂,我們將在下一節(jié)有關(guān)重新播種的內(nèi)容中討論。
此方法比“沖洗并重復(fù)”更快,因為您將在最好情況下一次調(diào)用或最壞情況下兩次調(diào)用中請求所有文檔。
雖然如果文檔集是靜態(tài)的,則此方法會隨機為您提供文檔,但返回每個文檔的概率也將是靜態(tài)的。這是一個問題,因為根據(jù)它們獲得的初始隨機值,某些值可能具有不公平的低或高概率。在許多用例中,這很好,但在某些用例中,您可能希望增加長期隨機性,以便有更均勻的機會返回任何 1 個文檔。
請注意,插入的文檔最終會交織在中間,逐漸改變概率,刪除文檔也是如此。如果給定文檔數(shù)量,插入/刪除率太小,有一些策略可以解決這個問題。
您不必?fù)?dān)心重新播種,您始終可以為每個文檔創(chuàng)建多個隨機索引,然后每次隨機選擇其中一個索引。例如,讓字段 random
成為包含子字段 1 到 3 的映射:
{'random': {'1': 32456, '2':3904515723, '3': 766958445}}
現(xiàn)在您將隨機查詢 random.1、random.2、random.3,從而創(chuàng)建更大的隨機性分布。這本質(zhì)上是用增加的存儲空間來節(jié)省因重新播種而增加的計算(文檔寫入)。
每次更新文檔時,都會重新生成random
字段的隨機值。這將在隨機索引中移動文檔。
如果生成的隨機值不是均勻分布的(它們是隨機的,因此這是預(yù)期的),則可能會在不適當(dāng)?shù)臅r間內(nèi)選擇同一文檔。通過在讀取隨機選擇的文檔后使用新的隨機值更新該文檔,可以輕松解決此問題。
由于寫入成本更高并且可能成為熱點,因此您可以選擇僅在讀取時間的子集時進(jìn)行更新(例如,if random(0,100) === 0) update;
)。