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

标签 swift database firebase data-modeling google-cloud-firestore

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

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

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

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

root/
  posts/
     docA
     docB
     docC
     docD


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

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


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

请帮忙。

最佳答案

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

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


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


如何生成随机索引

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

自动编号版本

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

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

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

随机整数版本

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

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


32位整数:适用于小型(〜10K unlikely to have a collision)数据集
64位整数:大型数据集(注意:JavaScript本机不支持,yet


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

如何查询随机索引

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

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

环绕式

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

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


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

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;)。

关于swift - Firestore:如何在集合中获取随机文档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50059429/

相关文章:

javascript - firebase:angularfire 0.8.0 升级在记录中保存 ref.name

ios - 迭代用户以仅在 Swift 中显示他们的帖子

ios - Firebase动态链接不会缩短URL

ios - Swift UITest 处理所有测试的开始和所有测试用例的完成

sql 2008 express 在第一次请求时速度慢?去 sleep ?

ios - 我们在此 Google 项目中找不到包 ID '...'

swift - 更改 TextView 中文本的颜色

ios - 可选计算属性的两个代码块,为什么我不能以这种方式使用 .first?

c# - 将值插入 Database.mdf C#

mysql - RedHat Linux/MySql 服务器密码问题