FireStore - 如何绕过数组“不包含”查询
Posted
技术标签:
【中文标题】FireStore - 如何绕过数组“不包含”查询【英文标题】:FireStore - how to get around array "does-not-contain" queries 【发布时间】:2020-01-08 18:34:14 【问题描述】:经过一些研究,很明显我不能使用 FireStore 来查询给定数组不包含的项目。有人对此用例有解决方法吗?...
用户注册后,应用会获取一堆卡片,每张卡片在 FireStore 中都有对应的“卡片”文档。用户与卡片交互后,卡片文档将用户的 uid 添加到字段数组(例如:usersWhoHaveSeenThisCard:[userUID]),“用户”文档将卡片的 uid 添加到字段数组(例如:cardThisUserHasSeen:[cardUID] )。 “用户”文档存在于“用户”集合中,“卡片”文档存在于“卡片”集合中。
目前,我想获取用户未与之交互的所有卡片。但是,这是有问题的,因为我只知道用户与之交互的卡片,因此 .whereField(usersWhoHaveSeenThisCard, arrayContains: currentUserUID) 将不起作用,因为我需要一个不存在的“arrayDoesNotContain”语句。
最后,用户不能拥有卡片,因此我无法在卡片文档中创建真/假布尔字段(例如:userHasSeenThisCard: false)并根据该条件进行搜索。
我能想到的唯一解决方案是在卡片文档上创建一个新的字段数组,其中包括所有没有看到卡片的用户(例如:usersWhoHaveNotSeenThisCard: [userUID]),但这意味着每个用户注册必须将他们的 uid 写入 1000 多个卡片文档,这会占用我的数据。
我可能只是运气不好,但我希望对 NOSQL / FireStore 更了解的人可以提供一些见解。
// If any code sample would help, please let me know and I'll update - I think this is largely conceptual as of now
【问题讨论】:
【参考方案1】:正如您从查询限制中发现的那样,仅使用 Cloud Firestore 没有简单的解决方法。您需要以某种方式存储看到的文档列表,将其加载到客户端应用程序的内存中,然后手动从所有潜在文档的查询结果中减去这些文档。
您可能需要考虑使用另一个可以更干净地执行此类操作的数据库(例如可以执行连接和子查询的 SQL 数据库)来扩充您的应用,并并行维护它们。
要么这样,要么要求以可预测的顺序查看所有文档,例如按时间戳。然后,您只需存储最后一次看到的文档的时间戳,并使用它来过滤结果。
【讨论】:
谢谢 Doug,很遗憾我现在无法设置额外的数据库,但时间戳的想法可能会奏效。所以基本上按最旧排序并添加一个 .whereField("timestamp", isGreaterThan: lastTimestampSeen) 并将用户的 lastTimestampSeen 属性保存到用户文档中。它并不完美,但现在应该足够了 @bhersh90 我认为我们可以做到这一点,而无需添加或维护额外的数据库,甚至无需更改结构。我抛出了一个答案 - 可能适用于您的用例,也可能不适用于您的用例。【参考方案2】:有一个公认的很好的答案,但是,它不能直接解决这个问题,所以这里是……(这可能有用也可能没有用,但它确实有效)
我不确切知道您的 Firestore 结构是什么,所以这是我的假设:
cards
card_id_0
usersWhoHaveSeenThisCard
0: uid_0
1: uid_1
2: uid_2
card_id_1
usersWhoHaveSeenThisCard
0: uid_2
1: uid_3
card_id_2
usersWhoHaveSeenThisCard
0: uid_1
1: uid_3
假设我们想知道 uid_2 没有看到哪些卡 - 在本例中是 card_id_2
func findCardsUserHasNotSeen(uidToCheck: String, completion: @escaping ( ([String]) -> Void ) )
let ref = self.db.collection("cards")
ref.getDocuments(completion: snapshot, err in
if let err = err
print(err.localizedDescription)
return
guard let docs = snapshot?.documents else
print("no docs")
return
var documentsIdsThatDoNotContainThisUser = [String]()
for doc in docs
let uidArray = doc.get("usersWhoHaveSeenThisCard") as! [String]
let x = uidArray.contains(uidToCheck)
if x == false
documentsIdsThatDoNotContainThisUser.append(doc.documentID)
completion(documentsIdsThatDoNotContainThisUser)
)
那么,这样的用例
func checkUserAction()
let uid = "uid_2" //the user id to check
self.findCardsUserHasNotSeen(uidToCheck: uid, completion: result in
if result.count == 0
print("user: \(uid) has seen all cards")
return
for docId in result
print("user: \(uid) has not seen: \(docId)")
)
和输出
user: uid_2 has not seen: card_id_2
此代码遍历文档,获取存储在每个文档 usersWhoHaveSeenThisCard 节点中的 uid 数组,并确定 uid 是否在数组中。如果没有,它会将 documentID 添加到 documentsIdsThatDoNotContainThisUser 数组中。检查完所有文档后,将返回不包含用户 ID 的文档 ID 数组。
知道 Firestore 的速度有多快,我针对一个大型数据集运行了代码,结果很快就返回了,因此对于大多数用例来说它不会造成任何延迟。
【讨论】:
感谢 Jay - 我真的很喜欢这种方法。我唯一担心的是这可能会产生大量不必要的读取。我不确定 FireStore 是如何决定获取数据的顺序的,但似乎顺序非常相似(即:cardUID₁、cardUID₂ 等)。如果是这种情况,那么用户将不得不在看到任何新内容之前遍历他们所看到的所有内容,这在他们浏览完数百张卡片时会很糟糕。让我知道你的想法——否则我真的很喜欢这种方法。 我应该澄清一下,我只想一次提取一批 10 张卡片,并且目前在我的提取查询中有一个 .limit(10) 子句 @bhersh90 在这个用例中,需要读取以确定用户没有看到哪些卡片。如果数据集有几千张卡片,它应该是最小的影响。至于限制应该非常简单,包括一个计数器并在达到 10 时退出 for 循环。 在 docs for-in 循环中每个文档需要读取一次,对吗?如果普通用户看到了 90 张卡片,因此必须遍历 100 个文档才能获得这批 10 张新卡片,那么在 500 次这些获取请求之后,我会达到我的 50k 读取限制。平均而言,每个会话用户会调用大约 5 次获取,因此在计费开始之前将支持大约 100 个每日用户。输入它,这实际上还不错。我想我可以切换到这个解决方案。再次感谢!以上是关于FireStore - 如何绕过数组“不包含”查询的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI + Firestore - 基于从 Firestore 返回的数组的过滤器列表
如何在 BigQuery 中从 firestore 导出的数组中查找元素