使用随机生成的索引和简单查询,您可以从Cloud Firestore中的集合或集合组中随机 Select 文档.
此答案分为4个部分,每个部分有不同的选项:
- 如何生成随机索引
- 如何查询随机索引
- Select 多个随机文档
- 为持续的随机性重新 seeder
如何生成随机索引
这个答案的基础是创建一个索引字段,当按升序或降序排序时,所有文档都会被随机排序.有不同的方法来创建它,所以让我们看看2,从最容易获得的开始.
自动识别版本
如果您使用的是我们的客户端库中提供的随机生成的自动ID,那么您可以使用相同的系统随机 Select 一个文档.在这种情况下,随机排序的索引is表示文档id.
在后面的查询部分中,您生成的随机值是一个新的自动id(iOSAndroidWeb),您查询的字段是__name__
字段,后面提到的"低值"是一个空字符串.这是迄今为止生成随机索引最简单的方法,无论使用何种语言和平台,它都能正常工作.
默认情况下,文档名(__name__
)仅按升序编制索引,并且除了删除和重新创建之外,您也无法重命名现有文档.如果您需要这两种方法中的任何一种,您仍然可以使用此方法,只需将自动id存储为名为random
的实际字段,而不是为此目的重载文档名.
随机整数版本
编写文档时,首先在有界范围内生成一个随机整数,并将其设置为名为random
的字段.根据预期的文档数量,可以使用不同的有界范围来节省空间或降低碰撞风险(这会降低此技术的有效性).
你应该考虑你需要哪些语言,因为会有不同的考虑.虽然Swift很简单,但JavaScript显然有一个问题:
这将创建一个索引,对文档进行随机排序.在后面的查询部分中,您生成的随机值将是这些值中的另一个,后面提到的"低值"将是-1.
如何查询随机索引
现在您有了一个随机索引,您将需要查询它.下面我们来看一些简单的变体,可以 Select 1个随机文档,以及 Select 多个文档的选项.
对于所有这些选项,您都希望生成一个新的随机值,其形式与编写文档时创建的索引值相同,由下面的变量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)
只要您有一个文档,就可以保证至少返回一个文档.
双向的
环绕方法易于实现,只需启用升序索引即可优化存储.缺点之一是价值观可能受到不公平的保护.例如,如果10K中的前3个文档(A、B、C)的随机索引值为A:409496、B:436496、C:818992,那么A和C被选中的几率仅为1/10K,而B实际上被A的接近度所屏蔽,只有大约1/160K的几率.
您可以在>=
到<=
之间随机 Select ,而不是在单个方向上进行查询,并在找不到值时进行环绕,这将不公平屏蔽值的概率降低了一半,而代价是索引存储的两倍.
如果一个方向没有返回结果,请切换到另一个方向:
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)
Select 多个随机文档
通常,您会希望一次 Select 多个随机文档.有两种不同的方法来调整上述技术,具体取决于您想要的权衡.
Rinse & Repeat
这种方法很简单.只需重复这个过程,包括每次 Select 一个新的随机整数.
这种方法将为您提供随机的文档序列,而不用担心重复看到相同的模式.
取舍是,它将比下一种方法慢,因为它需要每个文档单独往返于服务.
继续
在这种方法中,只需增加所需文档的数量.这有点复杂,因为您可能会在通话中返回0..limit
个文档.然后,您需要以相同的方式获取丢失的文档,但限制仅限于差异.如果你知道总文档数超过你要的数量,你可以通过忽略在第二次通话(但不是第一次)时永远无法收回足够文档的边缘情况进行优化.
这种解决方案的权衡是重复的.虽然文档是随机排序的,但如果最终出现重叠区域,您将看到与以前相同的模式.下一节将讨论如何缓解这种担忧.
这种方法比"冲洗"更快;重复"因为在最好的情况下,你需要一个电话或最坏的情况下两个电话的所有文件".
为持续的随机性重新 seeder
如果文档集是静态的,这种方法会随机生成文档,但返回每个文档的概率也是静态的.这是一个问题,因为根据获得的初始随机值,某些值可能具有不公平的低概率或高概率.在许多用例中,这是可以的,但在某些情况下,您可能希望增加长期随机性,以便有更均匀的机会返回任何1个文档.
请注意,插入的文档最终会交织在两者之间,从而逐渐改变概率,删除文档也是如此.考虑到文档的数量,如果插入/删除率太小,有几种策略可以解决这个问题.
多重随机
您可以 for each 文档创建多个随机索引,然后每次随机 Select 其中一个索引,而不用担心重新 seeder .例如,将字段random
设置为包含子字段1到3的 map :
{'random': {'1': 32456, '2':3904515723, '3': 766958445}}
现在,您将对random进行查询.1.随机.2.随机.3随机性,创造更大的随机性传播.这实质上是用增加的存储空间来节省增加的计算量(文档写入量).
在写作上重新设定种子
每次更新文档时,都要重新生成random
字段的随机值.这将在随机索引中移动文档.
在阅读中重新设定种子
如果生成的随机值不是均匀分布的(它们是随机的,所以这是意料之中的),那么同一个文档可能会被选取的时间不成比例.这很容易通过在随机 Select 的文档读取后使用新的随机值更新来抵消.
由于写入成本更高,而且可能成为热点,因此您可以 Select 只在读取时更新一部分时间(例如,if random(0,100) === 0) update;
).