一尘不染

Firestore:如何在集合中获取随机文档

swift

对于我的应用程序而言,至关重要的是能够从Firebase的集合中随机选择多个文档。

由于Firebase(我知道)没有内置本机函数来实现执行此操作的查询,因此我的第一个想法是使用查询游标选择随机的起始索引和终止索引,前提是我拥有​​其中的文档数集合。

这种方法行之有效,但只能以有限的方式进行,因为每次每次文档都会与其相邻文档一起依次送达。但是,如果我能够通过其父集合中的索引选择一个文档,则可以实现随机文档查询,但是问题是我找不到任何描述如何执行此操作甚至可以执行此操作的文档。

这是我想做的,请考虑以下Firestore模式:

root/
  posts/
     docA
     docB
     docC
     docD

然后在我的客户端(我处于Swift环境中)中,我想编写一个查询来做到这一点:

db.collection("posts")[0, 1, 3] // would return: docA, docB, docD

无论如何,我可以做些类似的事情吗?或者,是否可以通过其他方式以类似方式选择随机文档?

请帮忙。


阅读 187

收藏
2020-07-07

共1个答案

一尘不染

使用随机生成的索引和简单查询,您可以从Cloud Firestore中的集合或集合组中随机选择文档。

该答案分为4个部分,每个部分中都有不同的选项:

  1. 如何生成随机索引
  2. 如何查询随机索引
  3. 选择多个随机文档
  4. 重新播种进行中的随机性

如何生成随机索引

该答案的基础是创建一个索引字段,该字段在按升序或降序排序时会导致所有文档都被随机排序。有多种创建方法,因此让我们看一下2,从最容易获得的开始。

自动编号版本

如果您使用的是我们客户库中提供的随机生成的自动ID,则可以使用同一系统随机选择一个文档。在这种情况下,随机排序的索引 文档ID。

在我们的查询部分的后面,您生成的随机值是一个新的自动ID(iOSAndroidWeb),而您查询的__name__字段是该字段,后面提到的“低值”是一个空字符串。到目前为止,这是生成随机索引的最简单方法,无论使用哪种语言和平台,它都可以工作。

默认情况下,文档名称(__name__)仅按升序索引,并且您也不能在删除和重新创建后重命名现有文档。如果您需要其中任何一个,您仍然可以使用此方法,仅将自动ID存储为一个实际的字段,random而不是为此目的而重载文档名称。

随机整数版本

编写文档时,首先生成一个有界范围内的随机整数,并将其设置为称为的字段random。根据您期望的文档数量,您可以使用其他有界范围来节省空间或降低发生冲突的风险(这会降低该技术的有效性)。

您应该考虑所需的语言,因为会有不同的考虑。尽管Swift很简单,但是JavaScript显然有一个陷阱:

  • 32位整数:适用于小型(〜10K 不太可能发生碰撞)数据集
  • 64位整数:大型数据集(注:JavaScript的本身并不支持,

这将创建一个索引,其中您的文档被随机排序。在我们的查询部分的后面,您生成的随机值将是这些值中的另一个,而稍后提到的“低值”将是-1。

如何查询随机索引

现在您有了一个随机索引,您将要查询它。下面我们看一些选择1个随机文档的简单变体,以及选择1个以上随机文档的选项。

对于所有这些选项,您将希望以与编写文档时创建的索引值相同的形式生成一个新的随机值,由random下面的变量表示。我们将使用此值在索引上找到随机点。

环绕式

现在您有了一个随机值,就可以查询一个文档:

let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: random)
                   .order(by: "random")
                   .limit(to: 1)

检查是否已返回文档。如果不是,请再次查询,但对随机索引使用“低值”。例如,如果您做了随机整数,lowValue则为0

let postsRef = db.collection("posts")
queryRef = postsRef.whereField("random", isGreaterThanOrEqualTo: lowValue)
                   .order(by: "random")
                   .limit(to: 1)

只要您有一个文档,就可以保证至少返回1个文档。

双向的

环绕式方法易于实现,并允许您仅启用升序索引来优化存储。缺点之一是价值观受到不公正保护的可能性。例如,如果10K中的前3个文档(A,B,C)具有随机索引值A:409496,B:436496,C:818992,则A和C的被选中机会不到1
/ 10K,而B被A的接近有效地屏蔽,只有大约1 / 160K的机会。

您可以在>=和之间进行随机选择,而不必在一个方向上进行查询并四处寻找,而是可以在和之间随机选择<=,这将不正确屏蔽的值的概率降低了一半,但代价是索引存储量增加了一倍。

如果一个方向未返回结果,请切换到另一方向:

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)

选择多个随机文档

通常,您一次要选择多个随机文档。有两种不同的方法可以根据所需的权衡来调整上述技术。

冲洗并重复

这种方法很简单。只需重复此过程,包括每次选择一个新的随机整数。

此方法将为您提供随机的文档序列,而不必担心重复看到相同的模式。

权衡是,它将比下一种方法慢,因为它需要为每个文档单独进行服务往返。

保持它来

用这种方法,只需增加所需文档的数量即可。由于您可能会0..limit在通话中返回文档,因此稍微复杂一点。然后,您将需要以相同的方式获取丢失的文档,但是将限制减少到仅差异。如果您知道总的文档数量超过了所需数量,则可以通过忽略在第二次调用(而不是第一次调用)中永远不会取回足够文档的极端情况进行优化。

这种解决方案的权衡是重复进行的。尽管文档是随机排序的,但是如果最终出现重叠范围,则会看到与以前相同的模式。在缓解播种的下一节中将讨论减轻这种担忧的方法。

这种方法比“漂洗和重复”的速度更快,因为您将在一次调用的最佳情况下或两次调用的最坏情况下请求所有文档。

重新播种进行中的随机性

尽管如果文档集是静态的,此方法会为您随机提供文档,但是返回每个文档的概率也将是静态的。这是一个问题,因为根据获得的初始随机值,某些值可能具有不公平的低或高概率。在许多用例中,这很好,但是在某些情况下,您可能希望增加长期随机性,以便有更统一的机会返回任何1个文档。

请注意,插入的文档最终将在中间编织,逐渐更改概率,而删除文档也会如此。如果给定文档数,如果插入/删除率太小,则有几种解决方法。

多随机

不必担心重新播种,您始终可以为每个文档创建多个随机索引,然后每次都随机选择这些索引之一。例如,让该字段random成为具有1到3子字段的地图:

{'random': {'1': 32456, '2':3904515723, '3': 766958445}}

现在,您将随机查询random.1,random.2,random.3,从而扩大随机性。从本质上讲,这需要交换增加的存储空间,以节省因重新设置种子而增加的计算量(文档写入)。

重新写入种子

每当您更新文档时,请重新生成该random字段的随机值。这将使文档在随机索引中移动。

重新读取种子

如果生成的随机值不是均匀分布的(它们是随机的,那么这是可以预期的),那么可能会在不适当的时间内选择同一文档。通过在读取后用新的随机值更新随机选择的文档,可以轻松解决此问题。

由于写入操作较为昂贵且可能会引起热点,因此您可以选择仅在部分时间读取时进行更新(例如if random(0,100) === 0) update;)。

2020-07-07