如何获取Firebase Cloud Firestore中存在/不存在特定字段的文档?

eiee3dmh  于 2023-04-22  发布在  其他
关注(0)|答案(7)|浏览(171)

在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没有“!=”操作符,我需要使用“〈”和“〉”操作符,但仍然没有成功。

问题:忽略这些解决方法,通过检查特定字段是否存在来获取文档的首选方法是什么?

kmb7vmvb

kmb7vmvb1#

作为@Emile Moureau解决方案。我更喜欢

.orderBy(`field`)

使用该字段查询文档存在。因为它可以处理任何类型的数据和任何值,即使是null
正如@Doug史蒂文森所说:
您不能查询Firestore中不存在的内容。字段需要存在,以便Firestore索引知道它。
没有该字段,您无法查询文档。至少目前是这样。

rdlzhqv9

rdlzhqv92#

获取存在指定字段的文档的首选方法是使用:

.orderBy(fieldPath)

如Firebase文档中所述:

因此,@hisoft提供的答案是有效的。我只是决定提供官方来源,因为问题是首选的方式。

yrwegjxp

yrwegjxp3#

Firestore是一个indexed database。对于文档中的每个字段,该文档将根据您的配置适当地插入到该字段的索引中。如果文档不包含特定的字段(像challenge_id)它将出现在该字段的索引中,并且将从该字段的查询中省略。通常,由于Firestore的设计方式,查询应该在一个连续的扫描中读取索引。在引入!=not-in运算符之前,这意味着你不能排除特定的值,因为这需要跳过索引的部分。当试图在单个查询中使用独占范围(v<2 || v>4)时,仍然会遇到这种限制。
字段值将根据实时数据库排序顺序进行排序,但在遇到重复项时,可以按多个字段而不是仅按文档ID对结果进行排序。

Firestore值排序顺序

优先级排序值优先级排序值
1null* 琴弦 *
false* 参考文件 *
true* 地理点 *
* 数字 ** 数组 *
* 时间戳 *10个* Map *

不等式!=/<>

  • 本节记录了在2020年9月发布!=not-in运算符之前不等式是如何工作的。请参阅有关如何使用这些运算符的文档。以下部分将保留用于历史目的。*

要在Firestore上执行不等式查询,您必须修改查询,以便可以通过从Firestore的索引阅读来读取。对于不等式,这是通过使用两个查询来完成的-一个查询小于equality的值,另一个查询大于相等的值。
举个简单的例子,假设我想要不等于3的数字。

const someNumbersThatAreNotThree = someNumbers.filter(n => n !== 3)

可以写成

const someNumbersThatAreNotThree = [
   ...someNumbers.filter(n => n < 3),
   ...someNumbers.filter(n => n > 3)
];

将此应用于Firestore,您可以转换此(以前)不正确的查询:

const docsWithChallengeID = await colRef
  .where('challenge_id', '!=', '')
  .get()
  .then(querySnapshot => querySnapshot.docs);

这两个查询并合并它们的结果:

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

重要提示:请求用户必须能够读取所有与查询匹配的文档,才不会出现权限错误。

缺少/未定义字段

简单地说,在Firestore中,如果一个字段没有出现在文档中,该文档就不会出现在该字段的索引中。这与实时数据库不同,在实时数据库中,省略的字段的值为null
由于NoSQL数据库的性质,您正在使用的模式可能会更改,从而使旧文档缺少字段,因此您可能需要一个解决方案来“修补数据库”。为此,您将遍历集合并将新字段添加到缺少字段的文档中。
为了避免权限错误,最好使用具有服务帐户的Admin SDK进行这些调整,但您可以使用具有数据库适当读/写访问权限的用户使用常规SDK进行此调整。
这个函数循环遍历整个查询,并且只执行一次

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

上面的函数也可以调整,以允许基于文档的其他字段计算默认值:

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))
u3r8eeie

u3r8eeie4#

我使用的解决方案是:
用途:.where('field', '>', ''),
这里的“场”就是我们要找的场!

lnvxswe2

lnvxswe25#

正如你所说的,不可能基于!=进行过滤。如果可能的话,我会添加一个额外的字段来定义目标类型。可以在安全规则中使用!=,沿着各种字符串比较方法,这样你就可以根据你的challenge_id强制执行正确的目标类型。

指定目标类型

创建一个type字段并基于此字段进行筛选。
type: mastertype: custom并搜索.where('type', '==', 'master')或搜索自定义。

标记自定义目标

创建customGoal字段,可以是truefalse
customGoal: true并搜索.where('customGoal', '==', true)或false(根据需要)。

更新

现在可以在Cloud Firestore中执行!=查询了

bfhwhh0e

bfhwhh0e6#

Firestore确实能接受布尔值,这是一个东西!而且可以是orderBy‘d。
所以经常,像现在这样,为了这个,我把这个加入到数组中-从onSnapshotget推送,使用.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;
sbtkgmzw

sbtkgmzw7#

这不是一个理想的解决方案,但当字段不存在时,我的解决方案如下:

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
  }
})

请注意,它会影响你的阅读计数很多,所以只有使用这个,如果你有小的收集或可以负担得起的读取。

相关问题