是否可以使用新的Firebase数据库Cloud Firestore来计算一个集合有多少项?

如果是,我该怎么做?


当前回答

使用admin. keystore . fieldvalue . Increment增加一个计数器:

exports.onInstanceCreate = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onCreate((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(1),
    })
  );

exports.onInstanceDelete = functions.firestore.document('projects/{projectId}/instances/{instanceId}')
  .onDelete((snap, context) =>
    db.collection('projects').doc(context.params.projectId).update({
      instanceCount: admin.firestore.FieldValue.increment(-1),
    })
  );

在本例中,每次将文档添加到instances子集合时,我们都会增加项目中的instanceCount字段。如果该字段还不存在,它将被创建并增加到1。

增量在内部是事务性的,但如果需要更频繁地递增,则应该使用分布式计数器。

通常最好实现onCreate和onDelete而不是onWrite,因为你将调用onWrite进行更新,这意味着你在不必要的函数调用上花费了更多的钱(如果你更新了你的集合中的文档)。

其他回答

最简单的方法是读取“querySnapshot”的大小。

db.collection("cities").get().then(function(querySnapshot) {      
    console.log(querySnapshot.size); 
});

你也可以在querySnapshot中读取文档数组的长度。

querySnapshot.docs.length;

或者如果“querySnapshot”为空,则读取空值,将返回一个布尔值。

querySnapshot.empty;

除了上面我的npm包adv-firestore-functions,你还可以使用firestore规则来强制一个好的计数器:

Firestore规则

function counter() {
  let docPath = /databases/$(database)/documents/_counters/$(request.path[3]);
  let afterCount = getAfter(docPath).data.count;
  let beforeCount = get(docPath).data.count;
  let addCount = afterCount == beforeCount + 1;
  let subCount = afterCount == beforeCount - 1;
  let newId = getAfter(docPath).data.docId == request.path[4];
  let deleteDoc = request.method == 'delete';
  let createDoc = request.method == 'create';
  return (newId && subCount && deleteDoc) || (newId && addCount && createDoc);
}

function counterDoc() {
  let doc = request.path[4];
  let docId = request.resource.data.docId;
  let afterCount = request.resource.data.count;
  let beforeCount = resource.data.count;
  let docPath = /databases/$(database)/documents/$(doc)/$(docId);
  let createIdDoc = existsAfter(docPath) && !exists(docPath);
  let deleteIdDoc = !existsAfter(docPath) && exists(docPath);
  let addCount = afterCount == beforeCount + 1;
  let subCount = afterCount == beforeCount - 1;
  return (createIdDoc && addCount) || (deleteIdDoc && subCount);
}

像这样使用它们:

match /posts/{document} {
  allow read;
  allow update;
  allow create: if counter();
  allow delete: if counter();
}
match /_counters/{document} {
  allow read;
  allow write: if counterDoc();
}

前端

用以下函数替换你的set和delete函数:

set

async setDocWithCounter(
  ref: DocumentReference<DocumentData>,
  data: {
    [x: string]: any;
  },
  options: SetOptions): Promise<void> {

  // counter collection
  const counterCol = '_counters';

  const col = ref.path.split('/').slice(0, -1).join('/');
  const countRef = doc(this.afs, counterCol, col);
  const countSnap = await getDoc(countRef);
  const refSnap = await getDoc(ref);

  // don't increase count if edit
  if (refSnap.exists()) {
    await setDoc(ref, data, options);

    // increase count
  } else {
    const batch = writeBatch(this.afs);
    batch.set(ref, data, options);

    // if count exists
    if (countSnap.exists()) {
      batch.update(countRef, {
        count: increment(1),
        docId: ref.id
      });
      // create count
    } else {
      // will only run once, should not use
      // for mature apps
      const colRef = collection(this.afs, col);
      const colSnap = await getDocs(colRef);
      batch.set(countRef, {
        count: colSnap.size + 1,
        docId: ref.id
      });
    }
    batch.commit();
  }
}

删除

async delWithCounter(
  ref: DocumentReference<DocumentData>
): Promise<void> {

  // counter collection
  const counterCol = '_counters';

  const col = ref.path.split('/').slice(0, -1).join('/');
  const countRef = doc(this.afs, counterCol, col);
  const countSnap = await getDoc(countRef);
  const batch = writeBatch(this.afs);

  // if count exists
  batch.delete(ref);
  if (countSnap.exists()) {
    batch.update(countRef, {
      count: increment(-1),
      docId: ref.id
    });
  }
  /*
  if ((countSnap.data() as any).count == 1) {
    batch.delete(countRef);
  }*/
  batch.commit();
}

更多信息请看这里…

J

截至2022年10月,Firestore在客户端sdk上引入了count()方法。现在您可以在没有下载的情况下计算查询。

对于1000份文件,它将收取你阅读1份文件的费用。

网络(v9)

Firebase 9.11.0介绍:

const collectionRef = collection(db, "cities");
const snapshot = await getCountFromServer(collectionRef);
console.log('count: ', snapshot.data().count);

Web V8

不可用。

节点(管理)

const collectionRef = db.collection('cities');
const snapshot = await collectionRef.count().get();
console.log(snapshot.data().count);

Android (Kotlin)

在firestore v24.4.0 (BoM 31.0.0)引入:

val query = db.collection("cities")
val countQuery = query.count()
countQuery.get(AggregateSource.SERVER).addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val snapshot = task.result
        Log.d(TAG, "Count: ${snapshot.count}")
    } else {
        Log.d(TAG, "Count failed: ", task.getException())
    }
}

苹果平台(Swift)

Firestore v10.0.0引入:

do {
  let query = db.collection("cities")
  let countQuery = query.countAggregateQuery
  let snapshot = try await countQuery.aggregation(source: AggregateSource.server)
  print(snapshot.count)
} catch {
  print(error)
}

其中一个快速省钱的技巧是:-

创建一个文档并在firestore中存储一个“count”变量,当用户在集合中添加新文档时,增加该变量,当用户删除一个文档时,减少变量。如。 updateDoc(doc(db, "Count_collection", "Count_Doc"), {count: increment(1)});

注:计数用(-1)递减,用(1)递增

如何节省金钱和时间:-

你(firebase)不需要循环通过集合,浏览器也不需要加载整个集合来计数文档的数量。 所有的计数都保存在一个名为“count”或其他变量的文档中,因此使用不到1kb的数据,并且它只在firebase firestore中使用1次读取。

这使用计数来创建数字唯一ID。在我的使用中,我将永远不会递减,即使需要ID的文档被删除。

在需要唯一数值的集合创建时

用一个文档指定一个集合appData,只设置.doc id 在firebase防火控制台中将uniqueNumericIDAmount设置为0 使用doc.data()。uniqueNumericIDAmount + 1作为唯一的数字id 更新appData收集uniqueNumericIDAmount与firebase.firestore.FieldValue.increment(1)

firebase
    .firestore()
    .collection("appData")
    .doc("only")
    .get()
    .then(doc => {
        var foo = doc.data();
        foo.id = doc.id;

        // your collection that needs a unique ID
        firebase
            .firestore()
            .collection("uniqueNumericIDs")
            .doc(user.uid)// user id in my case
            .set({// I use this in login, so this document doesn't
                  // exist yet, otherwise use update instead of set
                phone: this.state.phone,// whatever else you need
                uniqueNumericID: foo.uniqueNumericIDAmount + 1
            })
            .then(() => {

                // upon success of new ID, increment uniqueNumericIDAmount
                firebase
                    .firestore()
                    .collection("appData")
                    .doc("only")
                    .update({
                        uniqueNumericIDAmount: firebase.firestore.FieldValue.increment(
                            1
                        )
                    })
                    .catch(err => {
                        console.log(err);
                    });
            })
            .catch(err => {
                console.log(err);
            });
    });