[英]How do I get documents where a specific field exists/does not exists in Firebase Cloud Firestore?
In Firebase Cloud Firestore, I have "user_goals" in collections and goals may be a predefined goal (master_id: "XXXX") or a custom goal (no "master_id" key)在 Firebase Cloud Firestore 中,我在集合中有“user_goals”,目标可能是预定义的目标(master_id:“XXXX”)或自定义目标(没有“master_id”键)
In JavaScript, I need to write two functions, one to get all predefined goals and other to get all custom goals.在 JavaScript 中,我需要编写两个函数,一个用于获取所有预定义目标,另一个用于获取所有自定义目标。
I have got some workaround to get custom goals by setting "master_id" as "" empty string and able to get as below:我有一些解决方法可以通过将“master_id”设置为“”空字符串来获得自定义目标,并且能够获得如下:
db.collection('user_goals')
.where('challenge_id', '==', '') // workaround works
.get()
Still this is not the correct way, I continued to use this for predefined goals where it has a "master_id" as below仍然这不是正确的方法,我继续将其用于具有“master_id”的预定义目标,如下所示
db.collection('user_goals')
.where('challenge_id', '<', '') // this workaround
.where('challenge_id', '>', '') // is not working
.get()
Since Firestore has no "!=" operator, I need to use "<" and ">" operator but still no success.由于 Firestore 没有“!=”运算符,我需要使用“<”和“>”运算符但仍然没有成功。
Question: Ignoring these workarounds, what is the preferred way to get docs by checking whether a specific field exists or does not exists?问题:忽略这些变通方法,通过检查特定字段是否存在来获取文档的首选方法是什么?
As @Emile Moureau solution.作为@Emile Moureau 解决方案。 I prefer
我更喜欢
.orderBy(`field`)
To query documents with the field exists.用字段来查询文档是否存在。 Since it will work with any type of data with any value even for
null
.因为它可以处理任何类型的数据,即使是
null
也是如此。
But as @Doug Stevenson said:但正如@Doug Stevenson 所说:
You can't query for something that doesn't exist in Firestore.
您无法查询 Firestore 中不存在的内容。 A field needs to exist in order for a Firestore index to be aware of it.
字段需要存在才能让 Firestore 索引知道它。
You can't query for documents without the field.您无法查询没有该字段的文档。 At least for now.
至少现在。
The preferred way to get docs where a specified field exists is to use the:获取存在指定字段的文档的首选方法是使用:
.orderBy(fieldPath)
As specified in the Firebase documentation :如Firebase 文档中所述:
Thus the answer provided by @hisoft is valid.因此@hisoft 提供的答案是有效的。 I just decided to provide the official source, as the question was for the preferred way.
我只是决定提供官方来源,因为问题是针对首选方式。
The solution I use is:我使用的解决方案是:
Use: .where('field', '>', ''),
使用:
.where('field', '>', ''),
Where "field" is the field we are looking for!其中“字段”是我们正在寻找的字段!
Firestore is an indexed database . Firestore 是一个索引数据库。 For each field in a document, that document is inserted into that field's index as appropriate based on your configuration .
对于文档中的每个字段,该文档会根据您的配置适当地插入到该字段的索引中。 If a document doesn't contain a particular field (like
challenge_id
) it will not appear in that field's index and will be omitted from queries on that field.如果文档不包含特定字段(如
challenge_id
),它将不会出现在该字段的索引中,并且将从对该字段的查询中省略。 Normally, because of the way Firestore is designed, queries should read an index in one continuous sweep.通常,由于 Firestore 的设计方式,查询应该在一次连续扫描中读取索引。 Prior to the introduction of the
!=
and not-in
operators, this meant you couldn't exclude particular values as this would require jumping over sections of an index.在引入
!=
和not-in
运算符之前,这意味着您不能排除特定值,因为这需要跳过索引的各个部分。 This limitation is still encountered when trying to use exclusive ranges ( v<2 || v>4
) in a single query.尝试在单个查询中使用独占范围 (
v<2 || v>4
) 时,仍然会遇到此限制。
Field values are sorted according to the Realtime Database sort order except that the results can be sorted by multiple fields when duplicates are encountered instead of just the document's ID.字段值根据实时数据库排序顺序进行排序,但在遇到重复时,结果可以按多个字段排序,而不仅仅是文档的 ID。
Firestore Value Sort Order Firestore 值排序顺序
Priority![]() |
Sorted Values![]() |
Priority![]() |
Sorted Values![]() |
---|---|---|---|
1 ![]() |
null |
6 ![]() |
strings![]() |
2 ![]() |
false |
7 ![]() |
DocumentReference![]() |
3 ![]() |
true |
8 ![]() |
GeoPoint![]() |
4 ![]() |
numbers![]() |
9 ![]() |
arrays![]() |
5 ![]() |
Timestamp![]() |
10 ![]() |
maps![]() |
!=
/ <>
!=
/ <>
This section documents how inequalities worked prior to the release of the !=
and not-in
operators in Sep 2020 .本节记录了在2020 年 9 月发布
!=
和not-in
运算符之前不等式是如何工作的。 See the documentation on how to use these operators. 请参阅有关如何使用这些运算符的文档。 The following section will be left for historical purposes.
以下部分将留作历史用途。
To perform an inequality query on Firestore, you must rework your query so that it can be read by reading from Firestore's indexes.要在 Firestore 上执行不等式查询,您必须重新编写查询,以便可以通过读取 Firestore 的索引来读取它。 For an inequality, this is done by using two queries - one for values less than the
equality
and another for values greater than the equality.对于不等式,这是通过使用两个查询来完成的——一个用于小于
equality
的值,另一个用于大于等于的值。
As a trivial example, let's say I wanted the numbers that aren't equal to 3.作为一个简单的例子,假设我想要不等于 3 的数字。
const someNumbersThatAreNotThree = someNumbers.filter(n => n !== 3)
can be written as可以写成
const someNumbersThatAreNotThree = [
...someNumbers.filter(n => n < 3),
...someNumbers.filter(n => n > 3)
];
Applying this to Firestore, you can convert this ( formerly ) incorrect query:将此应用于 Firestore,您可以转换此( 以前)不正确的查询:
const docsWithChallengeID = await colRef
.where('challenge_id', '!=', '')
.get()
.then(querySnapshot => querySnapshot.docs);
into these two queries and merge their results:进入这两个查询并合并它们的结果:
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());
Important Note: The requesting user must be able to read all the documents that would match the queries to not get a permissions error.重要提示:请求用户必须能够读取与查询匹配的所有文档,以免出现权限错误。
Simply put, in Firestore, if a field doesn't appear in a document, that document won't appear in that field's index.简而言之,在 Firestore 中,如果某个字段未出现在文档中,则该文档将不会出现在该字段的索引中。 This is in contrast to the Realtime Database where omitted fields had a value of
null
.这与实时数据库形成对比,其中省略字段的值为
null
。
Because of the nature of NoSQL databases where the schema you are working with might change leaving your older documents with missing fields, you might need a solution to "patch your database".由于 NoSQL 数据库的性质,您正在使用的架构可能会发生变化,从而使您的旧文档缺少字段,您可能需要一个解决方案来“修补您的数据库”。 To do this, you would iterate over your collection and add the new field to the documents where it is missing.
为此,您将遍历您的集合并将新字段添加到缺少它的文档中。
To avoid permissions errors, it is best to make these adjustments using the Admin SDK with a service account, but you can do this using a regular SDK using a user with the appropriate read/write access to your database.为避免权限错误,最好使用带有服务帐户的 Admin SDK 进行这些调整,但您可以使用对数据库具有适当读/写访问权限的用户使用常规 SDK 来执行此操作。
This function is recursive, and is intended to be executed once .此函数是递归的,旨在执行一次。
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;
}
You would then apply changes using:然后,您将使用以下方法应用更改:
const goalsQuery = firebase.firestore()
.collection("user_goals");
addDefaultValueForField(goalsQuery, "challenge_id", "")
.catch((err) => console.error("failed to patch collection with new default value", err));
The above function could also be tweaked to allow the default value to be calculated based on the document's other fields:也可以调整上述函数以允许根据文档的其他字段计算默认值:
let getUpdateData;
if (typeof defaultFieldValue === "function") {
getUpdateData = (doc) => ({ [fieldName]: defaultFieldValue(doc) });
} else {
const updateData = { [fieldName]: defaultFieldValue };
getUpdateData = () => updateData;
}
/* ... later ... */
const addFieldPromise = doc.ref.update(getUpdateData(doc))
As you correctly state, it is not possible to filter based on !=
.正如您正确指出的那样,无法根据
!=
进行过滤。 If possible, I would add an extra field to define the goal type.如果可能,我会添加一个额外的字段来定义目标类型。 It is possible to use
!=
in security rules, along with various string comparison methods, so you can enforce the correct goal type, based on your challenge_id
format.可以在安全规则中使用
!=
以及各种字符串比较方法,因此您可以根据您的challenge_id
格式强制执行正确的目标类型。
Create a type
field and filter based on this field.创建一个
type
字段并基于此字段进行过滤。
type: master
or type: custom
and search .where('type', '==', 'master')
or search for custom. type: master
或type: custom
并搜索.where('type', '==', 'master')
或搜索 custom。
Create a customGoal
field which can be true
or false
.创建一个可以为
true
或false
的customGoal
字段。
customGoal: true
and search .where('customGoal', '==', true)
or false (as required). customGoal: true
并搜索.where('customGoal', '==', true)
或 false(根据需要)。
It is now possible to perform a != query in Cloud Firestore现在可以在 Cloud Firestore 中执行 != 查询
Firestore does pick up on boolean, which is a thing! Firestore 确实接受了布尔值,这是一回事! and can be
orderBy
'd.并且可以
orderBy
'd。
So often, like now, for this, I add this into the array-pushing from onSnapshot
or get
, use .get().then(
for dev...所以经常,就像现在一样,为此,我将它添加到来自
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
});
}
}
This way, I can query with orderBy
instead of where
这样,我可以使用
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;
It is not an ideal solution, but here is my workaround when a field does not exist:这不是一个理想的解决方案,但是当字段不存在时,这是我的解决方法:
let user_goals = await db.collection('user_goals').get()
user_goals.forEach(goal => {
let data = goal.data()
if(!Object.keys(data).includes(challenge_id)){
//Perform your task here
}
})
Note that it would impact your read counts a lot so only use this if you have small collection or can afford the reads.请注意,它会极大地影响您的读取计数,因此只有在您的收藏量较小或负担得起读取时才使用它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.