在Firebase Cloud Firestore中,我在集合中有“user_goals”,目标可能是预定义的目标(master_id:“XXXX”)或自定义目标(没有“master_id”键)
在JavaScript中,我需要编写两个函数,一个用于获取所有预定义目标,另一个用于获取所有自定义目标。
我有一些解决方法可以通过将“master_id”设置为“”空字符串来获得自定义目标,并且能够获得如下所示:
db.collection('user_goals')
.where('challenge_id', '==', '') // workaround works
.get()
这仍然不是正确的方法,我继续将此用于预定目标,其中有一个“master_id”,如下所示
db.collection('user_goals')
.where('challenge_id', '<', '') // this workaround
.where('challenge_id', '>', '') // is not working
.get()
由于Firestore没有“!=”运算符,我需要使用“&lt;”和“&gt;”运营商,但仍然没有成功。
问题:忽略这些变通方法,通过检查特定字段是否存在来获取文档的首选方法是什么?
答案 0 :(得分:22)
获取存在指定字段的文档的首选方法是使用:
.orderBy(fieldPath)
根据Firebase documentation中的规定:
因此,@ hisoft提供的答案是有效的。我只是决定提供官方消息来源,因为问题是首选方法。
答案 1 :(得分:2)
我使用的解决方案是:
使用:.where('field', '>', ''),
“字段”是我们要寻找的字段!
答案 2 :(得分:2)
作为@Emile Moureau解决方案。我更喜欢
.orderBy(`field`)
要查询的文档具有该字段存在。因为它适用于null
甚至任何值的任何类型的数据。
但是正如@Doug Stevenson所说:
您无法查询Firestore中不存在的内容。为了使Firestore索引知道该字段,需要存在一个字段。
如果没有该字段,则无法查询文档。至少现在。
答案 3 :(得分:1)
正如您所说,无法根据!=
进行过滤。如果可能的话,我会添加一个额外的字段来定义目标类型。可以在安全规则中使用!=
以及各种字符串比较方法,因此您可以根据challenge_id
格式强制执行正确的目标类型。
创建type
字段并根据此字段进行过滤。
type: master
或type: custom
并搜索.where('type', '==', 'master')
或搜索自定义。
创建customGoal
字段,可以是true
或false
。
customGoal: true
并搜索.where('customGoal', '==', true)
或false(根据需要)。
答案 4 :(得分:0)
Firestore 是一个 indexed database。对于文档中的每个字段,该文档都被插入到该字段的索引 as appropriate based on your configuration 中。如果文档不包含特定字段(如 challenge_id
),它将不出现在该字段的索引中,并且将从对该字段的查询中省略。重要的是,由于 Firestore 的设计方式,查询必须能够在一次连续扫描中读取索引,以防止使用不等 (!=
, <>
) 查询和独占范围 ({{1 }}) 在单个查询中,因为这些需要跳转索引部分。
字段值根据 Realtime Database sort order 排序,但遇到重复项时,结果可以按多个字段排序,而不仅仅是文档的 ID。
Firestore 值排序顺序
|优先级|排序值|优先级|排序值|
|-|-|-|-|
|1|v<2 || v>4
|6|字符串|
|2|null
|7|文档参考|
|3|false
|8|地理点|
|4|数字|9|数组|
|5|时间戳|10|地图|
true
/!=
要在 Firestore 上执行不等式查询,您必须重新编写查询,以便可以通过读取 Firestore 的索引来读取它。对于不等式,这是通过使用两个查询来完成的 - 一个查询小于 <>
的值,另一个查询大于等式的值。
举个简单的例子,假设我想要不等于 3 的数字。
equality
可以写成
const someNumbersThatAreNotThree = someNumbers.filter(n => n !== 3)
将此应用于 Firestore,您可以转换此不正确的查询:
const someNumbersThatAreNotThree = [
...someNumbers.filter(n => n < 3),
...someNumbers.filter(n => n > 3)
];
进入这两个查询并合并它们的结果:
const docsWithChallengeID = await colRef
.where('challenge_id', '!=', '')
.get()
.then(querySnapshot => querySnapshot.docs);
重要说明:发出请求的用户必须能够读取所有与查询匹配的文档,以免出现权限错误。
简单地说,在 Firestore 中,如果某个字段未出现在文档中,则该文档将不会出现在该字段的索引中。这与实时数据库形成对比,实时数据库中省略的字段值为 const docsWithChallengeID = await Promise.all([
colRef
.orderBy('challenge_id')
.endBefore('')
.get()
.then(querySnapshot => querySnapshot.docs),
colRef
.orderBy('challenge_id')
.startAfter('')
.get()
.then(querySnapshot => querySnapshot.docs),
]).then(results => results.flat());
。
由于 NoSQL 数据库的性质,您正在使用的架构可能会发生变化,从而导致旧文档缺少字段,因此您可能需要一个解决方案来“修补您的数据库”。为此,您需要遍历您的集合并将新字段添加到缺少它的文档中。
为避免权限错误,最好使用具有服务帐户的 Admin SDK 进行这些调整,但您也可以使用对数据库具有适当读/写访问权限的用户使用常规 SDK 来执行此操作。
此函数是递归的,旨在执行一次。
null
然后您将使用以下方法应用更改:
async function addDefaultValueForField(queryRef, fieldName, defaultFieldValue, pageSize = 100) {
let checkedCount = 0, pageCount = 1;
const initFieldPromises = [], newData = { [fieldName]: defaultFieldValue };
// get first page of results
console.log(`Fetching page ${pageCount}...`);
let querySnapshot = await queryRef
.limit(pageSize)
.get();
// while page has data, parse documents
while (!querySnapshot.empty) {
// for fetching the next page
let lastSnapshot = undefined;
// for each document in this page, add the field as needed
querySnapshot.forEach(doc => {
if (doc.get(fieldName) === undefined) {
const addFieldPromise = doc.ref.update(newData)
.then(
() => ({ success: true, ref: doc.ref }),
(error) => ({ success: false, ref: doc.ref, error }) // trap errors for later analysis
);
initFieldPromises.push(addFieldPromise);
}
lastSnapshot = doc;
});
checkedCount += querySnapshot.size;
pageCount++;
// fetch next page of results
console.log(`Fetching page ${pageCount}... (${checkedCount} documents checked so far, ${initFieldPromises.length} need initialization)`);
querySnapshot = await queryRef
.limit(pageSize)
.startAfter(lastSnapshot)
.get();
}
console.log(`Finished searching documents. Waiting for writes to complete...`);
// wait for all writes to resolve
const initFieldResults = await Promise.all(initFieldPromises);
console.log(`Finished`);
// count & sort results
let initializedCount = 0, errored = [];
initFieldResults.forEach((res) => {
if (res.success) {
initializedCount++;
} else {
errored.push(res);
}
});
const results = {
attemptedCount: initFieldResults.length,
checkedCount,
errored,
erroredCount: errored.length,
initializedCount
};
console.log([
`From ${results.checkedCount} documents, ${results.attemptedCount} needed the "${fieldName}" field added.`,
results.attemptedCount == 0
? ""
: ` ${results.initializedCount} were successfully updated and ${results.erroredCount} failed.`
].join(""));
const errorCountByCode = errored.reduce((counters, result) => {
const code = result.error.code || "unknown";
counters[code] = (counters[code] || 0) + 1;
return counters;
}, {});
console.log("Errors by reported code:", errorCountByCode);
return results;
}
也可以调整上述函数以允许根据文档的其他字段计算默认值:
const goalsQuery = firebase.firestore()
.collection("user_goals");
addDefaultValueForField(goalsQuery, "challenge_id", "")
.catch((err) => console.error("failed to patch collection with new default value", err));
答案 5 :(得分:0)
Firestore 确实接受了布尔值,这是一回事!并且可以orderBy
。
很多时候,就像现在一样,为此,我将其添加到 onSnapshot
或 get
的数组推送中,使用 .get().then(
进行开发...
if (this.props.auth !== undefined) {
if (community && community.place_name) {
const sc =
community.place_name && community.place_name.split(",")[1];
const splitComma = sc ? sc : false
if (community.splitComma !== splitComma) {
firebase
.firestore()
.collection("communities")
.doc(community.id)
.update({ splitComma });
}
const sc2 =
community.place_name && community.place_name.split(",")[2];
const splitComma2 =sc2 ? sc2 : false
console.log(splitComma2);
if (community.splitComma2 !== splitComma2) {
firebase
.firestore()
.collection("communities")
.doc(community.id)
.update({
splitComma2
});
}
}
这样,我可以用 orderBy
而不是 where
browseCommunities = (paginate, cities) => {
const collection = firebase.firestore().collection("communities");
const query =
cities === 1 //countries
? collection.where("splitComma2", "==", false) //without a second comma
: cities //cities
? collection
.where("splitComma2", ">", "")
.orderBy("splitComma2", "desc") //has at least two
: collection.orderBy("members", "desc");
var shot = null;
if (!paginate) {
shot = query.limit(10);
} else if (paginate === "undo") {
shot = query.startAfter(this.state.undoCommunity).limit(10);
} else if (paginate === "last") {
shot = query.endBefore(this.state.lastCommunity).limitToLast(10);
}
shot &&
shot.onSnapshot(
(querySnapshot) => {
let p = 0;
let browsedCommunities = [];
if (querySnapshot.empty) {
this.setState({
[nuller]: null
});
}
querySnapshot.docs.forEach((doc) => {
p++;
if (doc.exists) {
var community = doc.data();
community.id = doc.id;