Swift 3中Firebase查询name匹配项报错,如何实现全局匹配查询?
嘿,我来帮你梳理下这个问题。首先得说清楚Firebase Realtime Database的一个核心限制:它的查询只能针对单个特定节点下的直接子元素,没办法直接跨多个顶级节点(比如根节点下的不同子节点)执行统一的匹配查询。结合你的情况,我们一步步来解决:
1. 为什么你会收到那个索引提示?
你看到的错误提示 Consider adding ".indexOn": "name" at / to your security rules,大概率是因为你之前尝试过直接在根节点执行查询,比如写过类似这样的代码:
let query = ref.queryOrdered(byChild:"name").queryEqual(toValue: name)
但你的name字段根本不在根节点的直接子元素里——它是在goals_new节点的子对象中。所以这个根节点的查询不仅查不到任何结果,还会触发索引缺失的提示:Firebase以为你要在根节点的直接子节点上按name筛选,而那里没有对应的索引。
其实你当前针对goals_new节点的查询是完全正确的,而且你已经在安全规则里给goals_new配置了".indexOn": ["name"],这个索引足够支持你在该节点下的查询需求,不用管根节点的提示(除非你真的需要在根节点的直接子节点上查name,但显然你不需要)。
2. 如何获取所有匹配条件的name记录?
情况一:所有带name字段的记录都在goals_new节点下
你现在的代码已经能获取goals_new节点下所有name匹配的记录了。如果觉得结果有问题,可能是因为你的goals_new是数组(还包含null元素),查询时会自动跳过null,只返回有name字段的对象。你可以改成用.value事件一次性获取所有结果,方便验证:
let query = ref.child("goals_new").queryOrdered(byChild:"name").queryEqual(toValue: name) query.observe(.value) { snapshot in guard let goals = snapshot.value as? [[String: Any]] else { return } let matchedNames = goals.compactMap { $0["name"] as? String } print("所有匹配的name:\(matchedNames)") }
.childAdded是逐个返回匹配的子节点,.value则是一次性返回所有匹配结果,根据你的需求选择就行。
情况二:数据库里有多个顶级节点(比如除了goals_new还有别的节点)包含name字段
如果你的name字段分散在多个顶级节点下,Firebase不支持跨节点的统一查询,你得分别对每个目标节点执行查询,然后手动合并结果:
// 假设你需要查询的节点是goals_new和another_target_node let targetNodes = ["goals_new", "another_target_node"] var allMatchedNames: [String] = [] let dispatchGroup = DispatchGroup() for node in targetNodes { dispatchGroup.enter() let query = ref.child(node).queryOrdered(byChild:"name").queryEqual(toValue: name) query.observeSingleEvent(of: .value) { snapshot in defer { dispatchGroup.leave() } if let items = snapshot.value as? [[String: Any]] { let names = items.compactMap { $0["name"] as? String } allMatchedNames.append(contentsOf: names) } } } dispatchGroup.notify(queue: .main) { print("全局匹配的所有name:\(allMatchedNames)") }
这里用DispatchGroup来确保所有查询都完成后再输出合并后的结果,避免异步操作导致的结果不完整。
3. 优化数据库结构(可选,但推荐)
如果你的需求是频繁做全局name查询,建议调整下数据库结构,把所有带name的记录集中到一个专门的节点下(比如all_name_records),同时保留原来的goals_new节点用于其他业务逻辑。这样你只需要一次查询就能拿到所有匹配结果,效率更高。比如:
{ "goals_new": { "goal_1": { "name": "Eric thomas", "pic": "...", "title": "Be Obsessed with your Goals", "url": "..." }, "goal_2": { "name": "Bob Proctor", "pic": "...", "title": "Goal Achievement System", "url": "..." } }, "all_name_records": { "goal_1": { "name": "Eric thomas", "source_node": "goals_new" }, "goal_2": { "name": "Bob Proctor", "source_node": "goals_new" } } }
这里把原来的数组改成了键值对(Firebase非常推荐用键值对存储列表,避免数组索引混乱的问题),然后给all_name_records节点配置".indexOn": ["name"],查询起来就更顺畅了。
内容的提问来源于stack exchange,提问作者Iam Wayne

